13. 类继承
13.2 继承: is-a关系
c++三种继承方式:
公有继承
- 建立一种is-a关系,即派生类对象也是一个基类对象,可以对基类对象执行的任何操作,也可以对派生类对象执行
保护继承
私有继承
13.3 多态公有继承
- 在派生类中重新定义基类的方法。
- 使用虚方法,使用关键词virtual
- 基类声明了一个虚析构函数,为了确保释放派生对象时,按正确的顺序调用析构函数,
- 关键字virtual只用于类声明的方法原型中
- 派生类并不能直接访问基类的私有数据,而必须使用基类的公有方法才能访问这些数据
- 派生类构造函数在初始化基类私有数据时,采用的是成员初始化列表语法

编译器对虚方法使用动态联编。
通常,编译器处理虚函数的方法是:给每个对象添加一个隐藏成员。隐藏成员中保存了一个指向函数地址数组的指针。这种数组称为虚函数表(virtual function table,vtbl)。虚函数表中存储了为类对象进行声明的虚函数的地址。例如,基类对象包含一个指针,该指针指向基类中所有虚函数的地址表。派生类对象将包含一个指向独立地址表的指针。如果派生类提供了虚函数的新定义,该虚函数表将保存新函数的地址:如果派生类没有重新定义虚函数,该b1将保存函数原始版本的地址。如果派生类定义了新的虚函数,则该函数的地址也将被添加到vb中(参见图13.5)。注意,无论类中包含的虚函数是1个还是10个,都只需要在对象中添加1个地址成员,只是表的大小不同而已。
调用虚函数时,程序将查看存储在对象中的vbl地址,然后转向相应的函数地址表。如果使用类声明中定义的第一个虚函数,则程序将使用数组中的第一个函数地址,并执行具有该地址的函数。如果使用类声明中的第三个虚函数,程序将使用地址为数组中第三个元素的函数。
- 每个对象都将增大,增大量为存储地址的空间:
- 对于每个类,编译器都创建一个虚函数地址表(数组):
- 对于每个函数调用,都需要执行一项额外的操作,即到表中查找地址。
通常应给基类提供一个虚析构函数,即使它并不需要析构函数。
友元不能是虚函数,因为友元不是类成员,而只有成员才能是虚函数
这引出了两条经验规则:第一,如果重新定义继承的方法,应确保与原来的原型完全相同,但如果返回类型是基类引用或指针,则可以修改为指向派生类的引用或指针(这种例外是新出现的)。这种特性被称为返回类型协变(covariance of return type),因为允许返回类型随类类型的变化而变化:
访问控制 protected
访问类别,这种类别用关键字protected表示。关键字protected与private相似,在类外只能用公有类成员来访问protected部分中的类成员。private和protected之间的区别只有在基类派生的类中才会表现出来。派生类的成员可以直接访问基类的保护成员,但不能直接访问基类的私有成员。因此,对于外部世界来说,保护成员的行为与私有成员相似:但对于派生类来说,保护成员的行为与公有成员相似。
13.6 抽象基类
c++通过使用纯虚函数提供未实现的函数。纯虚函数声明的结尾处为=0,如:
1 |
|
当类声明中包含纯虚函数时,则不能创建该类的对象。这里的理念是,包含纯虚函数的类只用作基类。要成为真正的ABC,必须至少包含一个纯虚函数。原型中的-0使虚函数成为纯虚函数。这里的方法Area()没有定义,但C++甚至允许纯虚函数有定义。例如,也许所有的基类方法都与Move()一样,可以在基类中进行定义
在原型中使用=0指出类是一个抽象基类,在类中可以不定义该函数。
13.7 继承和动态内存分配
基类使用动态内存分配,并重新定义赋值和复制构造函数,这将怎样影响派生类的实现呢?
第一种情况:派生类不使用new
不需要定义显式析构函数、复制构造函数和赋值运算符
第二种情况:派生类使用new

必须为派生类定义显式析构函数、复制构造函数和赋值运算符

复制构造函数
hasDMA复制构造函数只能访问hasDMA的数据,因此它必须调用baseDMA复制构造函数来处理共享的baseDMA数据
对于赋值运算符,这是通过使用作用域解析运算符显式地调用基类的赋值运算符来完成的。

作为hasDMA类的友元,该函数能够访问style成员。然而,还存在一个问题:该函数如不是baseDMA类的友元,那它如何访问成员lable和rating呢?答案是使用baseDMA类的友元函数operator<<()。下一个问题是,因为友元不是成员函数,所以不能使用作用域解析运算符来指出要使用哪个函数。这个问题的解决方法是使用强制类型转换,以便匹配原型时能够选择正确的函数。因此,代码将参数const hasDMA&转换成类型为const baseDMA&的参数:

13.8 类设计回顾
1. 默认构造函数

2. 复制构造函数

3. 赋值运算符


4. 构造函数不能被继承
构造函数是不能继承的,也就是说,创建派生类对象时,必须调用派生类的构造函数。然而,派生类构造函数通常使用成员初始化列表语法来调用基类构造函数,以创建派生对象的基类部分。如果派生类构造函数没有使用成员初始化列表语法显式调用基类构造函数,将使用基类的默认构造函数。在继承链中,每个类都可以使用成员初始化列表将信息传递给相邻的基类。C++11新增了一种让您能够继承构造函数的机制,但默认仍不继承构造函数。