详~谈~继承

来源:互联网 发布:淘宝上最贵的东西 编辑:程序博客网 时间:2024/06/03 20:49

说到继承,我们会想到子承父业,其实c++中的继承和这个词差不多,如果有两个类,(记作:一个是父类,一个是子类,)那么子类就可以继承父类的所有成员变量,以及函数等。

继承的概念:是面向对象设计代码,可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展和增进功能。这样产生的新类成为派生类
继承的格式:class DeriveClassName:继承类型 BaseClassName

下面看一个关于两个类继承的程序

#include<iostream>using namespace std;//测试继承的定义class Base{public:    int _b;};class Derived:public Base{public:    int _d;};int main(){    Derived d;    d._b = 1;    d._d = 2;    cout << sizeof(d) << endl;    return 0;}

我们可以看到,两个类的继承关系,通过测验可以知道,在public的继承下,Derived完全继承了Base类的成员,所以,结果是8,也就是说,在本身的类Derived中有4个字节的成员,然后又从Base中继承了4个字节。

不过,如果其他的继承方式,或不会也是这种情况呢?

这里写图片描述

其实,在实际的运用中,public最常见,极少场景下才会出现protected/private。

在学习继承之前,我们所知道的protected和private有没有区别呢?答案是,好像都没咋见过protected的使用场景。

原因是,因为protected就是为了继承而生的,现在说一个重点,基类的private成员在派生类中是不能被访问的,如果基类的成员不想被类外直接访问,但需要在派生类中被访问,就定义protected。

//测试private和protected的区别class Base{public:    Base()    {        cout << "B()" << endl;    }    ~Base()    {        cout << "~B()" << endl;    }    void ShowBase()    {        cout << "_pri = " << _pri << endl;        cout << "_pro = " << _pro << endl;        cout << "_pub = " << _pub << endl;    }private:    int _pri;protected:    int _pro;public:    int _pub;};class Derived :public Base{public:    Derived()    {        cout << "D()" << endl;    }    ~Derived()    {        cout << "~D()" << endl;    }    void ShowDerived()    {        cout << _pro << endl;        cout << _pub << endl;        //cout << _pri << endl;        cout << "_d_pri = " << _d_pri << endl;        cout << "_d_pro = " << _d_pro << endl;        cout << "_d_pub = " << _d_pub << endl;    }private:    int _d_pri;protected:    int _d_pro;public:    int _d_pub;};

经过上面程序的验证,我发现,在Derived中访问public和protected是不会报错的,然而访问private编译不通过。因此验证了上面的重点内容,也就是protected是因继承才出现的。

那么如果写程序的时候把继承限定符给忘了,咋办?系统会默认成什么?

class会默认成private,不信可以试试,总共就三种继承方式,可以先在类外创建一个对象,去访问派生类D中的成员,然而并不能访问任何成员,因此可以排除public。至于是protected还是private,再写一个派生类C,继承方式写成public,如果能访问派生类D中的成员,那么说明是protected,然而并不能。经过这一系列的排除,就会发现,只剩下一个private。另外再说一下,结构体的默认继承方式是public。

同名隐藏

先从名字上看,同名,说明有两个东西,额,成员,名字相同,隐藏,说明有一个隐藏了,那么哪一个隐藏了呢?还是来测试一下吧:

