面向对象程序设计的核心思想是数据抽象、继承、和动态绑定。
基类和派生类
- 派生类包括自己定义的对象和继承自基类的对象。这些对象不一定连续存储。因为派生类对象含有基类的对象,所以可以把派生类当做基类使用(通过指针或引用)。
- 派生类必须使用基类的构造函数初始化基类部分; 初始化时,先初始化基类,然后按照声明顺序初始化派生类。
- 如果基类有一个静态成员,则在整个集成体系中只存在唯一实例,不能覆盖。派生类能否访问取决于它是否private
- 声明派生类时不应包含派生列表。
- 如果不希望一个类被继承,就在类名后加一个final .
类型转换与继承
- 静态类型:编译时已知。 动态类型:运行时才知道。
虚函数
- 虚函数会执行动态绑定,每个虚函数都必须有定义。派生类的虚函数应与基类中的虚函数 函数名、形参类型应完全一致。
- 派生类虚函数声明override ,1.让程序员意图更明显。 2.编译器可以发现一些错误。
- 类成员函数声明为final , 永远无法被覆盖。
- 可以使用作用域运算符强行调用某个类中的虚函数,避免动态绑定。
抽象基类
- 含有纯虚函数的类是抽象基类。
- 纯虚函数: 虚函数的函数列表后加一个 =0。 派生类中必须覆盖纯虚函数。
- 抽象基类无法创建对象。
- 抽象基类不应有private成员。
访问控制和继承
- 派生类的成员和友元只能访问派生类对象中的基类部分的受保护成员。
- 规则
- 派生类可以访问直接基类的public和protected,不能访问private。
- public继承,成员遵循原有的访问说明符。 private继承,成员都是private; protecetd继承,成员都是 protected
- 当前对象如果可以访问直接基类的成员 (用户代码可以访问public; 类成员可以访问public和protected),则可以使用派生类到基类的转换,否则不可以。
- 可以使用using声明改变成员的可访问性。 (派生类可访问的名字才可以使用using)
- class默认私有继承, struct默认公有继承
类作用域
- 名字查找
- 先确定静态类型,在静态类型对应的域中进行名字查找,如果没有找到则依次在直接基类中查找。(如果没有找到,则报错,并不会考虑动态绑定。)
- 找到后,如果是普通函数则直接调用;如果是虚函数,则根据动态绑定的类型确定该调用哪个版本。
- 名字查找先于类型检查,如果内层作用域有一个和外层作用域同名的函数,即使形参列表不一样,仍会隐藏外层作用域的函数。(不能跨作用域重载 )
- 但是对于虚函数来说,本身就是跨作用域的,并且两个虚函数的参数列表必须一致,否则就变成了上述情况,派生类中的函数不再是虚函数,并且隐藏了基类的虚函数。
构造函数与拷贝控制
应把基类中的析构函数定义为虚函数,如果发生动态匹配可以找到正确的析构函数。并且不遵循三五准则。不一定需要拷贝构造函数等..。编译器不会为虚析构函数的类合称移动操作。、
派生类析构函数只负责销毁派生类自己分配的资源。
对象创建时,先创建基类再创建派生类;析构时,先销毁派生类再销毁基类。 (只能调用直接基类的构造函数,逐层调用)。
类只能继承直接基类的构造函数,且不能继承默认、拷贝、移动构造函数。
派生类中删除的拷贝控制与基类的关系
如果基类中的默认构造函数、拷贝构造函数、拷贝赋值运算符或析构函数是删除的或者不可访问,则派生类中对应的成员也会是删除的。
如果基类的析构函数删除或不可访问,派生类合成的默认构造函数和拷贝构造函数也是被删除的。因为如果构造了基类部分,却无法析构。
容器与继承
- 如果使用容器存放集成体系的类对象,容器中保存的一般是基类的(智能)指针 。