之前我们讲述的是类之间的嵌套关系,下面我们要介绍一个全新的操作——继承(派生)
简介
继承与派生其实是同一过程从不同的角度看
我们将保持已有类的特性而构造新类的过程称为继承,简单来说继承的目的就是实现原来设计与代码的重用,希望尽量利用原有的类
然而当新的问题出现,原有程序无法解决或不能完全解决时,需要对原有程序进行改造,在已有类的基础上新增自己的特性而产生新类的过程称为派生
基类(或父类):被继承的原有类
直接基类:直接参与派生出某类的基类
间接基类: 基类的基类甚至更高层的基类
派生类(或子类): 派生出的新类,包括三部分:
- 吸收基类的成员:默认情况下派生类包含了基类除了构造函数和析构函数之外的所有成员,但是C++11规定可以用using关键字将构造函数也继承过来。
- 改造基类的成员:如果派生类声明了一个和基类成员同名的新成员,这样基类同名的成员就被覆盖了。
- 添加新的成员:派生类在功能上有所发展。
定义
定义派生类时需要指出继承方式,如未显示指出,默认为private方式继承
继承方式 | 基类 | 派生类 |
---|---|---|
public | public 成员 | public 成员 |
protected 成员 | protected 成员 | |
private 成员 | 不可直接使用 | |
private | public 成员 | private 成员 |
protected 成员 | private 成员 | |
private 成员 | 不可直接使用 | |
protected | public 成员 | protected 成员 |
protected 成员 | protected 成员 | |
private 成员 | 不可直接使用 |
上面这个表格非常清楚得显示出了不同类型的继承规则
可以发现,在这里我们遇到了一个全新的类型:protected
现在我们可以来总结一下这三种类型的区别和联系啦
- public是公有类型,在当前类,继承该类的派生类,类外部都可以被访问
- private是私有类型,只允许被当前类的成员函数和友元访问,不能被继承该类的派生类访问,也不能在类外部直接访问
- protected是受保护类型,只允许被当前类的成员函数和友元,与继承该类的派生类访问,却不能在类外部直接访问
protected成员可以被基类的所有派生类使用,这一性质可以沿继承树无限向下传播
也就是说:基类中的protected类型数据成员和成员函数,在其protected类型的派生类中可以进行访问,派生类外部不允许对其进行访问
目前来看,在类外部如果想访问类中的成员,只能直接使用public类型的成员
protected和private都是不能被访问的,对于类外使用而言,这两个类型是同权的
那么protected类型有什么显而易见的作用呢?
用protected类型代替public类型,可以使派生类拥有访问基类private成员的权限,同时又可以保证基类的private成员不会被类外部的类实例访问
class A{
public:
int x,y;
};
class B:public A{
public:
void print() {cout<<x<<" "<<y<<endl;}
};
int main()
{
B obj;
obj.x=233;
return 0;
}
//------------------------------------------
class A{
protected:
int x,y;
};
class B:public A{
public:
void print() {cout<<x<<" "<<y<<endl;}
};
int main()
{
B obj;
//obj.x=233; ERROR!
return 0;
}
//------------------------------------------
class A{
public:
int x,y;
};
class B:protected A{
public:
void print() {cout<<x<<" "<<y<<endl;}
};
int main()
{
B obj;
//obj.x=233; ERROR
return 0;
}
//------------------------------------------
class A{
protected:
int x,y;
};
class B:protected A{
public:
void print() {cout<<x<<" "<<y<<endl;}
};
int main()
{
B obj;
//obj.x=233; ERROR
return 0;
}
访问
派生类如何访问基类的数据成员?
- 在派生类内部,使用成员名就可以引用基类的public成员和protected成员
- 当派生类重新定义了基类的成员函数时,访问方式:
base-class name:: + 成员函数
即使是函数名相同,参数不同(函数签名不同),派生类也不能直接调用基类的函数,必须要指定基类作用域
class A{
public:
void f() const{cout<<"f in A is called."};
void g() const{cout<<x<<" "<<y<<endl;}
private:
int x,y;
};
class B:public A{
public:
void f() const{cout<<"f in B is called."};
void h() const{cout<<x<<" "<<y<<endl;}
private:
int z;
};
int main()
{
B obj;
obj.f();
obj.A::f();
return 0;
}
// Output
f in B is called.
f in A is called.
初始化
Review:构造函数初始化列表
只能使用构造函数初始化列表进行初始化的数据成员:
- const data member 常数据成员 (特例:const static integer)
- reference data member 引用类型的数据成员
- member objects 数据成员是其他类(而且未提供缺省构造函数)的对象
- base class 继承类的基类(未提供缺省构造函数)
对于派生类来说,ta的部分数据成员继承于基类,在派生类内部并没有显式定义
因此在初始化这些继承而来的数据成员时,应该使用基类的构造函数
PlusCommissionEmployee::PlusCommissionEmployee(const string &first,const string &last,double sales,double rate,double salary):CommissionEmployee(first,last,sales,rate)
{setBaseSalary(salary);}
注意
分文件定义派生类时,需要使用#include
包含基类的头文件:
- 告知编译器基类的存在
- 让编译器根据类的定义确定对象的大小,派生类的对象大小取决于派生类显示定义的数据成员 和继承自基类的数据成员
- 让编译器能够判断派生类是否正确使用了基类的成员
基类与派生类之间的联系
派生类可以直接访问基类的protected数据成员,这就存在一定的缺点:
- 影响数据的有效性检查
- 派生类依赖于基类的实现
- 基类的数据成员发生改变有可能影响派生类的实现
- 软件 " 易碎 "
而且我们在扩展派生类时,可能会定义与基类成员函数名相同的函数,这时我们就将其视为对基类成员函数的重定义
class CommissionEmployee {
public:
CommissionEmployee(const string &,const string &,double,double);
double getBaseSalary() const{...};
double earnings() const;
void print() const();
protected:
string firstName;
string lastName;
double grossSales;
double commissionRate;
};
class PlusCommissionEmployee:public CommissionEmployee {
public:
PlusCommissionEmployee(const string &first,const string &last,double sales,double rate,double salary):CommissionEmployee(first,last,sales,rate)
{setBaseSalary(salary);}
//重定义
double earnings() const {
return getBaseSalary()+CommissionEmployee::earnings();
}
//重定义
void print() const() {
cout<<"base salary: ";
CommissionEmployee::print();
}
private:
double baseSalary;
};
注意:
- 我们可以通过调用基类的public成员函数,在派生类中访问基类的私有数据成员
- 当功能相同时,尽量调用成员函数,以避免代码拷贝
- 重定义基类成员函数时,如果牵扯到调用基类成员函数,就必须使用
::
限定,否则会引起无线递归- 符合软件工程的要求:使用继承,通过调用成员函数隐藏了数据,保证了数据的一致性
构造与析构
构造顺序
- 建立派生类的对象时,必须先调用基类的构造函数初始化派生类对象的继承成员
- 派生类的构造函数既可以隐式调用基类的构造函数(缺省构造函数),也可以在派生类的构造函数初始化列表中显示调用(提供初始值)
析构顺序
- 析构函数地调用顺序和构造函数的顺序相反,因此派生类的析构函数在基类析构函数之前被调用
- 全局对象:在任何函数执行前构造,在程序结束时析构
- 局部变量:
- 自动变量:对象定义时构造,块结束时析构
- 静态变量:首次定义时构造,程序结束时析构
- 全局和静态对象(均为静态存储类别)析构顺序恰好与构造顺序相反
特殊情况一:调用
exit
函数退出程序执行时,除了析构静态存储类别对象,不调用剩余对象的析构函数
特殊情况二:调用abort
函数退出程序执行时,不调用任何剩余对象的析构函数
int main()
{
{ // begin new scope
CommissionEmployee employee1("Bob","Lewis",5000,0.2);
} // end scope
cout<<endl;
PlusCommissionEmployee employee2("Lisa","Jones",7000,0.4);
cout<<endl;
PlusCommissionEmployee employee3("Mark","Sands",8000,0.3);
cout<<endl;
return 0;
}
// Output
CommissionEmployee constructor.
CommissionEmployee destructor.
CommissionEmployee constructor.
PlusCommissionEmployee constructor.
CommissionEmployee constructor.
PlusCommissionEmployee constructor.
PlusCommissionEmployee constructor.
CommissionEmployee constructor.
PlusCommissionEmployee constructor.
CommissionEmployee constructor.
包含成员对象的基类与派生类
若基类和派生类都包含其他类的对象:
- 在建立派生类的对象时,首先执行基类成员对象的构造函数,接着执行基类的构造函数,然后执行派生类的成员对象的构造函数,最后执行派生类的构造函数。
- 析构函数的调用次序与调用构造函数的次序相反。
- 建立成员对象的顺序是对象在类定义中的声明顺序。成员初始化值的顺序不影响建立对象的顺序。
这就牵扯出来一个很重要的问题:Review 对象创建的步骤
对象创建的步骤:
- 分配内存,数据成员初始化
- 按照类定义的顺序,为数据成员分配内存空间,并初始化每个数据成员
- 如果初始化列表中未指定初始化,则使用系统提供的缺省构造函数进行初始化
- 执行constructor函数体内的语句
class Obj{
public:
Obj(int x) {cout<<"Obj constructor.\n"; num=x;}
Obj() {cout<<"Obj default constructor.\n";}
~Obj() {cout<<"Obj destructor.\n";}
private:
int num;
};
class BassClass{
public:
//构造函数,显式调用Obj的构造函数
BaseClass(int x,int y):object(x)
{cout<<"BaseClass constructor.\n"; num=y;}
//缺省构造函数,隐式调用Obj的缺省构造函数
BaseClass() {cout<<"BaseClass default constructor.\n";}
~BaseClass() {cout<<"BaseClass destructor.\n";}
private:
Obj object;
int num;
};
class DerivingClass:public BaseClass{
public:
//构造函数,隐式调用BaseClass的缺省构造函数,显式调用Obj的构造函数
DerivingClass(int x,int y):_object(x)
{cout<<"DerivingClass constructor.\n"; num=y;}
//缺省构造函数,隐式调用BaseClass的缺省构造函数,隐式调用Obj的缺省构造函数
DerivingClass() {cout<<"DerivingClass default constructor.\n";}
~DerivingClass() {cout<<"DerivingClass destructor.\n";}
private:
Obj _object;
int num;
};
int main()
{
DerivingClass A(10,20);
return 0;
}
// Output
Obj default constructor.
BaseClass default constructor.
Obj constructor.
DerivingClass constructor.
DerivingClass destructor.
Obj destructor.
BaseClass destructor.
Obj destructor.
修改一下:
class Obj{
public:
Obj(int x) {cout<<"Obj constructor.\n"; num=x;}
Obj() {cout<<"Obj default constructor.\n";}
~Obj() {cout<<"Obj destructor.\n";}
private:
int num;
};
class BassClass{
public:
//构造函数,显式调用Obj的构造函数
BaseClass(int x,int y):object(x)
{cout<<"BaseClass constructor.\n"; num=y;}
//缺省构造函数,隐式调用Obj的缺省构造函数
BaseClass() {cout<<"BaseClass default constructor.\n";}
~BaseClass() {cout<<"BaseClass destructor.\n";}
private:
Obj object;
int num;
};
class DerivingClass:public BaseClass{
public:
//构造函数,显式调用BaseClass的构造函数,显式调用Obj的构造函数
DerivingClass(int x,int y):_object(x),BaseClass(x,y)
{cout<<"DrivingClass constructor.\n"; num=y;}
//缺省构造函数,隐式调用BaseClass的缺省构造函数,隐式调用Obj的缺省构造函数
DerivingClass() {cout<<"DerivingClass default constructor.\n";}
~DerivingClass() {cout<<"DerivingClass destructor.\n";}
private:
Obj _object;
int num;
};
int main()
{
DerivingClass A(10,20);
return 0;
}
// Output
Obj constructor.
BaseClass constructor.
Obj constructor.
DerivingClass constructor.
DerivingClass destructor.
Obj destructor.
BaseClass destructor.
Obj destructor.