这里写目录标题
初始化列表
一般是能和赋值初始化互用的,以下情况只能用初始化列表,不能用赋值初始化:
-
如果对象成员对应的类没有默认构造函数,那对象成员也只能在初始化列表进行初始化。
-
如果类是继承而来,基类没有默认构造函数的时候,基类的构造函数要在派生类构造函数初始化列表中调用。
-
const成员的初始化只能在构造函数初始化列表中进行
-
引用成员的初始化也只能在构造函数初始化列表中进行
关键字public private protected
修饰类的成员
- 在类的内部(定义类的代码内部),无论成员被声明为 public、protected 还是 private,都是可以互相访问的,没有访问权限的限制。
- 在类的外部(定义类的代码之外),只能通过对象访问成员,并且通过对象只能访问 public 属性的成员,不能访问 private、protected 属性的成员。
修饰继承方式
public继承方式
- 基类中所有 public 成员在派生类中为 public 属性
- 基类中所有 protected 成员在派生类中为 protected 属性
- 基类中所有 private 成员在派生类中不能使用
private继承方式
- 基类中的所有 public 成员在派生类中为 protected 属性
- 基类中的所有 protected 成员在派生类中为 protected 属性
- 基类中的所有 private 成员在派生类中不能使用
protected继承方式
- 基类中的所有 public 成员在派生类中均为 private 属性
- 基类中的所有 protected 成员在派生类中均为 private 属性
- 基类中的所有 private 成员在派生类中不能使用
构造函数
C++编译器至少会为我们写的类提供以下几个函数:
- 一个默认构造函数(无参)
- 一个拷贝构造函数
- 一个析构函数
- 赋值运算符
如果用户自己定义了有参构造函数 编译器将不再提供无参构造函数 但任会提供拷贝构造函数
如果用户自己定义了拷贝构造函数,编译器将不再提供默认无参构造函数与默认的拷贝构造函数
拷贝构造函数与移动构造函数
拷贝构造函数
作用:主要是实现类的深拷贝(内存的拷贝),防止浅拷贝出现的内存多次释放的问题。
class A {
public:
A() {
num = new int(10);
}
~A() {
delete num;
}
int* num;
};
当程序中出现如下语句时,程序会崩溃
A a;
A b(a);
原因:系统提供的拷贝构造函数是浅拷贝,只是简单的将所有的数据进行值复制。因此对象a与b中的int指针的值也一样,即a与b中的int指针指向了同一块区域,于是当析构函数释放申请的内存时,就会出现同一块内存被释放两次的情况,导致程序崩溃
拷贝构造函数就是为了解决这个问题。我们需要手动重写拷贝构造函数,自己完成值拷贝
A(const A& a) {
num = new int(*a.num);
}
移动构造函数
作用:减少深拷贝造成的不必要复制,与移动语义和右值引用有关
右值与左值引用
struct与class
- 默认的继承访问权。class默认的是private,strcut默认的是public
- 默认访问权限:struct作为数据结构的实现体,它默认的数据访问控制是public的,而class作为对象的实现体,它默认的成员变量访问控制是private的
- class这个关键字还用于定义模板参数,就像typename。但关建字struct不用于定义模板参数
空类的大小为1
多态
静态多态
包括函数重载和函数模板。静态多态是由编译器在编译期间完成的。
函数重载:重载函数的关键是函数参数列表。包括:函数的参数数目和类型,以及参数的排列顺序。
函数的返回值不作为函数重载的依据(只有返回值不同的两个函数不能发生重载)
动态多态
动态多态是程序在运行时动态绑定函数实现多态。
派生类配合虚函数,由父类指针指向子类对象,实现运行时多态。
虚函数与纯虚函数
- 子类的虚函数可以不被子类重写,但父类的纯虚函数必须被子类重写
- 含有纯虚函数的类叫抽象类,抽象类不能实例化对象,而含有虚函数不含有纯虚函数的类可以实例化对象
构造函数、析构函数、虚函数
结论:C++中,构造函数不可以是虚函数,而析构函数可以且常常是虚函数。
- 首先,构造函数没必要时虚函数
在调用构造函数时还不能确定对象的真实类型(由于子类会调父类的构造函数);并且构造函数的作用是提供初始化,在对象生命期仅仅运行一次,因此,构造函数没必要是虚函数 - 其次,构造函数也不能是虚函数
虚函数的调用是通过虚函数表来实现的。而虚函数表是由类的实例化对象的vptr指针去寻找的。也就是说一个类的虚函数表需要类对象去寻找。那么我们就必须先创建并初始化好一个对象实例,而类的构造函数就是用来创建初始化实例对象的,这就产生了矛盾:
要想实例化对象我们就必须知道虚函数表在哪,这样才能完成初始化,而虚函数表又必须由实例化好的对象的vptr指针才能找到
这两个变成了一种死锁的状态,因此,构造函数不可以是虚函数。 - 我们使用虚函数往往是用来实现多态的,而多态是由父类指针指向子类对象实现的,那么在析构的时候如果不重写父类的析构函数,那么只会调用父类的析构函数,而父类的析构函数只会释放父类空间,那么子类的一些空间就没办法释放,造成内存泄漏。
class Father {
public:
Father() {
cout << "父类构造函数被调用" << endl;
}
~Father() {
cout << "父类析构函数被调用" << endl;
}
};
class Son : public Father {
public:
Son() {
cout << "子类构造函数被调用" << endl;
}
~Son() {
cout << "子类析构函数被调用" << endl;
}
};
Father* fa = new Son();
delete fa;
输出:
父类构造函数被调用
子类构造函数被调用
父类析构函数被调用
若将父类的析构函数改成虚函数,输出:
父类构造函数被调用
子类构造函数被调用
子类析构函数被调用
父类析构函数被调用