C++中的继承特性(2)

4.static成员与继承
在类中,无论创建了多少个对象,被static修饰的成员变量永远只有一个,那么派生类是否也继承了这个静态成员变量呢?
假设一:派生类继承了这个变量,且与基类共享同一个静态成员;
假设二:派生类拷贝了这个变量,不与基类共享,是派生类所有对象共有的static静态成员。
这一点其实很好证明。由上一篇博文我们基本理解了派生类的构造函数和析构函数,那么这里就利用构造函数和析构函数来验证哪一种假设正确。

我们来看下面一段代码:

class A{
public:
    A(){
        ++_a;
        cout << "A: _a = " << _a << endl;
    }
    ~A(){
        --_a;
        cout << "~A: _a = " << _a << endl;
    }
    static int _a;
};
int A::_a = 0;
class B :public A{
public:
    B(){
        cout << "B: _a = " << _a << endl;
    }
    ~B(){
        cout << "~B: _a = " << _a << endl;
    }
};

void FunTest(){
    A a;
    B b;

}
int main(){
    FunTest();

    system("pause");
    return 0;
}

阅读上段代码,分析可得:
<1>在假设一下,创建对象a时使静态成员变量_ a自增,创建对象b时将在初始化列表中调用A(),使_ a自增,此时_ a为2;销毁对象b时先调用~B(),再调用~A()使_ a自减,此时_ a为1,销毁对象a时再次使_ a自减,最终为零,故此时应在屏幕上输出如下结果:

A: _a = 1
A: _a = 2
B: _a = 2
~B: _a = 1
~A: _a = 1
~A: _a = 0

<2>在假设二下,创建对象a时使A::_ a自增至1,创建对象b时调用A()使B类自己的静态成员_ a自增至1;销毁对象b时先调用~B(),再调用~A()使B类自己的静态成员_ a自减至0,销毁对象a时使A::_ a自减至0,此时应在屏幕上输出如下结果:

A: _a = 1
A: _a = 1
B: _a = 1
~B(): _a = 1
~A: _a = 1
~A: _a = 0

而后在VS中运行上述程序代码,得到结果如下图:
C++中的继承特性(2)_第1张图片
验证假设一正确!即所有的派生类与基类共享相同的static静态成员变量。


5.继承的分类
1)单继承
一个子类只有一个直接父类称为单继承。单继承结构简单且易于理解,示意图如下:
单继承
2)多继承
一个子类有多个直接父类称为多继承,示意图如下:
C++中的继承特性(2)_第2张图片


6.虚继承
在多继承中,会出现如下一种继承方式——菱形继承:
C++中的继承特性(2)_第3张图片
我们通过如下一段代码观察一个Three对象中各个变量的存放:

class Zero{
public:
    int zero;
};
class One :public Zero{
public:
    int one;
};
class Two :public Zero{
public:
    int two;
};
class Three :public One, public Two{
public:
    int three;
};
void FunTest(){
    Three t;
    t.One::zero = 0;
    t.one = 1;
    t.Two::zero = 2;
    t.two = 3;
    t.three = 4;
}

int main(){
    FunTest();

    system("pause");
    return 0;
}

代码执行的内存截图如下:
C++中的继承特性(2)_第4张图片
首先从内存视图中我们可以看到:1.类中的成员变量存放次序是先父类,后本类;2.若父类不只一个,将按继承的顺序依次存放。
其次,很明显这样会使得一个Three的对象中存在两个Zero的对象,如果我们的本意不是这样,将造成数据冗余和空间浪费。为解决此问题,C++出现了虚拟继承——即将共同基类设置为虚基类,当出现了共同基类时,就只有一个基类对象,同名数据成员仅有一份拷贝,同一个函数名也只有一个映射,解决了二义性问题、节省了内存空间、避免了数据不一致的问题(参考http://blog.csdn.net/crystal_avast/article/details/7678704)。
虚继承格式为: class <派生类类名> :virtual 继承方式 <基类名>
那么同样,我们来研究一下虚继承情况下上述Three类对象的内存空间,修改后的代码如下:

class Zero{
public:
    int zero;
};
class One :virtual public Zero{
public:
    int one;
};
class Two :virtual public Zero{
public:
    int two;
};
class Three :public One, public Two{
public:
    int three;
};
void FunTest(){
    Three t;
    t.One::zero = 0;
    t.one = 1;
    t.Two::zero = 2;
    t.two = 3;

    t.zero = 0;
    t.three = 4;
}

int main(){
    cout << sizeof(Three) << endl;
    FunTest();

    system("pause");
    return 0;
}

调试上述代码,首先得到Three这个类的大小为24Byte,在执行FunTest函数后观察内存可以看到:
C++中的继承特性(2)_第5张图片
分析此时的内存布局,是如下情况:
C++中的继承特性(2)_第6张图片
再分别进入两个地址,得到0110e0a4所指向的空间如下图,发现前四个字节为0,后四个字节值为20,正好是t中one类对象与zero对象地址的差值:
内存2
得到0110e1a8所指向的空间如下,同理得到后四个字节值是two对象与zero对象地址的差值:
内存3
故而对于有两个基类的虚继承,内存布局如下图所示:
C++中的继承特性(2)_第7张图片
通过查阅其它博客(http://blog.chinaunix.net/uid-16723279-id-3568748.html)关于虚继承的讨论,得到在虚继承的情况下内存空间分布为:
1、B1的虚函数表(子类重定义的基类的虚函数、重定义的B1的虚函数、子类自己的虚函数)
2、继承自B1的数据成员
3、B2的虚函数表(子类重定义的基类的数据成员、重定义的B2的虚函数)
4、继承自B2的数据成员
5、子类自己的数据成员
6、基类的虚函数表(子类重定义的虚函数、基类的虚函数)
7、基类的数据成员

以上仅为博主初学认知,如有错误或不当之处,欢迎批评指正!

你可能感兴趣的:(编程心得,C/C++)