1.面向对象的三大特征
1.1 封装
- 将抽象得到的数据和行为(或功能)相结合,形成一个有机整体,也就是将数据与操作数据的函数进行有机的结合,形成“类”,其中的数据和函数都是类的成员。
1.2 继承
- C++语言提供了类的继承机制,允许程序员在保持原有类的基础上,进行更具体更详细的说明。
- 继承的缺点:
a.父类的内部细节对子类是可见的。
b.子类从父类继承的方法在编译时就确定下来了,所以无法在运行期间改变从父类继承的方法的行为。
c.如果对父类的方法做了修改的话(比如增加了一个参数),则子类的方法必须做出相应的修改。所以说子类与父类是一种高耦合,违背了面向对象思想。
1.3 多态
- 是指一段程序能够处理多种类型对象的能力。这种多态性可以通过强制多态,重载多态,类型参数化多态,包含多态等4种形式来实现。
2.构造函数
2.1 构造函数的基本特性
- 定义:每个类都分别定义了它的对象被初始化的方式,类通过一个或者几个特殊的成员函数来控制其对象初始化过程,这些函数称为构造函数。
- 利用=default构造:C++11新标准,例如:
Test ()=default;//次函数完全等同于合成的默认构造函数;
2.2 构造函数不能为const
- 构造函数不能为const:const是可以修饰类的成员函数,但是该函数不能修改数据成员。构造函数也属于类的成员函数,但是构造函数是要修改类的成员变量,所以类的构造函数不能是const类型。
- const对象与初始化:当我们创建类的const对象时,直到构造函数完成初始化过程对象才能真正取得const属性。因此构造函数在const对象的构造过程中可以向其写值。
2.3 构造函数不能为static
- 构造函数不能为static:构造函数的目的是初始化类实例的内容,static方法没有与之关联的实例。 因此,没有static构造函数。
2.4 构造函数不能为虚函数
- 存储空间角度:虚函数相应一个指向虚表的指针,虚表指针存储在对象的内存空间。若构造函数是虚函数则需要通过虚表指针来调用,但是此时对象还未实例化即内存空间还没有,无法找到虚表。
- 使用时期角度:创建一个对象时需要确定对象的类型,而虚函数是在运行时动态确定其类型的。在构造一个对象时,由于对象还未创建成功,编译器无法知道对象的实际类型。
- 实际使用角度:虚函数主要用于在信息不全的情况下,能使重载的函数得到对应的调用;而构造函数的作用是提供初始化,在对象生命期只执行一次,不是多态行为,那么使用虚函数就没有实际意义。
2.5 构造函数列表初始化
- 如果成员是const、引用或者属于没有提供默认构造函数的类类型,必须通过构造函数初始值列表为这些成员提供初值。例如:
class ConstRef{
public:
ConstRef(int ii);
private:
int i;
const int ci;
int &r;
}
ConstRef::ConstRef(int ii){
i=ii;
ci=ii;
r=ii;
}
ConstRef::ConstRef(int ii)::i(ii),ci(ii),ri(ii){
}
- 初始化类表中必须初始化的数据成员:
a.没有默认构造函数的内嵌对象,因为这类对象初始化时必须提供参数。
b.引用类型的数据成员,因为引用类型变量必须在初始化时绑定引用的对象。
2.6 构造函数和析构函数的执行顺序
- 基类构造函数:如果有多个基类,则构造函数的调用顺序是某类在类派生表中出现的顺序,而不是它们在成员初始化表中的顺序。
- 成员类对象构造函数:如果有多个成员类对象则构造函数的调用顺序是对象在类中被声明的顺序,而不是它们出现在成员初始化表中的顺序。
- 派生类构造函数。
- 析构函数的调用执行顺序与构造函数刚好相反。
2.7 含有虚基类的派生类构造函数的执行顺序
- 虚基类的构造函数(多个虚基类则按照继承的顺序执行构造函数)。
- 基类的构造函数(多个普通基类也按照继承的顺序执行构造函数)。
- 类类型的成员对象的构造函数(按照初始化顺序)。
- 派生类的构造函数。
3.默认构造函数
3.1 默认构造函数的定义和分类
- 与普通构造函数区别:默认构造函数在声明对象的时候,可以不传递参数,使用空值或者默认值对对象进行初始化;而普通构造函数在声明对象时必须传递参数(实参),否则构造函数的参数没有值,编译出错。默认构造函数分为不带任何参数和带默认值的默认构造函数两种。
- 不带任何参数的默认构造函数:这种默认构造函数没有带任何参数,例如:
class Test{Test(){} };
- 带默认值的默认构造函数:如果一个构造函数为所有参数都提供了默认实参,则它实际上也定义了默认构造函数。例如下面,当没有给定实参或者给定了一个string实参时,两个版本的类创建了相同的对象。
Class Test{
public:
Test(string s=""):bookNo(s){
}
private:
string bookNo;
}
3.2 不能依赖合成的默认构造函数的三种情况
- 编译器只有发现类中不包含任何构造函数的情况下才会生成一个默认的构造函数。一旦定义了其他的构造函数,那么除非重新定义默认构造函数,否则类中没有默认构造函数。这条规则依据是如果类在某种情况下需要控制对象的初始化,那么该类很可能所有情况下都需要控制。
- 对于某些类来说,合成的默认构造函数可能执行错误的操作。例如内置类型或者复合类型的对象被默认初始化,它们的值是未定义的。因此,内置类型或者复合类型成员应该在类的内部初始化这些成员(类内初始化),或者定义一个自己的默认构造函数。否则用户在创建类的对象时就可能得到未定义的值。
- 编译器不能为某些类合成默认的构造函数。比如说类中包含类类型且这个成员的类型没有默认构造函数,那么编译器无法初始化该成员。对于这样的类,必须自定义默认构造函数,否则该类将没有可用的默认构造函数。
3.3 合成默认构造函数的四种情况
- 含有类对象数据成员,该类对象类型有默认构造函数:如果一个类没有任何构造函数,但是它含有一个类对象数据成员且该类对象类型有默认构造函数,那么编译器就会为该类合成一个默认构造函数,不过这个合成操作只有在构造函数真正需要被调用的时候才会发生。如果类中有多种类对象成员,则编译器按照这些类对象成员声明的顺序,在构造函数按顺序插入调用各个类默认构造函数的代码。
- 基类带有默认构造函数的派生类:当一个类派生自一个含有默认构造函数的基类时,该类也符合编译器需要合成默认构造函数的条件。编译器合成的默认构造函数将根据基类声明顺序调用上层的基类默认构造函数。同样的道理,如果设计者定义了多个构造函数,编译器将不会重新定义一个合成默认构造函数,而是把合成默认构造函数的内容插入到每一个构造函数中去。
- 带有虚函数的类:含有虚函数的类对象都含有一个虚表指针vptr,编译器需要对vptr设置初值以满足虚函数机制的正确运行,编译器会把这个设置初值的操作放在默认构造函数中。如果设计者没有定义任何一个默认构造函数,则编译器会合成一个默认构造函数完成上述操作,否则编译器将在每一个构造函数中插入代码来完成相同的事情。
- 带有虚基类的类:编译器将会在合成默认构造函数中完成虚继承中数据成员的位置。同样的,如果设计者已经写了多个构造函数,那么编译器不会重新写默认构造函数,而是把虚基类指针的代码插入已有的构造函数中。
- 合成默认构造函数总结起来就是调用对象成员或基类的默认构造函数或者为对象初始化虚表指针与虚基类指针。
3.4 默认构造函数总结
- 合成默认构造函数总是不会初始化类的内置类型及复合类型的数据成员,只有基类对象和成员类对象会被初始化。
- 分清楚默认构造函数被程序需要与被编译器需要,只有被编译器需要的默认构造函数,编译器才会合成默认构造函数。
- 只有在编译器需要默认构造函数来完成编译任务的时候,编译器才会为没有任何构造函数的类合成一个默认构造函数,或者是把这些操作插入到已有的构造函数中。
- 除合成默认构造函数的四种情况之外,如果类没有声明任何构造函数,他们就会有一个隐式而无用的默认构造函数,他们实际上并不会被合成构造出来。
4.拷贝构造函数
4.1 拷贝构造函数定义
- 定义:拷贝构造函数一种特殊的构造函数,具有一般构造函数的所有特性,函数的名称必须和类名称一致,它的一个参数必须是本类型的引用。作用是使一个已经存在的对象(由拷贝构造函数的参数指定),去初始化同类的一个新对象。如果程序员没有定义类的拷贝构造函数,系统在必要的时候自动生成一个隐藏的拷贝构造函数。这个隐藏的拷贝构造函数的功能是把初始值对象的每个数据成员的值都复制到新建立的对象中。语法形式为
类名 (类名 &对象名) {}
4.2 深拷贝和浅拷贝
- 浅拷贝:只是拷贝一个指针,并没有新开辟一个地址,拷贝的指针和原来的指针指向同一块地址,如果原来的指针所指向的资源释放了,那么再释放浅拷贝的指针的资源就会出现错误。
- 深拷贝