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中运行上述程序代码,得到结果如下图:
验证假设一正确!即所有的派生类与基类共享相同的static静态成员变量。
5.继承的分类
1)单继承
一个子类只有一个直接父类称为单继承。单继承结构简单且易于理解,示意图如下:
2)多继承
一个子类有多个直接父类称为多继承,示意图如下:
6.虚继承
在多继承中,会出现如下一种继承方式——菱形继承:
我们通过如下一段代码观察一个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;
}
代码执行的内存截图如下:
首先从内存视图中我们可以看到:1.类中的成员变量存放次序是先父类,后本类;2.若父类不只一个,将按继承的顺序依次存放。
其次,很明显这样会使得一个Three的对象中存在两个Zero的对象,如果我们的本意不是这样,将造成数据冗余和空间浪费。为解决此问题,C++出现了虚拟继承——即将共同基类设置为虚基类,当出现了共同基类时,就只有一个基类对象,同名数据成员仅有一份拷贝,同一个函数名也只有一个映射,解决了二义性问题、节省了内存空间、避免了数据不一致的问题(参考http://blog.youkuaiyun.com/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函数后观察内存可以看到:
分析此时的内存布局,是如下情况:
再分别进入两个地址,得到0110e0a4所指向的空间如下图,发现前四个字节为0,后四个字节值为20,正好是t中one类对象与zero对象地址的差值:
得到0110e1a8所指向的空间如下,同理得到后四个字节值是two对象与zero对象地址的差值:
故而对于有两个基类的虚继承,内存布局如下图所示:
通过查阅其它博客(http://blog.chinaunix.net/uid-16723279-id-3568748.html)关于虚继承的讨论,得到在虚继承的情况下内存空间分布为:
1、B1的虚函数表(子类重定义的基类的虚函数、重定义的B1的虚函数、子类自己的虚函数)
2、继承自B1的数据成员
3、B2的虚函数表(子类重定义的基类的数据成员、重定义的B2的虚函数)
4、继承自B2的数据成员
5、子类自己的数据成员
6、基类的虚函数表(子类重定义的虚函数、基类的虚函数)
7、基类的数据成员
以上仅为博主初学认知,如有错误或不当之处,欢迎批评指正!