面向对象
封装
public
成员:可以被任意实体访问protected
成员:只允许被子类及本类的成员函数访问private
成员:只允许被本类的成员函数访问
问题
如何定义一个只能在堆上(栈上)生成对象的类?
-
只能在堆上:
- 方法:将析构函数设置为私有
- 原因:C++ 是静态绑定语言,编译器管理栈上对象的生命周期,编译器在为类对象分配栈空间时,会先检查类的析构函数的访问性。若析构函数不可访问,则不能在栈上创建对象。
-
只能在栈上
- 方法:将 new 和 delete 重载为私有
- 原因:在堆上生成对象,使用 new 关键词操作,其过程分为两阶段:第一阶段,使用 new 在堆上寻找可用内存,分配给对象;第二阶段,调用构造函数生成对象。将 new 操作设置为私有,那么第一阶段就无法完成,就不能够在堆上生成对象。
继承
-
基类(父类)——> 派生类(子类)
-
public
继承:基类成员在派生类中的访问权限保持不变 -
private
继承:基类所有成员在派生类中的访问权限都会变为私有(private)权限 -
protected
继承:基类的共有成员(public)和保护成员(protected)在派生类中的访问权限都会变为保护(protected)权限,私有成员(private)在派生类中的访问权限仍然是私有(private)权限。 -
基类成员权限 public继承 private继承 protected继承 public public private protected private private private private protected protected private protected
多态
- 是对于不同对象接收相同消息时产生不同的动作。
- C++的多态性具体体现在编译和运行两个方面:
- 在程序编译时多态性体现在函数和运算符的重载上,也叫静态多态、早绑定
- 在程序运行时的多态性通过继承和虚函数来体现,也叫动态多态、晚绑定
构造函数
问题
复制构造函数中能否传值参数?
不可以,如果允许复制构造函数传值,则会在复制构造函数中调用复制构造函数,就会形成永无休止的递归调用从而导致栈溢出。
成员初始化列表作用及哪些情况必须使用
- 更高效:少了一次调用默认构造函数的过程。
- 有些场合必须要用初始化列表:
- 常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面
- 引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面
- 没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化。
- 成员变量的初始化顺序只与他们在类中声明的顺序有关,而与在初始化列表中的顺序无关。
class A
{
private:
int n1;
int n2;
public:
A():n2(0),n1(n2+2){
}
void Print()
{
cout<<n1<<" "<<n2<<end;
}
};
int main()
{
A a;
a.Print();
return 0;
}
上面代码的输出
n1先于n2被声明,因此n1也会在n2之前被初始化,所以会先用n2+2去初始化n1,由于n2这时候还没有被初始化,因此它的值是随机的。同理,n1的值也是随机的,之后又用0初始化n2,因此,最终n2的值是0.
构造函数和析构函数的执行顺序
class A
{
public:
A(){
cout << "Construct A" << endl;}
~A(){
cout << "Destruct A" << endl;}
};
class C
{
public:
C(){
cout << "Construct C" << endl;}
~C(){
cout << "Destruct C" << endl;}
};
//Notice: C is a virtual public
class B: public A, public virtual C
{
public:
B(): a(A()), c(C()) {
cout << "Construct B" << endl;}
~B(){
cout << "Destruct B" << endl;}
C c;
A a;
};
int main(int argc, char const *argv[])
{
B b;
return 0;
}
- 输出:
Construct C
Construct A
Construct C
Construct A
Construct B
Destruct B
Destruct A
Destruct C
Destruct A
Destruct C - 在类被构造的时候,先执行虚拟继承的父类的构造函数,然后从左到右执行普通继承的父类的构造函数,然后按照定义的顺序执行对象成员的初始化,最后是自身的构造函数的调用。析构函数与之完全相反,互成镜像。
- 虚拟继承 > (从左到右)普通继承 > (按定义顺序)对象成员 > 自身
- 初始化列表中的初始化顺序按照定义的顺序初始化。
对象成员与对象成员指针
- 类被实例化时,先实例化作为成员的对象,再实例化该类;销毁时顺序正好相反。
使用
class Line
{
public:
Coordinate m_CoorA;// 对象成员
Coordinate *m_pCoorA;// 对象成员指针
}
this指针
this
指针是一个隐含于每一个非静态成员函数中的特殊指针。它指向调用该成员函数的那个对象。- 当对一个对象调用成员函数时,编译程序先将对象的地址赋给
this
指针,然后调用成员函数,每次成员函数存取数据成员时,都隐含使用this
指针。
使用
class Array
{
public:
Array(T *this, int _len){
this->len = _len;}// this指针作为每一个成员函数的隐含参数
int getLen(T *this){
return this->len;}
vois setLen(T *this, int _len){
this->len = _len;}
Array& printInfo()// 注意返回引用
{
cout<<len<<endl;
return *this;// 返回this指针,可以实现对象的链式引用
}
private:
int len;
}
int main(){
Array arr1(10);
arr1.printInfo().setLen(5);// 对象的链式引用
cout<<arr1.getLen()<<endl;
return 0;
}
深拷贝与浅拷贝
class Array
{
public:
Array()
{
m_iCount=5;
m_pArr=new int[m_iCount];
}
// 浅拷贝
Array(const Array& arr)
{
m_iCount=arr.m_iCount;
m_pArr=arr.m_pArr; // 只拷贝了指针,使得两个指针指向同一个地址。1、在调用析构函数时会delete一块内存两次,造成程序崩溃。2、任何一方的改动都会影响另一方。
}
// 深拷贝
Array(const Array& arr)
{
m_iCount=arr.m_iCount;
m_pArr=new int[m_iCount];
for(int i=0;i<m_iCount;i++){
m_pArr[i]=arr.m_pArr[i];// 将堆中内存的数据也进行拷贝
}
}
private:
int m_iCount;
int *m_pArr; // 注意:数据成员是指针
}