《C++ Primer Plus》第11章知识点总结
继承的作用
(1)可以在已有类的基础上添加功能
(2)可以给类添加数据
(3)可以修改类方法的行为
派生一个类
class RatedPlayer : public TableTennisPlayer
{
...
};
冒号指出RatedPlayer 类的基类是TableTennisPlayer类。上述特殊的声明头表明TableTennisPlayer时一个公有基类,这被称为公有派生。
使用公有派生,基类的公有成员将称为派生类的公有成员;基类的私有部分也将称为派生类的一部分,但只能通过基类的公有和保护方法访问
派生类对象存储了基类的数据成员(派生类继承了基类的实现)
派生类对象可以使用基类的方法(派生类继承了基类的接口)
构造函数必须给新成员(如果有的话)和继承的成员提供数据
创建派生类对象时,程序首先创建基类对象。如果派生类构造函数没有调用基类构造函数,程序将使用默认的基类构造函数。所以除非要使用默认构造函数,否则应显式调用正确的基类构造函数。
有关派生类构造函数的要点如下
(1)首先创建基类对象
(2)派生类构造函数应通过成员初始化列表将基类信息传递给基类构造函数
(3)派生类构造函数应初始化派生类新增的数据成员
(4)创建派生类对象时,程序首先调用基类构造函数,然后再调用派生类构造函数
(5)派生类对象过期时,程序会首先调用派生类析构函数,然后再调用基类析构函数
派生类和基类之间的特殊关系
(1)基类指针可以在不进行显式类型转换的情况下指向派生类对象
(2)基类引用可以在不进行显式类型转换的情况下引用派生类对象
//RatedPlayer是派生类,TableTennisPlayer是基类
RatedPlayer rplayer1(1140,"Malloy","Duck",true);
TableTennisPlayer& rt=rplayer1;//基引
TableTennisPlayer* pt=&rplayer;//基指
rt.Name();//invoke Name() with reference
pt->Name();//invoke Name() with pointer
然而,基类指针或引用只能用于调用基类方法,而不能调用派生类方法
另一方面,不可以将基类对象和地址赋给派生类引用和指针
继承:is-a关系
C++有3种继承方式:公有继承、保护继承和私有继承
不要用公有继承建立is-like-a关系、is-implemented-as-a(作为...来实现)关系、uses-a关系和has-a关系
多态公有继承
有两种重要的机制可用于实现多态公有继承
(1)在派生类中重新定义基类的方法
(2)使用虚方法
虚方法关键词virtual
如果类的方法没有使用关键词virtual,程序将根据引用类型或指针类型选择方法
如果使用了virtual,程序将根据引用或指针的对象的类型来选择方法。下面看两个例子大家就明白了
//Brass是基类,BrassPlus是派生类
//ViewAcct()不是虚的
Brass dom("Dominc",11224,412.52);
BrassPlus dot("Domincccc",2332,123.52);
Brass& b1_ref=dom;//这个引用类型是Brass
Brass& b2_ref=dot;//这个引用类型是Brass
b1_ref.ViewAcct();//use Brass::ViewAcct()
b2_ref.ViewAcct();//use Brass::ViewAcct()
//ViewAcct()是虚的
Brass dom("Dominc",11224,412.52);
BrassPlus dot("Domincccc",2332,123.52);
Brass& b1_ref=dom;//这个引用类型是Brass
Brass& b2_ref=dot;//这个引用类型是Brass
b1_ref.ViewAcct();//use Brass::ViewAcct()
b2_ref.ViewAcct();//use BrassPlus::ViewAcct()
//因为这个方法是虚的,所以由对象dot的类型决定
注意:关键字virtual只用于类声明的方法原型中,所以在方法定义中不用写
非构造函数不能使用成员初始化列表语法,但派生类方法可以调用公有的基类方法
虚析构函数
如果析构函数不是虚的,则将只调用对应于指针类型的析构函数。如果析构函数时虚的,将调用相应对象类型的析构函数。因此,使用虚析构函数可以确保正确的析构函数序列被调用
静态联编和动态联编
联编:将源代码中的函数调用解释为执行待定的函数代码块被称为函数名联编
静态联编:在编译过程中进行联编被称为静态联编
动态联编:编译器生成能够再程序运行时选择正确的虚方法的代码,被称为动态联编
向上强制转换:将派生类引用或指针转换为基类引用或指针,这使公有继承不需要进行显式类型转换。该规则是is-a关系的一部分
向下强制转换:将基类指针或引用转换为派生类指针或引用,如果不使用显式类型转换,则这是不允许的。原因是is-a关系通常是不可逆的
关于虚方法的几点注意
(1)构造函数
构造函数不能是虚函数。创建派生类对象时,将调用派生类的构造函数,而不是基类的构造函数,然后,派生类的构造函数将使用基类的一个构造函数,这种顺序不同于继承机制。因此,派生类不继承基类的构造函数,所以将类构造函数声明为虚的没什么意义
(2)析构函数
析构函数应当是虚函数,除非类不用做基类
(3)友元
友元不能是虚函数,因为友元不是类成员,而只有成员才能是虚函数
(4)没有重新定义
如果派生类没有重新定义函数,将使用该函数的基类版本
(5)重新定义将隐藏方法
重新定义继承的方法并不是重载。如果重新定义的派生类中的函数,将不只是使用相同的函数参数列表覆盖基类声明,无论参数列表是否相同,该操作将隐藏所有的同名基类方法
这引出两条经验规则
①如果重新定义继承的方法,应确保与原来的原型完全相同,但如果返回类型是基类引用或指针,则可以修改为指向派生类的引用或指针。这种特性被称为返回类型协变,因为允许返回类型随类型的变化而变化
②如果基类声明被重载了,则应再派生类中重新定义所有的基类版本
访问控制:protected
private和protected之间的区别只有再基类派生的类中才会表现出来。派生类的成员可以直接访问基类的保护成员,但不能直接访问基类的私有成员
抽象基类
C++通过使用纯虚函数提供未实现的函数。纯虚函数声明的结尾处为=0,例如
virtual double Area() const=0;
当类声明中包含纯虚函数时,则不能创建该类的对象。这里的理念是,包含纯虚函数的类只用作基类
继承和动态内存分配
假设基类使用了动态内存分配
构造函数使用new时需要的特殊方法:析构函数、复制构造函数、重载赋值运算符
(1)派生类不使用new时,不需要为派生类定义显式析构函数、复制构造函数、重载赋值运算符
(2)派生类使用new时,必须为派生类定义显式析构函数、复制构造函数、重载赋值运算符
类设计回顾
复制构造函数
在下述情况下,将使用复制构造函数:
(1)将新对象初始化为一个同类对象
(2)按值将对象传递给函数
(3)函数按值返回对象
(4)编译器生成临时对象
赋值运算符
默认的赋值运算符用于处理同类对象之间的赋值。
如果语句创建新的对象,则使用初始化;如果语句修改已有对象的值,则是赋值
有关使用基类方法的说明
(1)派生类对象自动使用继承而来的基类方法,如果派生类没有重新定义该方法
(2)派生类的构造函数自动调用基类的构造函数
(3)派生类的构造函数自动调用基类的默认构造函数,如果没有再成员初始化列表中指定其他构造函数
(4)派生类构造函数显式地调用成员初始化列表中指定的基类构造函数
(5)派生类方法可以使用作用域解析运算符来调用公有的和受保护的基类方法
(6)派生类的友元函数可以通过强制类型转换,将派生类引用或指针转换为基类引用或指针,然后使用该引用或指针来调用基类的友元函数