第七章 继承与派生
继承的基类和语法
概述
继承与派生是同一过程从不同的角度看
保持已有类的特性而构造新类的过程称为继承
在已有类的基础上新增自己的特性而产生新类的过程称为派生。
被继承的已有类称为基类(或父类)
派生出的新类称为派生类(或子类)
直接参与派生出某类的基类称为直接基类
基类的基类甚至更高层的基类称为间接基类
目的
继承的目的:实现设计与代码的重用。
派生的目的:当新的问题出现,原有程序无法解决(或不能完全解决)时,需要对原有程序进行改造。
单继承时派生类的定义
语法
class 派生类名:继承方式 基类名
{
成员声明;
}
例
class Derived: public Base
{
public:
Derived ();
~Derived ();
};
多继承时派生类的定义
语法
class 派生类名:继承方式1 基类名1,继承方式2 基类名2,…
{
成员声明;
}
注意:每一个“继承方式”,只用于限制对紧随其后之基类的继承。
例
class Derived: public Base1, private Base2
{
public:
Derived ();
~Derived ();
};
继承方式
public, private, protect
基类与派生类类型转换
公有派生类对象可以被当作基类的对象使用,反之则不可。
派生类的对象可以隐含转换为基类对象;
派生类的对象可以初始化基类的引用;
派生类的指针可以隐含转换为基类的指针。
通过基类对象名、指针只能使用从基类继承的成员。
有示例代码;
派生类的构造和析构
派生类的构造函数
默认情况下:
基类的构造函数不被继承;
派生类需要定义自己的构造函数;
C++11规定
可用using语句继承基类构造函数
但是只能初始化从基类继承的成员;
语法形式: using B::B;
若不继承基类的构造函数
派生类新增成员:派生类定义构造函数初始化;
继承来的成员:自动调用基类构造函数进行初始化;
派生类的构造函数需要给基类的构造函数传递参数。
单继承
派生类名::派生类名(基类所需的形参,本类成员所需的形参):
基类名(参数表), 本类成员初始化列表
{
//其他初始化;
};
单继承时的构造函数举例
#include<iostream>
using namespace std;
class B {
public:
B();
B(int i);
~B();
void print() const;
private:
int b;
};
B::B() {
b=0;
cout << "B's default constructor called." << endl;
}
B::B(int i) {
b=i;
cout << "B's constructor called." << endl;
}
B::~B() {
cout << "B's destructor called." << endl;
}
void B::print() const {
cout << b << endl;
}
class C: public B {
public:
C();
C(int i, int j);
~C();
void print() const;
private:
int c;
};
C::C() {
c = 0;
cout << "C's default constructor called." << endl;
}
C::C(int i,int j): B(i), c(j){
cout << "C's constructor called." << endl;
}
C::~C() {
cout << "C's destructor called." << endl;
}
void C::print() const {
B::print();
cout << c << endl;
}
int main() {
C obj(5, 6);
obj.print();
return 0;
}
多继承且有对象成员时派生的构造函数定义语法
派生类名::派生类名(形参表):
基类名1(参数), 基类名2(参数), …, 基类名n(参数),
本类成员(含对象成员)初始化列表
{
//其他初始化
};
构造函数的执行顺序
调用基类构造函数。
顺序按照它们被继承时声明的顺序(从左向右)。
对初始化列表中的成员进行初始化。
顺序按照它们在类中定义的顺序。
对象成员初始化时自动调用其所属类的构造函数。由初始化列表提供参数。
执行派生类的构造函数体中的内容。
派生类的复制构造函数
-
若无声明复制构造函数
编译器会在需要时生成一个隐含的复制构造函数;
先调用基类的复制构造函数;
再为派生类新增的成员执行复制;
-
若有定义
一般都要为基类的复制构造函数传递参数。
复制构造函数只能接受一个参数,既用来初始化派生类定义的成员,也将被传递给基类的复制构造函数。
基类的复制构造函数形参类型是基类对象的引用,实参可以是派生类对象的引用
例如: C::C(const C &c1): B(c1) {…}
派生类的析构函数
析构函数不被继承,派生类如果需要,要自行声明析构函数。
声明方法与无继承关系时类的析构函数相同。
不需要显式地调用基类的析构函数,系统会自动隐式调用。
先执行派生类析构函数的函数体,再调用基类的析构函数。
派生类成员的标识与访问
访问从基类继承的成员
当派生类与基类中有相同成员时:
若未特别限定, 则通过派生类对象使用的是派生类中的同名成员;
如果要通过派生类对象访问基类中被隐藏的同名成员,
应使用基类名和作用域操作符(::)来限定;
二义性问题
如果从不同基类继承了同名成员, 但是在派生类中没有
定义同名成员, “派生类对象名或引用名.成员名”,
"派生类指针->成员名"访问成员存在二义性问题;
解决方式一:用类名限定;
解决方式二:同名隐藏;
虚基类
是不是用类名限定就可以解决重名问题?有这么简单吗?
比如继承的有同名成员的基类又继承自一个最远基类,就很麻烦;
问题与解决
问题:
当派生类从多个基类派生, 而这些基类又有共同基类,
则在访问此共同基类中的成员时, 将产生冗余,并有可能
因冗余带来不一致性;
虚基类声明:
class B1:virtual public B
作用:
主要用来解决多继承时可能发生的对同一基类继承多次
而产生的二义性问题;
为最远的派生类提供唯一的基类成员, 而不重复产生多次复制;
注意:
在第一级继承时就要将共同基类设计为虚基类;
虚基类不是万能的, 一些情况须慎用;
虚基类带来的问题: 其派生类构造函数
建立对象时所指定的类称为最远派生类
虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的
在整个继承结构中, 直接或间接继承虚基类的所有派生类,
都必须在构造函数的成员初始化表中为虚基类的构造函数列出参数;
如果未列出, 则表示调用该虚基类的默认构造函数;
在建立对象时, 只有最远派生类的构造函数调用虚基类的构造函数,
其他类对虚基类构造函数的调用被忽略;
小结
主要内容
继承与派生的基本概念
单继承与多继承
类成员的访问控制
派生类对象的构造和析构
派生类与基类对象的类型转换
类成员的标识与访问
虚继承