1.封装
- 类的基本思想是数据抽象和封装。数据抽象是一种依赖于接口和实现分离的编程技术。类的接口包括用户能执行的操作;类的实现则包括类的数据成员、负责接口实现的函数体以及定义类所需的各种私有函数。封装后的类隐藏了实现细节。
- 当我们设计类的时候,应该考虑如何才能使得类易于使用;当我们使用类时不应该考虑实现机理。
- 良好的封装能减少耦合;内部实现自由修改不影响其它类;具有清晰的对外接口。
2.继承
通过继承联系在一起的类构成一种层次关系,在层次关系的根部有一个基类,其他类直接或间接地从基类继承而来,继承得到地类成为派生类。基类负责定义在层次关系中所有类共同拥有的成员,而每个派生类定义自己特有的成员。
继承的优点是所有子类公共部分都放在了父类,使代码得到共享、避免重复,在修改或拓展继承而来的实现都比较容易。
3.多态
多态的含义为“多种形式”,把具有继承关系的多个类型称为多态类型,因为我们能使用这些类型的“多种形式”而无需在意他们的差异。引用或指针的静态类型和动态类型不同是多态性的根本所在。
京剧的子承父业,儿子代父亲上台,1.以父亲的身份表演(声明时是父类的指针或引用),2.以自己的方式演绎父亲的技巧(子类自己实现的虚函数),3.儿子特有的技巧不能演示(子类特有的方法和属性不能被调用)。
虚函数:
- 虚函数:基类希望派生类各自定义适合自身的版本的函数。
- 派生类如果不覆盖继承的虚函数,那么类似普通成员,会直接继承基类中的版本。
- 基类中的虚函数在派生类中也是虚函数,当在派生类中覆盖了某个虚函数,可以再一次用virtual关键字显式指出该函数的性质,也可以不用。
- override:c11中用来说明派生类中的虚函数,如果没有覆盖已经存在的虚函数,编译器将报错。
- final:C11中不允许后续的其他类覆盖该虚函数或不允许其他类继承。
虚函数的回避机制:当一个派生类的虚函数调用覆盖的基类的虚函数版本时,如果要调用基类版本(不要动态绑定,强迫执行虚函数的某个特定版本),使用作用域运算符可以实现。
派生类到基类的类型转换
在派生类中含有与其基类对应的组成部分,这一事实是继承的关键所在。
一个派生类包含多个组成部分:一个含有派生类自己定义的成员的子对象,以及一个与派生类继承的基类对应的子对象。因为这样,我们可以把派生类的对象当作基类对象来使用,能将基类的指针和引用绑定到派生类对象的基类部分上。
这种转换称为派生类到基类的类型转换,编译器会隐式执行这种转换。
Quote item;//基类
Bulk_quote bulk;//派生类
Quote *p = &item;//基类指针指向基类对象
p = &bulk;//基类指针指向派生类对象的基类部分
Quote &r = bulk;//基类引用绑定到派生类对象的基类部分
不存在从基类到派生类的隐式转换
- dynamic_cast可以将基类的指针或引用转换为派生类的指针或引用,但必须含有一个或一个以上的虚函数。该转换的安全检查在运行时执行,如果转换无效,返回空指针或抛出异常。
- static_cast在已知某个基类向派生类的转换时安全的前提下,可以用来强制覆盖掉编译器的检查工作。
- 当我们在容器中存放具有继承关系的对象时,我们实际上存放的通常是基类的指针,更好的选择是智能指针。
对象之间不存在类型转换
派生类向基类的自动类型转换只对指针或引用有效,在对象之间不存在这样的转换。当我们初始化或赋值一个类类型的对象时,实际上是在调用某个函数。即构造函数和赋值运算符,这些函数通常包含一个类类型的const版本的引用的参数。当我们使用一个派生类对象为一个基类对象初始化或赋值时,只有该派生类对象中的基类部分会被拷贝、移动或赋值,派生类部分将会被忽略掉。
动态绑定:
由于可以将基类的指针或引用绑定到派生类对象上,所以在使用基类的指针或引用时,我们并不清楚指针或引用绑定对象的真实类型。
静态类型: 在变量声明时的类型或表达式生成的类型,在编译时已知。
动态类型: 变量或表达式表示的内存中的对象的类型,直到运行时才知道。
1.对非虚函数的调用2.对类对象进行调用的函数,都是在编译的时候进行绑定的。
动态绑定:当且仅当通过指针或引用调用虚函数时,才会在运行时解析该调用,根据实际传入的对象类型来决定执行哪个版本的虚函数。也只有在这种情况下才有可能动态类型和静态类型不同。
抽象基类和纯虚函数
纯虚函数: 当不希望创建一个基类对象,基类代表一种通用概念,而不是具体对象时,可以定义一个纯虚函数,基类中的纯虚函数没有实际意义,无需定义。通过在声明语句分号之前书写=0说明。
抽象基类: 含有或未经覆盖直接继承纯虚函数的类时抽象基类,不能直接定义一个抽象基类的对象。
重构: 重新设计类的体系,以便将操作和数据从一个类移动到另一个类中。即在代码写好之后改进它的设计。
访问控制和继承
访问说明符protected: 派生类的成员和友元(派生类的友元)只能访问派生类对象中的基类部分的受保护成员,对于普通的基类对象中的成员不具有特殊的访问权限。
派生访问说明符: 对派生类的成员(内部实现)及友元能否直接访问直接基类的成员没什么影响。目的是控制派生类用户(派生类对象和派生类子类)对于基类成员的访问权限。
- 只有当公有继承时,派生类用户(派生类对象和派生类子类)才能使用派生类到基类的转换。对于派生类的成员和友元来说,无论什么方式继承,都能访问该转换。
- protected继承时基类的public成员在子类中的权限为protected,private继承时基类的public成员在子类中的权限为private
- 和访问说明符类似,默认情况下,class定义的派生类是私有继承的,struct定义的派生类是公有继承的。
继承中的作用域:
派生类的作用域嵌套在基类的作用域中,和其他作用域一样,派生类定义在内层作用域(即派生类)的名字将隐藏外层作用域(即基类)的名字。可以通过作用域来使用被隐藏的成员。
函数的调用过程:(p.men()/p->men())
1.确定p的静态类型
2.在静态类型对应的类中查找men,如果找不到,依次在直接基类中不断查找直到继承链的顶端。如果还是找不到就会报错。(不会查找被同名隐藏的函数,所以虚函数的形参列表必须相同)
3.找到了就进行常规的类型检查
4.如果调用合法,根据是否是虚函数产生不同的代码。
构造函数和拷贝控制
虚析构函数: 如果我们删除一个指向派生类对象的基类指针,则需要虚析构函数。通常在基类中将析构函数定义成虚函数以确保执行正确的析构函数版本。这时派生类的析构函数也是虚析构函数,使用delete时根据实际类型调用相应的析构函数。
-
构造函数;派生类的构造函数会执行直接基类的构造函数,以此类推直到继承链顶端。
-
如果基类中默认构造函数、拷贝构造函数、拷贝赋值运算符、析构函数是被删除的或者不可访问,那么派生类中对应的成员将是被删除的。因为编译器无法对派生类的基类部分进行操作。
-
如果析构函数是被删除的或者不可访问,则派生类的默认构造函数、拷贝构造函数、拷贝构造函数将是被删除的。因为编译器无法销毁派生类的基类部分。
-
当为派生类定义拷贝或移动构造函数时,通常显式使用对应的基类构造函数初始化对象的基类部分。如果没有基类部分将默认初始化。赋值运算符同理。
-
析构函数不用显式执行,而是自动调用。
-
在构造函数或析构函数中调用虚函数,应该执行所属类型相应的虚函数版本
以上内容来源于《C++ Primer》第十五章、《大话设计模式》