类的继承
对于一个特定的继承关系,一般将创建的新类称为子类,被继承的原始类称为基类或父类。
多态性是指类的对象在接收同样的信息时所做出的响应不同。
继承和派生其实是一回事,子类一般也称派生类。
派生类一经声明,就继承了基类除构造函数和析构函数以外的所有成员。如果派生类定义的成员函数名称和参量表与基类的成员函数完全一致,则称派生类覆盖了基类同名成员函数。
1. 三种继承
公有派生、保护派生、私有派生。公有成员、保护成员、私有成员。
基类中的成员 | 在公有派生类中的访问属性 | 在保护派生类中的访问属性 | 在私有派生类中的访问属性 |
---|---|---|---|
公有成员 | 公有 | 保护 | 私有 |
保护成员 | 保护 | 保护 | 私有 |
私有成员 | 不可直接访问 | 不可直接访问 | 不可直接访问 |
从父类继承的私有成员不同于子类本身的私有成员,是更加封闭的私有成员,子类自身的成员函数也无法直接访问。
派生类成员函数可以自由访问自身的保护成员以及基类的保护成员,但是派生类的外部使用者(如main函数)仍无法通过派生类访问保护成员。
(类的外部:是指类的声明代码,以及在类声明外,成员函数的定义代码以外的代码)
类的保护成员的意义:
基类中如果有一种方法需要在多个子类中使用,但这种方法不会在类的外部通过"对象.成员"或"对象指针->成员"的方式使用,那么这个方法就应该被定义为protected类型。
2. 派生类的构造和析构函数
构造函数执行次序:基类构造函数→内嵌对象构造函数→派生类构造函数体中的内容。
析构函数执行次序:派生类析构函数→内嵌对象析构函数→基类析构函数。
类的多态
1. 多态的两种形式
c++中,多态性有两种不同的形式:编译时多态(或称静态绑定),运行时多态(或称动态绑定)。
- 编译时多态:
通过函数重载实现,包括普通函数重载、类成员函数重载、运算符重载。
程序在编译前就知道程序在什么地方应该调用哪个版本的函数。
(函数地址早绑定,编译阶段确定函数地址) - 运行时多态:
主要通过派生类和虚函数来实现。
收到一条指令后到底执行哪一个函数要等到程序执行到这一条指令时才能确定。
(函数地址晚绑定,运行阶段确定函数地址)
优点:动态绑定无需重新编译整个程序(只编译新编的部分)就能实现扩展,灵活性更大。几乎所有面向对象的语言都实现动态绑定,它是面向对象的环境所期望的关键特征之一。
2. 派生类对象转换为基类对象
派生类对象转换为基类对象的方式:
(1)派生类对象给基类对象赋值
(2)派生类对象初始化基类对象的引用
(3)令基类的指针指向派生类对象
利用赋值、引用或指针的形式将派生类对象转化为基类对象后,只能当作基类对象来使用。
不论派生类是否存在同名的成员函数,这样得到的基类对象所访问的成员都只能来自基类。
int main()
{
Shape *p,obj;//Shape为Line的基类
Line L1;
obj=L1;
obj.draw();
Shape &p1=L1;
p1.draw();
p=&L1;
p->draw();//以上三次 draw()函数都是在访问基类对象的 draw()
return 0;
}
3. 虚函数的作用
通过将基类的同名成员函数声明为虚函数,指向派生类的基类指针就能调用派生类中的同名成员函数,实现多态性。
//虚函数的语法
virtual 返回类型 函数名(参数表)
{
函数体
}
当基类的相关成员函数定义为虚函数后,使用基类指针或引用指向的派生类对象时,才能实现多态性。
- 派生类对象给基类对象赋值,然后基类调用派生类中的同名成员函数,结果调用的还是基类中的函数。
引用类型被初始化后不能修改,实现多态最好的方式还是使用指针方式。
- 派生类中重定义的基类虚函数仍为虚函数,同时可省略virtual关键字
- 不能定义虚构造函数,可以定义虚析构函数
多态性是指对象对同一消息的不同反应,在对象产生之前或消亡之后,多态性都没有意义。
纯虚函数和抽象类
//纯虚函数的语法定义
virtual 返回类型 函数名(参数表)=0;
纯虚函数在基类声明后,不能定义其函数体。它是为了实现多态性而存在的。
至少包含一个纯虚函数的类称为抽象类。
抽象类的使用要求:
- 抽象类不能实例化。
- 抽象类只能作为基类被继承,否则无意义。
- 可以定义指向抽象类的指针或引用,将来这个指针或引用必然指向派生类对象,从而实现多态。