1.面向对象的程序设计的核心思想是数据抽象、动态绑定和继承。
继承:根部称作基类,继承得来的类称作派生类。派生类必须通过使用派生类列表来明确指出它是从哪个基类继承而来的,列表形式是一个冒号,后面紧跟逗号分隔的基类列表。基类可将函数声明成虚函数来让派生类定义自己的版本。
动态绑定:动态绑定指的是在运行时才知道函数的运行版本,因此我们在使用基类的引用(或指针)调用一个虚函数时会发生动态绑定。
2.定义基类和派生类
a.基类通常该定义一个虚析构函数;
b.成员函数如果没被声明为虚函数,其解析过程发生在编译时而非运行时。
c.派生类和其他类一样能访问基类公有成员,不能访问私有成员。但是派生类能访问受保护(protected)成员,其他类不行。
d.类派生列表的基类可以使用访问说明符:public、protected或者private
e.一个派生类对象包含多个部分:一个含有派生类自己定义的成员,一个派生类继承基类对应的子对象。自己理解:只需一个派生类对象,在需要时可转化为基类的引用
f.派生类对象中从基类继承过来的成员不能使用派生类的构造函数初始化,必须使用基类的构造函数初始化。
g.派生类的声明包含类名但是不包含它的派生列表。
h.如果想将某个类用作基类,就必须定义而非仅仅声明,防止继承用关键词final。
i.不存在从基类向派生类的隐式转换,对象之间不存在类型转换。因此派生类对象的引用向基类转换时,调用的是基类的构造函数,也就只能调用基类的成员。派生类的成员就被忽略掉了。
3.虚函数
a.引用或指针的静态类型与动态类型不同这一事实是c++语言支持多态性的根本所在。
b.新标准中可以使用override关键字来说明派生类中的虚函数。
C.
一个函数在基类中被声明为虚函数,那么在派生类override覆盖基类的这个函数时可以不加virtual关键字,因为一个函数一旦被声明为虚函数时,那么它在所有派生类中也是虚函数。
一个派生类的函数如果覆盖了基类的虚函数,那么这个函数的参数和返回类型必须和虚函数一致。
自己测试了下,当派生类覆盖基类的函数形参或者返回类型与基类的虚函数不一致时,这个函数被编译器认为是派生类重新自己定义的新的函数,而不被认为是覆盖掉基类的虚函数。如果我们在实际变成中发生这种错误是很难发现的,我在测试时是在派生类的函数中添加了override关键字发现的,这也是c++11所添加它的原因。
但存在一个例外,就是返回类型如果是类本身的指针或引用时,这个规则无效。就是基类可以返回base*或base&,派生类可以返回dervie*或derive&
结论:基类中的虚函数在派生类中隐含的也是一个虚函数,当派生类覆盖了某个虚函数时,该函数在基类总的形参必须与派生类中的形参严格匹配
d.如果将一个函数声明成final了,则之后任何尝试覆盖该函数的操作都将引发错误。
4.抽象基类
‘a.纯虚函数
新定义的disc_quote是一个打折通用的基类,而不是具体的折扣策略,所以我们不希望用户来定义一个disc_count,而是定义具体的折扣策略。
我们可以将disc_quote中的net_price定义为一个纯虚函数来实现。这样可以清晰的告诉用户当前这个net_price没有实际意义的。和普通虚函数不一样。
一个纯虚函数无需定义,我们通过在函数体的位置后面加上=0 即可。就可以将一个虚函数变为纯虚函数。其中=0只能出现在类内部的虚函数声明语句处。
注意:我们也可以为虚函数提供定义,不过函数体必须定义在类的外部,也就是说我们不能在类的内部为一个=0的函数提供函数体。
b.含有纯虚函数的类是抽象基类
含有(未经覆盖直接继承)纯虚函数的类是抽象基类,抽象基类负责定义接口,而后续的其他类可以覆盖接口,我们不能直接创建一个抽象基类的对象。
我们可以定义抽象基类的派生类对象,前提是覆盖了纯虚函数的接口。
注意:我们不能创建抽象基类的对象。
c.派生类构造函数只初始化它的直接基类
基类quote----->抽象基类disc_quote----->派生类bulk_quote
-->表示继承关系
那么我们定义bulk_quote的构造函数时只能初始化disc_quote,在由disc_quote的构造函数来初始化quote的成员。
关键概念重构:
重构负责重新设计类的体系以便将操作和/或数据从一个类移动到另一个类中,对于面向对象的应用程序来说,重构是一种很普遍的现象。
5.访问控制和继承
公有继承(public)、私有继承(private)、保护继承(protected)是常用的三种继承方式。
a. 公有继承(public)
公有继承的特点是基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态,而基类的私有成员仍然是私有的,不能被这个派生类的子类所访问。
b. 私有继承(private)
私有继承的特点是基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问。
c. 保护继承(protected)
保护继承的特点是基类的所有公有成员和保护成员都成为派生类的保护成员,并且只能被它的派生类成员函数或友元访问,基类的私有成员仍然是私有的。
d.派生类向基类转换的可访问性
1只有当derive类公有的继承base类时,用户代码才能使用派生类向基类的转换,如果derive继承base的方式是受保护的或者私有的,则用户代码不能使用该转换。
2不论derive以什么方式继承base类,derive的成员函数和友元都能使用派生类向基类的转换,派生类向其直接基类的类型转换对于派生类的成员和友元来说是永久可访问的。
3如果derive继承base的方式是共有的或者受保护的,则derive的派生类的成员和友元可以使用derive向base的类型转换,反之,如果derive继承base的方式是私有的,则不能使用
e.通过在类的内部使用using 声明语句,我们可以将该基类的直接或间接基类中的任何可访问成员标记出来,using 声明语句中名字的访问权限由它所在的访问说明符来决定。
6.继承中的类作用域
每个类定义自己的作用域,在这个作用域内我们定义类的成员,当存在继承关系时,派生类的作用域嵌套在其基类作用域之内。如果一个名字在自己的作用域内无法解析,那么就会在基类中查找。
名字查找先于类型检查。
7.构造函数和拷贝控制
a.虚析构函数:使用delete一个动态对象时将执行其析构函数,当delete指针时,其指针的静态类型与被删除对象的动态类型不一样,但是编译器需执行动态类型的析构函数。我们可以通过在基类中将析构函数定义成虚函数来确保。
8.容器和继承
当我们使用容器存放继承体系中的对象时,通常必须采用简介存储的方式,因为不允许在容器中保存不同类型的元素,所以我们不能把具有继承关系的多种类型
的对象直接存放在容器中。
注意:当派生类对象被赋值给基类对象时,其中的派生类部分将被切掉,因此容器和存在继承关系的类型无法兼容。
在容器中放置(智能)指针而非对象
当我们希望在容器中存放具有继承关系的对象时,我们实际上存放的通常是基类的指针,更好的是存放智能指针,和往常一样,这些指针所指对象的动态类型可能是
基类类型,也可能是派生类类型。
正如我们可以把派生类的指针转换为基类的指针一样
我们也可以把派生类的智能指针转换为基类的智能指针。
std::vector<std::shared<quote>>basket;