part1:概述
1.OOP核心思想:
数据抽象,可以将类的接口与实现分离
使用继承,可以定义相似的类型并对其相似关系建模
动态绑定,可以在一定程度上忽略相似类型的区别,以统一的方式管理他们的对象
2.使用基类的引用或指针调用一个虚函数将发生动态绑定
part2.定义基类和派生类
1.基础类需要定义一个虚析构函数,即使该函数不执行任何实际操作。
2.基础类需要把它的两种成员函数区分开
一种是基础类希望其派生类进行覆盖的函数。一般定义为虚函数
一种是基础类希望派生类直接继承而不要改变的函数。
3.构造函数之外的非静态函数,都可以是虚函数。基础类声明的话,派生类隐世地也是虚函数。
4.派生类经常覆盖它继承的虚函数。
5.因为在派生类对象中含有与其基础类对应的组成部分,所以我们能把派生类对象当成基础类对象来使用。也能将基础类的指针或者引用绑定到派生类的对象中的基础类部分。通常称为派生类到基础类的转换。
6.每个类控制它自己的成员初始化过程。派生类首先初始化基础类的部分,然后按照声明的顺序依次初始化派生类的成员。
7.不论派生了多少派生类,每个
静态成员都只存在唯一的实例。若静态成员是可访问的,那么从基础类和派生类都能访问它。
8.作为派生类的基础类必须声明且定义。
9.final会防止继承的发生
10.可以将基础类的指针或引用绑定到派生类的对象有一层极为重要的含义:当使用基础类的引用或指针时,实际上我们并不清楚该引用所绑定对象的真是类型。可能是基础类的对象,也可能是派生类的对象
11.区分静态类型与动态类型
静态类型在编译时已知
动态类型则是变量或表达式表示的内存中的对象类型。直到运行时才可知。
如果表达式既不是指针也不是引用,则它的动态类型永远与静态类型一致。
12.不存在从基础类向派生类的隐世类型转换。
13.对象之间不存在类型转换,只能是指针和引用
当我们使用一个派生类的对象为一个基础类对象初始化或赋值时,只有该派生类对象中基础类部分会被拷贝移动或赋值,派生类部分将被忽略掉。
part3.虚函数
1.虚函数必须要有定义,即使未被使用
2.当派生类覆盖了某个虚函数时,该函数在基础类中的形参必须与派生类中的形参严格匹配。
3.如果上述2不匹配,则编译器认为新定义的函数与原有函数是相互独立的,并未覆盖。可能是不小心弄错了形参表。
可以通过使用override标记该函数,如果该函数并未覆盖已存在的虚函数,将会报错。
1)必须是基础类中存在的函数
2)形参列表一致
3)只有虚函数才能被覆盖
4.也能把函数指定为final,如此则无法覆盖该函数。
5.如果虚函数使用默认实参,则基础类和派生类中定义的默认是参数最好一致。
6.只有友元函数中的代码才需要使用作用域运算符来回避虚函数机制。
part4.抽象基类
1.不能在类内部为纯虚函数提供函数体
2.
含有纯虚函数的类是抽象基类,无法直接定义该类对象。
3.派生类的构造函数只初始化它的直接基础类。
part5.访问控制与继承
1.不考虑继承,有两种用户:普通用户,类的实现者。
普通用户编写的代码使用类的对象,只能访问类的公有(借口)成员
类的实现者负责编写类的成员和友元,成员和友元既能访问类的公有部分,也能访问私有部分
加上继承的话,就有第三种用户,就是派生类。
基础类把它希望派生类能访问的部分声明为受保护的。普通用户不能访问受保护成员,而派生类及其友元仍旧不能访问私有成员。
2.派生类向基础类转换的可访问性:
如果D继承自B:只有当D公有的继承B时,用户代码才能使用派生类向基础类的转换。
3.友元关系无法继承
4.派生类只能为那些它可以访问的名字提供using声明。
part6.继承中类的作用域
1.一个对象,引用或指针的静态类型决定了该对象的那些成员是可见的,即使静态类型与动态类型不一致。
2.派生类的成员将隐藏同名的基础类成员,即使名字不一样。
3.除了覆盖继承而来的虚函数之外,派生类最好不要重用其他定义在基础类中的名字。
4.因此基础类与派生类中的虚函数必须有相同的形参列表。
5.即使基础类的指针指向一个派生类的对象,也无法访问基础类中不包含的函数。
6.需要覆盖重载的函数,用using声明语句指定一个名字而不指定形参列表,可以覆盖所有被重载的函数
part7.构造函数与拷贝控制
1.虚析构函数,如果基础类的析构函数不是虚函数,则delete一个指向派生类的对象的基础类指针的行为未定义。
2.徐析构函数将阻止合成移动操作
3.合成拷贝控制与集成
1)如果基础类的默认构造函数,拷贝构造函数,拷贝赋值运算符或析构函数是被删除的或者不可访问,俺么派生类中的对应成员也将是被删除的。
2)如果基础类中有一个不可访问或者删除掉的析构函数,则派生类中合成的默认和拷贝构造函数将是被删除的。因为编译器无法销毁派生类对象的基础类部分。
4.需要移动操作可以在基础类中定义
5.派生类的拷贝控制成员
1)当派生类定义了拷贝或移动操作时,该操作也负责基础类部分成员在内的真个对象
2)但是,析构函数只负责自己分配的资源
6.如果想在派生类中拷贝或移动基础类部分,就要在派生类的构造函数初始化列表中显示地使用基础类的拷贝或移动构造函数:
D ( const D &d): Base(d) {};
D (D && d ): Base( std:move(d) ) {}
7.派生类的析构函数,首先执行,然后是基础类的析构函数。
8.如果构造函数或者析构函数调用了某个虚函数,则应该执行与构造函数或析构函数所属类型相对应的虚函数版本。
part8.继承的构造函数
1.构造函数中使用using,对于基础类的每个构造函数,编译器都在派生类中生成一个形参列表完全相同的构造函数。
class D : public B{
public:
using B::B;//继承B的构造函数
}
2.using不改变构造函数的访问级别。基础类中私有的在派生类中还是私有
3.两种情况不会被继承
1)派生类中含有与基础类构造函数相同的参数列表
2)默认、拷贝和移动构造函数不会被继承,会按正常方式合成
part9.容器与继承
1.当派生类对象被赋值给基础类对象时,派生类部分会被切掉。因此容器和存在继承关系的类型无法兼容
2.可以在容器中存放指针而非对象
vector < shared_ptr<Quote>> basket;
basket.push_back( make_shared<Quote>( “xxxxxx”) );
3.模拟虚拷贝,防止赋值时被切掉一部分对象。。跳过
part10.文本查询程序再探。。跳过