通过继承机制,可以方便地利用一个已有的类建立新类,重用已有软件中的部分甚至很大的部分,例如微软基础类就是通过类的继承来体现类的可重用性和可扩充性。
1.继承和派生的概念
所谓“继承”就是在一个或多个已存在的类的基础上建立一个新的类。已存在的类称为“基类”、“父类”或“一般类”。新建立的类称为“派生类”、“子类”或“特殊类”。
#include<iostream>
using namespace std;
class Circle
{
public:
void SetRadious(const int &r){radious = r;}
int GetRadious(){return radious;}
void ShowRadious()
{
cout<<"Base class Circle:radious="<<radious<<endl;
}
private:
int radious;
};
class Cylinder: public Circle
{
public:
void SetHeight(const int &h){height = h;}
int GetHeight(){return heigh;}
void ShowHeight()
{
cout<<"Derived class Cylinder:height="<<height<<endl;
}
private:
int height;
};
基类中的成员 | 在公用派生类中的访问属性 | 在私有派生类中的访问属性 | 在保护派生类中的访问属性 |
私有成员 | 不可访问 | 不可访问 | 不可访问 |
公用成员 | 公用 | 私有 | 保护 |
保护成员 | 保护 | 私有 | 保护 |
成员的同名问题(隐藏,重载,覆盖之隐藏)
#include<iostream>
using namespace std;
class Circle
{
public:
void Set(const int &r){radius = r;}
void Show()
{
cout<<"Base class Circle:radius="<<radius<<endl;
}
private:
int radius;
};
class Cylinder:public Circle
{
public:
void Set(const int &r,const int &h)
{Circle::Set(r);height = h;}
void Show()
{
Circle::Show();
cout<<"Derived class Cylinder:height="<<height<<endl;
}
private:
int height;
};
int main()
{
Cylinder obj;
obj.Set(10,20);
obj.Show();
// obj.Set(10); 被隐藏了,导致参数错误。
obj.Circle::Set(30);
obj.Circle::Show();
return 0;
}
派生类重定义基类数据成员的例子:
#include<iostream>
using namespace std;
class Base
{
public:
float y;
void set(const int &n){x=n;}
void show(){cout<<"Base class:x="<<x<<endl;}
private:
int x;
};
class Derived:public Base
{
public:
void set(int i,int j,int k){Base::set(i);y=j;z=k;}
void show(){
Base::show();
cout<<"Base class:y="<<Base::y<<endl;
cout<<"Derived class:y="<<y<<endl;
cout<<"Derived class:z="<<z<<endl;
};
private:
int y,z;
};
int main()
{
Derived obj;
obj.set(1,2,3);
obj.Base::y=12.3;
obj.show();
return 0;
}
2.派生类的构造函数和析构函数
派生类派生类Student的构造函数有6个形参,前3个形参作为调用基类构造函数的实参,后3个形参为对派生类新增数据成员初始化所需要的参数。
class Student: public Person
{public:
Student(char *Name, char Sex, int Age, char *Id,
char *Date, float Score): Person(Name, Sex, Age)
{ /*在构造函数的函数体中只对派生类
新增的数据成员初始化*/
strcpy(id, Id); strcpy(date, Date); score = Score;
cout << " The constructor of derived class
Student is called." << endl;
}
~Student( ) //派生类析构函数
{ cout << " The destructor of derived class
Student is called." << endl; }
定义简单派生类构造函数的一般形式为:
<派生类构造函数名>(<总参数列表>): <基类构造函数名>(<参数表>)
{
<派生类新增数据成员初始化>
};
在建立一个对象时,执行构造函数的顺序是:
(1)最先调用基类的构造函数,对基类数据成员初始化。
(2)再执行派生类构造函数的函数体,对派生类新增数据成员初始化。
3.多重继承(派生类由多个基类派生)
<派生类名>(<总参数列表>): <基类名1>(<参数表1>),…,<基类名n>(<参数表n>)
{
<派生类新增数据成员的初始化>
};
其中,<总参数表>必须包含完成所有基类数据成员初始化所需的参数。
先调用所有基类的构造函数,再执行派生类构造函数的函数体。所有基类构造函数的调用顺序将按照它们在继承方式中的声明次序调用,而不是按派生类构造函数参数初始化列表中的次序调用。
从上节的介绍可知,如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留该间接共同基类成员的多份同名成员。这种情况有时是必要的,但是由于保留间接共同基类的多份成员,不仅占用较多的存储空间,还增加了访问这些成员时的困难,容易出错。为了解决这个问题,C++提供了虚基类(virtual base class)的方法,使得在继承间接共同基类时只保留其一份成员
class Base
{…};
class Base1: virtual publicBase
{…};
class Base2: virtual publicBase
{…};
class 派生类名: virtual 继承方式 基类名
为了保证虚基类在派生类中只继承一次,应当在该基类的所有直接派生类中都把基类声明为虚基类。否则仍然会出现对基类的多次继承。
如果在虚基类中定义了带参数的构造函数,而且没有定义默认构造函数,则在其所有派生类(包括直接派生或间接派生的派生类)中,都要通过构造函数的初始化表对虚基类进行初始化。
#include<iostream>
using namespace std;
class Base //声明基类Base
{public: //公用部分
Base(int m,int n):x(m),y(n){} //基类构造函数
protected: //保护部分
int x, y;
};
class Base1: virtual public Base
{public:
Base1(int m,int n,int k): Base(m,n) { z1=k; }
protected:
int z1;
};
class Base2: virtual public Base
{public:
Base2(int m,int n,int p):Base(m,n) { z2=p; }
protected:
int z2;
};
class Derived:public Base1, public Base2
{public:
Derived(int m,int n,int o,int p,int q):
Base(m,n),Base1(m,n,o),Base2(m,n,p) { z=q; }
void show ()
{ cout<<"x="<<x<<endl;
cout<<"y="<<y<<endl;
cout<<"z1="<<z1<<endl;
cout<<"z2="<<z2<<endl;
cout<<"z="<<z<<endl;
}
protected:
int z;
};
int main()
{
Derived obj(12,13,14,15,16);
obj.show();
return 0;
}
派生类构造函数调用的次序有3个原则:
(1)同一层中对虚基类构造函数的调用优先于对非虚基类构造函数的调用。
(2)若同一层次中包含多个虚基类,则这些虚基类的构造函数按照它们在继承方式中的声明次序调用。
(3)若虚基类由非虚基类派生出来,则仍然先调用基类构造函数,再按派生类中构造函数的执行顺序调用。
派生类的析构函数调用的次序与构造函数的正好相反。如果存在虚基类时,在析构函数的调用过程中,同一层对普通基类析构函数的调用总是优先
基类对象与公用派生类对象之间的赋值兼容关系具体表现在以下3个方面:
(1)公用派生类对象可以向基类对象赋值
(2)公用派生类对象可以代替基类对象向基类对象的引用进行赋值或初始化
(3)如果函数的参数是基类对象或基类对象的引用,相应的实参可以使用公用派生类对象
(4)公用派生类对象的地址可以赋给指向基类对象的指针变量,指向基类对象的指针也可以指向公用派生类对象。但是通过指向基类对象的指针只能访问公用派生类对象中的基类成员,而不能访问公用派生类对象新增加的成员。
5.聚合与组合
前面学习的继承描述的是类与类之间的一般与特殊的关系,是“is-a”关系。如果A是B的一种,则允许A继承B的功能和属性。如研究生是学生的一种,那么研究生类可从学生类派生;汽车是交通工具的一种,小汽车是汽车的一种,那么汽车类可从交通工具类派生,小汽车类可以从汽车类派生。
而这里所说的聚合与组合描述的是类与类之间的整体与部分的关系。聚合关系中成员对象可以脱离整体对象独立存在,而组合关系中的部分和整体具有统一的生命周期。一旦整体对象不存在,部分对象也将不存在。组合关系也被称为是一种强聚合关系。
聚合关系是“has-a”关系,组合关系是“contains-a”关系。例如,计算机和CPU的关系、公司和员工的关系是一种聚合关系,而人和大脑的关系、窗口和其中的按钮的关系是一种组合关系。表现在代码层,这两种关系是一致的,为部分类对象(也称为子对象)以类属性的形式出现在整体类的定义中。区分它们只能从语义级别来区分。