继承(Inherit) 是一种联结类的层次模型,允许和鼓励类的重用用以扩展已存在的功能,提供了一种明确表述共性的方法,实现代码重用。继承是面向对象程序设计的重要特征。
派生(derive) 是继承的产物,派生是通过继承基类中原有的特性,并在此基础上修改原有功能或添加一些新的功能来产生新类。现实世界中利用分类、分层次进行分析和描述事物特征的事例很多,如交通工具、动物类等等。
在定义一个新类时,使用了已存在类中部分或全部特性,称继承。定义的新类称派生类(或子类),已存在的类称基类(或父类)。从一个类出发产生新类的过程称派生。派生类也可以作为基类再派生其子类,形成类的层次结构,构成一个类族。通过类的继承与派生关系,建立具有共同特征的类族,从而实现了代码重用。
在继承与派生过程中,如果一个派生类是由一个基类派生,称单一继承;由两个或以上基类共同派生称多重继承。
在继承与派生时,必须同时满足两个条件:需要从基类中继承部分或全部属性和行为特性,并在此基础上有约束或扩展。即:
(1)继承基类中部分或全部数据成员和函数成员;
(2)改变基类中部分成员数据成员和函数成员;
(3)增加新的数据成员和函数成员。
通过基类派生子类的一般格式为:
class <派生类名>:<继承方式><基类名1>,……
{ 派生类成员说明};
继承方式有三种,分别通过关键字public(公有继承)、**private(私有继承)和protected(保护继承)**说明。不同继承方式,将决定派生类如何访问从基类中继承来的不同访问权限的成员。在没有说明继承方式时,默认为private。
【例9.1】定义描述二维平面中一个点的类,并以此为基类派生出平面上圆的类。
class point//描述二维平面中一个点的类
double x,y;
public:
point(float i,float j){
x=i;y=j;
}
double area(){
return 0.0
}
};
class circle:public point// 在基类point成员基础上增加半径成员,派生了圆类
{
double radius;
public:
circle(double r=0){
radius=r;
}
double area(){
return PI*radius*radius;
} ;
};
继承与派生时,派生类继承了基类中除构造、析构函数外的所有成员,这些成员在派生类内或派生类外的访问权限,由继承方式决定。
公有继承时,基类中具有公有和保护访问权限的成员在派生类中保持不变,即作为派生类的公有和保护访问权限的成员,基类中私有访问权限的成员在派生类中不可直接访问,但可以通过基类中公有函数成员访问基类中的私有成员。
【例9.2】说明一个矩形类,通过公有继承方式,派生长方体类,通过派生类成员函数和派生类的对象访问基类中不同访问权限的成员。
#include<iostream>
using namespace std;
class Rectangle
{
float length;
protected:
float width;
public:
Rectangle(){
length=0;width=0;
}
Rectangle(float l,float w){
length=l;width=w;
}
float Area(){
return length*width;
}
float GetL(){
return length;
}
float GetW(){
return width;
}
void RPrint(){
cout<<"矩形的长为:"<<length;
cout<<"\t宽为"<<width;
}
};
class Cuboid:public Rectangle
{
float high;
public:
Cuboid(){
high=0;
}
Cuboid(float l,float w,float h):Rectangle(l,w){
high=h;
}
void CPrint()
{
cout<<"长方体的长为:"<<GetL()<<"\t宽为:"<<width;
cout<<"\t高为:"<<high<<"\t体积为:"<<high*Area()<<'\n';
}
};
int main(){
Rectangle r(10,5);
r.RPrint();
cout<<"\t面积为:"<<r.Area()<<'\n';
Cuboid c(6,3,10);
cout<<"矩形的长为:"<<c.GetL()<<"\t宽为:"<<c.GetW();
cout<<"\t面积为:"<<c.Area()<<'\n';
c.CPrint();
}
私有继承时,基类中具有公有和保护访问权限的成员成为派生类的私有成员,基类中私有访问权限的成员在派生类中不可直接访问,在派生类内可以通过基类中公有函数成员访问基类中的私有成员。
【例9.3】说明一个矩形类,通过私有继承方式,派生一个长方体类,通过派生类成员函数和派生类的对象访问基类中不同访问权限的成员。
#include<iostream>
using namespace std;
class Rectangle
{
float length;
protected:
float width;
public:
Rectangle(){
length=0;width=0;
}
Rectangle(float l,float w){
length=l;width=w;
}
float Area(){
return length*width;
}
float GetL(){
return length;
}
float GetW(){
return width;
}
void RPrint(){
cout<<"矩形的长为:"<<length;
cout<<"\t宽为"<<width;
}
};
class Cuboid:private Rectangle
{
float high;
public:
Cuboid(){
high=0;
}
Cuboid(float l,float w,float h):Rectangle(l,w){
high=h;
}
void CPrint()
{
cout<<"长方体的长为:"<<GetL()<<"\t宽为:"<<width;
cout<<"\t高为:"<<high<<"\t体积为:"<<high*Area()<<'\n';
}
};
int main(){
Rectangle r(10,5);
r.RPrint();
cout<<"\t面积为:"<<r.Area()<<'\n';
Cuboid c(6,3,10);
c.CPrint();
}
保护继承时,基类中具有公有和保护访问权限的成员成为派生类的保护成员,基类中私有访问权限的成员在派生类中不可直接访问。保护继承很少使用。
继承与派生是在继承基类成员的基础上,添加了新的成员。但在继承与派生过程中,基类的构造函数和析构函数是不能被继承的。因此在派生类中,一方面要负责为新增的成员进行初始化工作,另一方面还需要为从基类中继承来的成员进行初始化工作。
派生类为新增的成员进行初始化工作,需要在派生类中定义构造函数实现,而对从基类中继承来的成员进行初始化工作,还是由基类中的构造函数完成。
派生类的构造函数定义的一般格式为:
派生类名::派生类名(<形参表>):基类名1(<实参表>),……
{
派生类新增成员数据的初始化
}
注:如果基类没有说明构造函数,则派生类中也可以不定义构造函数,定义派生类对象时,将全部调用缺省的构造函数。
当说明派生类对象时,系统首先调用各基类的构造函数,对基类成员进行初始化,之后执行派生类的构造函数。具体调用执行顺序是:
首先,调用基类的构造函数。多重继承时,其调用顺序按照定义派生类时,对基类的说明顺序确定。
其次,执行派生类的构造函数的函数体。
派生类拷贝构造函数定义的一般格式为:
派生类名::派生类名(派生类名 &obj):基类名1(obj),……
{
完成派生类新增成员数据的拷贝
}
其中:派生类中定义拷贝构造函数的参数是派生类对象的引用。
继承与派生时,析构函数也不能继承,如果需要,则应在派生类中重新定义析构函数。派生类的析构函数与没有继承与派生的析构函数定义方法相同。
【例9.4】在继承与派生过程中,构造函数和析构函数的定义及调用关系。
#include<iostream>
using namespace std;
class BASE1
{
int x;
public:
BASE1(int a=0)
{
x=a;
cout<<"调用基类BASE1的构造函数!\n";
}
~BASE1()
{
cout<<"调用基类BASE1的析构函数!\n";
}
};
class BASE2
{
int y;
public:
BASE2(int a=0)
{
y=a;
cout<<"调用基类BASE2的构造函数!\n";
}
~BASE2()
{
cout<<"调用基类BASE2的析构函数!\n";
}
};
class DERIVE:public BASE1,public BASE2
{
int z;
public:
DERIVE(int a,int b, int c):BASE1(a),BASE2(b)
{
z=c;
cout<<"调用派生类DERIVE的构造函数!\n";
}
~DERIVE()
{
cout<<"调用派生类DERIVE的析构函数!\n";
}
};
int main(){
DERIVE d(10,20,30);
}
当派生类中还包含对象成员时,派生类构造函数定义的一般格式为:
派生类名::派生类名(<形参表>):基类名1(<实参表>),……,对象成员(<实参表>),……
{ 派生类新增成员数据的初始化}
其中:<形参表>既要满足对派生类新增成员数据的需要,同时也要包含对基类中成员数据和对象成员初始化的需要。
在多重继承时,如果多个基类中具有相同名字的成员,且在基类中的访问权限为公有(public)或保护(protected),当派生类使用到该基类中的成员时,将会出现不唯一性,称为冲突。
【例9.6】多重继承时引起冲突的例子。
#include<iostream>
using namespace std;
class BASE1
{
public:
int x;
BASE1(int a=0)
{
x=a;}
void Print(){
cout<<"x="<<x<<'\n';
};
};
class BASE2
{
protected:
int x;
public:
BASE2(int a=0)
{x=a;}
void Print(){
cout<<"x="<<x<<'\n';
}
};
class DERIVE:public BASE1,public BASE2
{
int z;
public:
DERIVE(int a,int b, int c):BASE1(a),BASE2(b)
{z=c;}
int Area(){
return x*x; }//A
void PrintC(){
cout<<"z="<<z<<'\n';
}
};
int main(){
DERIVE d(10,20,50);
d.Print();//B
}
解决冲突问题有如下两种方法:
限定基类中的访问权限为私有成员
使用作用域运算符区分基类成员
使用作用域运算符区分基类成员,是在使用基类成员时,成员名前加类名和作用域运算符,以说明属哪个基类。其格式为:
<类名>::<成员名>
#include<iostream>
using namespace std;
class BASE1
{
public:
int x;
BASE1(int a=0)
{
x=a;}
void Print(){
cout<<"x="<<x<<'\n';
};
};
class BASE2
{
protected:
int x;
public:
BASE2(int a=0)
{x=a;}
void Print(){
cout<<"x="<<x<<'\n';
}
};
class DERIVE:public BASE1,public BASE2
{
int z;
public:
DERIVE(int a,int b, int c):BASE1(a),BASE2(b)
{z=c;}
int Area(){
return BASE1::x*BASE2::x; }//A
void PrintC(){
cout<<"z="<<z<<'\n';
}
};
int main(){
DERIVE d(10,20,50);
d.BASE1::Print();//B
d.PrintC();
}
在多在继承与派生时,如果基类中,访问权限为public或protected成员,和派生类添加的新成员同名时,则不会引起冲突,**派生类的成员将覆盖基类中的同名成员**。**这种优先关系称为支配规则**。支配规则强调了**派生类中成员优先的原则**,如果需要使用基类中的成员,须使用作用域运算符,强调说明属于基类的成员。
【例9.8】继承与派生时,支配规则的使用。
#include<iostream>
using namespace std;
class BASE
{
public:
int x;
BASE(int a=0)
{
x=a;}
void Print(){
cout<<"BASE x="<<x<<'\n';
};
};
class DERIVE:public BASE
{
int x;
public:
DERIVE(int a,int b):BASE(a){
x=b;
}
int Area(){
return x*BASE::x;}//A
void PrintC(){
cout<<"DERIVE x="<<x<<'\n';
}
};
int main(){
DERIVE d(10,20);
d.BASE::Print();//B
d.PrintC();//C
}
在面向对象程序设计中,相同类型对象间可以相互赋值,但在基类与派生类对象间实现赋值存在赋值兼容关系。**由于派生类中包含从基类继承的成员,因此可以将派生类对象的值赋给基类对象,称为赋值兼容规则**。
具体规则包括以下几点:
(1)派生类的对象可以赋给基类的对象,将派生类对象中从基类继承来的成员赋给基类的对象。
(2)可以将派生类对象的地址赋给指向基类对象的指针。
(3)派生类对象可以初始化基类的引用。
如果一个派生类由多个基类共同派生,并且这些基类又具有共同的基类,则在最终产生的派生类中,会保留该共同基类数据成员的多份同名成员,在引用这些同名成员时可能会产生二义性(冲突)。如基类A分别派生了类B和类C,类B和类C共同派生类D 。
【例9.9】共同基类在最终的派生类中出现多份同名成员。
#include<iostream>
using namespace std;
class BASE
{
public:
int x;
BASE(int a){x=a;}
void Print()
{ cout<<"BASE x="<<x<<'\n';}
};
class BASE1:public BASE
{public: int y;
BASE1(int a,int b):BASE(a){y=b;}
void Print1()
{ cout<<"BASE1 x="<<x<<'\n';
cout<<"BASE1 y="<<y<<'\n';
}
};
class BASE2:public BASE
{public: int z;
BASE2(int a,int b):BASE(a) {z=b;}
void Print2()
{ cout<<"BASE2 x="<<x<<'\n';
cout<<"BASE2 z="<<z<<'\n';
}
};
class DERIVE:public BASE1,public BASE2
{ int sum;
public:
DERIVE(int a,int b,int c,int d)
:BASE1(a,b),BASE2(c,d)
{ sum=a+b+c+d;}
void PrintD()
{
//Print(); //A
Print1(); //B
Print2(); //C
cout<<"DERIVE sum="<<sum<<'\n';
}
};
int main()
{
DERIVE d(10,20,100,200);
d.PrintD();
}
从上述结果中可以看到,派生类DERIVE中,分别从派生类BASE1和派生类BASE2各继承一份共同基类BASE中的x成员,并且他们自己各自有独立数据。为使得在间接继承共同基类时,派生类中只保留一份共同基类的成员,C++提供虚基类的方法。
声明虚基类的一般格式为:
class <派生类名>:virtual <继承方式><基类名>
{…..};
其中:关键字virtual可以放在<继承方式>的前面,也可以放在后面,其作用一样。
与一般基类的初始化过程相似,虚基类的初始化同样由构造函数实现。所不同的是,由虚基类经过一次或多次派生出来的派生类,在其每一个派生类的构造函数的成员初始化列表中,必须给出对虚基类的构造函数的调用,如果未列出,则调用虚基类的缺省构造函数。C++编译器约定,在执行直接派生类的构造函数时不调用虚基类的构造函数,而是在最终派生类的构造函数中直接调用虚基类的构造函数。
【例9.9】共同基类在最终的派生类中出现多份同名成员。
#include<iostream>
using namespace std;
class BASE
{
public:
int x;
BASE(int a){x=a;}
void Print()
{ cout<<"BASE x="<<x<<'\n';}
};
class BASE1:virtual public BASE
{public: int y;
BASE1(int a,int b):BASE(a){y=b;}
void Print1()
{ cout<<"BASE1 x="<<x<<'\n';
cout<<"BASE1 y="<<y<<'\n';
}
};
class BASE2:public virtual BASE
{public: int z;
BASE2(int a,int b):BASE(a){z=b;}
void Print2()
{ cout<<"BASE2 x="<<x<<'\n';
cout<<"BASE2 z="<<z<<'\n';
}
};
class DERIVE:public BASE1,public BASE2
{ int sum;
public:
DERIVE(int a,int b,int c,int d)
:BASE1(a,b),BASE2(c,d),BASE(a+c)
{sum=a+b+c+d;}
void PrintD()
{
Print(); //A
Print1(); //B
Print2(); //C
cout<<"DERIVE sum="<<sum<<'\n';
}
};
int main()
{
DERIVE d(10,20,100,200);
d.PrintD();
}