class Base{public:    int _b;};class Derived :public Base{public:    int _b;};int main(){    Derived d;    d.Base::_b = 666;//先给作用于Base下的_b赋值    d.Derived::_b = 888;//再给作用于Derived下的_b赋值    cout << d._b << endl;//看结果是哪个    cout << d.Base::_b << endl;    cout << d.Derived::_b << endl;    system("pause");    return 0;}

结果是:
888
666
888
说明同名隐藏下,基类的成员隐藏,(只是隐藏起来,不代表没有继承),通过对象访问到的是派生类的成员。同样的方法,如果是成员函数,也是先访问派生类中的。

那么,如果我把Derived中的int _b,改成char _b呢?

结果是
x
666
x
还是先访问派生类中的。

作用域

继承体系中的作用域
1. 在继承体系中基类和派生类是两个不同作用域。
2. 子类和父类中有同名成员,子类成员将屏蔽父类对成员的直接访问。(在子类成员函数中,可以
使用 基类::基类成员 访问)–隐藏 –重定义
3. 注意在实际中在继承体系里面最好不要定义同名的成员。

我们知道,重载和同名隐藏都是名字相同,那么,隐藏的成员函数构成重载吗?

答案是否定的,因为作用于不同……

对象模型

对象模型就是对象中成员在内存空间中的布局,像一般的继承,派生类创建的对象,模型,额,看图吧。
这里写图片描述

现在来说一下基类和派生类的调用顺序吧,这个其实测试最容易,只要按照平时调试的步骤走着就可以,最后回发现:

先调用的是派生类的构造函数,然后是基类的构造函数,不过这个时候派生类的构造函数还没有执行完,当基类的构造函数执行完成之后,又回到派生类的构造函数的呢内部。

至于析构函数,其调用的顺序和成员变量在类中的声明顺序相反。

注意

基类没有缺省构造函数,派生类必须要在初始化列表中显式给出基类名和参数列表。
基类没有定义构造函数,则派生类也可以不用定义,全部使用缺省构造函数。
基类定义了带有形参表构造函数,派生类就一定定义构造函数。

友元关系可以继承吗?

友元函数是类的成员函数吗?显然不是,孩子才能继承遗产,朋友,呵呵。

多继承和菱形继承

显而易见,多继承就是,一个派生类类有两个以上的基类继承。相当于,一个孩子有一个亲爹,一个后爹。那么对象的模型(空间布局)按照继承的先后顺序划分。

class Base1{public:    int _b1;};class Base2{public:    int _b2;};class Derived :public Base1,public Base2{public:    int _d;};int main(){    Derived d;    d._b1 = 1;    d._b2 = 2;    d._d = 3;    return 0;}

要验证多继承的对象模型的空间布局,只需要将内存窗口打开,看一下他们的顺序即可。
这里写图片描述

如果把继承的顺序改成:class Derived :public Base2,public Base1
结果迥异:
这里写图片描述
现在可以发现,内存空间布局是和继承顺序有关的

菱形继承,形如其名
这里写图片描述

class Base{public:    int _b;};class Base1:public Base{public:    int _b1;};class Base2:public Base{public:    int _b2;};class Derived :public Base2,public Base1{public:    int _d;};int main(){    Derived d;    //d._b=0;    d._b1 = 1;    d._b2 = 2;    d._d = 3;    cout << sizeof(d) << endl;    return 0;}

我们从上面的程序可以看到,菱形继承存在一些问题。也就是二义性,当我们让对象访问_b的时候会出错,原因就是,二义性,Base1从Base中继承了一个_b,Base2也从Base中继承了一个_b,显然这两个_b都被Derived继承了,那么,我们直接访问_b的时候,系统就会报错“对_b的访问不明确”。

那么如何解决这个问题呢?

虚继承

虚继承解决了在菱形继承体系里面子类对象包含多份父类对象的数据冗余&浪费空间的问题。
虚继承体系看起来好复杂,在实际应用我们通常不会定义如此复杂的继承体系。一般不到万不得已都不要定义菱形结构的虚继承体系结构,因为使用虚继承解决数据冗余问题也带来了性能上的
损耗。

class Base{public:    int _b;};class Base1:virtual public Base{public:    int _b1;};class Base2:virtual public Base{public:    int _b2;};class Derived :public Base2,public Base1{public:    int _d;};int main(){    Derived d;    d._b=0;    d._b1 = 1;    d._b2 = 2;    d._d = 3;    cout << sizeof(d) << endl;    return 0;}

好吧,我就加了一个关键字virtual,加在两个重要的位置上,然后,二义性就解决了,神奇不?

那么到底是怎么解决的呢?

还是得看一下模型:
这里写图片描述

从图中我们可以看到,对象模型的空间布局是这样的,首先是Base2,然后是Base1,之后是Derived中本身带有的成员,最后是Base。从中我们可以得出虚继承和继承的空间布局正好是相反的;另外,我们还可以看到有地址的存在,这些地址的指向是派生类和基类的偏移量,怎么知道是偏移量呢?

首先是Base2,其中的偏移量是14,也就是十进制的20,那么我们从上到下数一下,哎,还真是!再拿第二个验证一下,0c,也就是12,正确。

从图中,我们可以看到Base中的成员,只有一个,那么二义性问题也就不存在了。

1 0