继承和派生
概念
继承:在定义一个新类B时,如果类B与已有的类A 相似(拥有类A的全部特性),那么就可以把类A当作基类,类B当作类A的一个派生类(也可叫做子类)。
对于派生类需要注意以下几点:
派生类是对基类进行修改和扩充得到的,在派生类中,可以增加新的成员变量和成员函数;
派生类一经定义,可以独立使用,不依附于基类;
派生类拥有基类的全部成员变量的成员函数,但是派生类的成员函数不能访问基类的private成员
声明派生类的写法
class 派生类名: public 基类名
{
...
}
派生类对象的内存空间
派生类的内存体积就等于基类的对象的体积再加上派生类自己新增的成员的体积。在派生类的对象中,包含着基类的对象,并且基类对象存储位置在派生类对象新增的成员变量之前。
让我们通过一段代码来验证一下
#include<iostream>
using namespace std;
class CBase
{
int v1,v2;
};
class CDerived:public CBase
{
int v3;
};
int main()
{
cout<<"CBase:"<<sizeof(CBase)<<" "<<" CDerived:"<<sizeof(CDerived)<<endl;
return 0;
}
运行结果
CBase:8 CDerived:12
基类与派生类对象的成员变量存储顺序如下图:
派生类的构造函数
既然是类,就一定会有构造函数,接下来介绍一些派生类构造函数方面的知识
1)派生类对象包含基类对象;
2)执行派生类构造函数之前,会先执行基类的构造函数;
3)派生类交代基类初始化,具体形式为:
构造函数名(形参表):基类名(基类构造函数实参表)
{
}
如果有其他类的对象做派生类的成员,那么该类成员对象的构造函数调用顺序在基类与派生类构造函数调用之之间,析构函数调用顺序相反,即
1.调用基类构造函数初始化基类成员变量
2.调用成员对象类的构造函数初始化成员对象
3.调用派生类构造函数初始化派生类的成员
析构函数调用顺序为:
派生类->成员对象类->基类
让我们通过一段代码来实际验证一下
#include<iostream>
using namespace std;
class A{
public:
int m;
A(int i):m(i){cout<<"A constructed"<<endl; }
~A(){
cout<<"A destructed"<<endl;
}
};
class Base{
public:
int n;
Base(int i):n(i){
cout<<"Base"<<n<<"constructed"<<endl;
}
~Base(){
cout<<"Base"<<n<<"destructed"<<endl;
}
};
class Derived:public Base{
public:
A a;
Derived(int i,int j):Base(i),a(j){
cout<<"Derived constructed"<<endl;
}
~Derived(){
cout<<"Derived destructed"<<endl;
}
};
int main(){
Derived obj(3,4);
return 0;
}
运行结果是
Base3constructed
A constructed
Derived constructed
Derived destructed
A destructed
Base3destructed
基类派生类同名成员和protected访问范围说明符
在派生类中是允许有和基类同名的成员变量或成员函数的,具体如何使用我们根据一段代码来详解
class base{//基类
int j;
public:
int i;
void func();
};
class derived:public base{//派生类
public:
int i;//与基类同名的成员变量i
void access();
void func();//与基类同名的成员函数
};
derived::access() {
j=5;//error,因为j是基类的私有成员,派生类无法访问
i=5;//派生类的i
base::i =5;//基类的成员变量i
func();//调用派生类的func函数
base::func() ;//调用基类的函数
}
int main()
{
derived obj;
obj.i =1;//改变派生类的i
obj.base::i=1;//改变基类的i
}
在定义obj这个对象的时候,他的成员变量是按下图这样存储的
相信聪明的你看到这应该已经明白了编译器对待基类派生类同名成员时候的处理方式。
下面再说一下访问范围说明符
基类的private成员可被以下函数访问
基类的成员函数
基类的友元函数
public成员可以被一下函数访问
基类的成员函数
基类的友元函数
派生类的成员函数
派生类的友元函数
其他函数
protected成员
基类的成员函数
基类的友元函数
派生类的成员函数可以访问当前对象的基类的保护成员
复合关系和继承关系
任何两个类之间只有三种关系:
1.复合关系
2.继承关系
3.没关系
接下来介绍一下概念
复合关系:即“有”关系。如类A 中有一个变量成员是类B 的对象,类A和类B就是复合关系
逻辑上要求:B对象是A对象的固有属性或组成部分。
继承关系:即“是”关系。基类C,类D是基类C的派生类
逻辑上要求:一个D对象也是一个C对象。
大家应该可以看出,继承关系就是上面我们提到的基类与派生类的关系,这里我们不再赘述,接下来讲一下复合关系的使用例子。
*如果要写一个小区养狗管理程序,需要写一个“业主”类,还需要写一个“狗”类。而狗是有 “主人” 的,主人当然是业主(假定狗只有一个主人,但一个业主可以有最多10条狗)
这时候我们需要通过狗可以找到他的主人,通过主人还要能找到狗,那么就需要狗中有人,人中有狗的类间的关系了,也就是复合关系。下面根据构想初步写上一段代码:
class CDog;
class CMaster
{
CDog dogs[10];
};
class CDog
{
CMaster m;
};
那么上面一段代码是否满足我们的需要了呢,答案当然是不满足,为什么呢,因为这两个类循环定义了!!!就等于进入了无底洞,永远没有尽头,编译时就会报错。
class CDog;
class CMaster {
CDog * dogs[10];
};
class CDog {
CMaster m;
};
那么上面这个写法呢,是可行的但是不好,因为每条狗里面都有一个主人,如果一个人养了多条狗,修改这个主人的信息,他的每条狗内主人的信息都需要修改,太麻烦。
所以究竟应该怎么写才是好的呢,我们可以通过两个指针使人狗互相连接,这样就解决了改一个引发多个数据更改的问题,代码如下
class CMaster; //CMaster必须提前声明,不能先写CMaster类后写Cdog类
class CDog {
CMaster * pm;
};
class CMaster {
CDog * dogs[10];
}
这样就形成了人狗交融,相互连接的友好局面!
public继承的赋值兼容规则
对于继承的赋值我们需要注意以下几点:
class Base{};
class Derived:public Base{};
Base b;
Derived d;
以以上代码为例
1)派生类的对象可以赋值给基类对象 b=d;
2)派生类对象可以初始化基类的引用 Base &ri=d;
3)派生类对象的地址可以赋值给基类的指针 Base *a=&d;
在继承关系中,基类的一个子类(派生类)也可以继续进行派生。
如下图,类A派生类B,类B派生类C,类C派生类D; 类A就是类B的直接基类,类A是类C、类D的间接基类......。
但是在声明一个派生类时,只需要列出他的直接基类,派生类就会沿着类的层次自动向上继承他的间接基类。
现在,我们再再总结一下派生类的成员包括:
1)派生类自己定义的成员
2)直接基类中的所有成员
3)所有间接基类的全部成员
让我们再通过一段代码来了解一下继承间的构造函数和析构函数的执行顺序
#include<iostream>
using namespace std;
class Base{
public:
int n;
Base(int i):n(i){
cout<<"Base"<<n<<"constructed"<<endl;
}
~Base(){
cout<<"Base"<<n<<"destructed"<<endl;
}
};
class Derived:public Base
{
public:
Derived(int i):Base(i){
cout<<"Derived constructed"<<endl;
}
~Derived(){
cout<<"Derived destructed"<<endl;
}
};
class MoreDerived:public Derived{
public:
MoreDerived():Derived(4){
cout<<"More Derived constructed"<<endl;
}
~MoreDerived(){
cout<<"More Derived destructed"<<endl;
}
};
int main()
{
MoreDerived obj;
}
运行结果
Base4constructed
Derived constructed
More Derived constructed
More Derived destructed
Derived destructed
Base4destructed
通过以上代码可以得出结论
在执行派生类构造函数之前,总是先执行他的直接基类的构造函数
在执行派生类析构函数之后,才开始执行他的直接基类的析构函数