虚继承
class A { public: int a } class B:public virtual A { public: Int b; } class C:public virtual A { public: Int c; } class D:public B,public C { public: int d; }
B C 虚继承A,A是B C的虚基类。
三篇有用的文章:
http://blog.csdn.net/flashtao613/article/details/4070071
http://bbs.csdn.net/topics/390251988
http://blog.sina.com.cn/s/blog_93b45b0f01011pkz.html
虚继承的特点是,继承B,C的类不会从B,C直接继承A的成员,它只会直接从A中继承。所以不会造成D中有两份A成员的拷贝。基于这一点,D中必须显示调用A中的带参构造函数(如果有的话,且其基类部分维护D中继承自A的成员的指针)
对于A ca;
Sizeof(A)= 4,其成员为int a;
对于B cb;
Sizeof(B)= 12,其成员有:
Vb_ptr |
B中的指针 |
Int b |
B自己的成员 |
Int a |
继承自A的成员 |
假设B的首地址为p;
Char* p = (char*)&cb;
Int offset = *Vb_ptr;
Int* data = (int*)(p+offset);
那么data指向a;
对于C cc;同上
对D cd;
vb_ptr: 继承自B的指针 |
int b: 继承自B公有成员 |
vc_ptr:继承自C的指针 |
int c: 继承自C的共有成员 |
int d: D自己的公有成员 |
int a: 继承自A的公有成员 |
按照普通继承D的结构为:
B
|
||
C
|
||
D
|
对于这个结构的解释是,D由三部分部组成B,C,D(myself),但是并不存在单独的B,C的对象。
只是D可以当作B,C来用(来解析),此例中由于有两个a所以会有二义性。且D中不必显示调用A的构造函数。
虚基类,虚继承就是为了解决这个二义性问题,方法就是把D的结构改造一下:
B
|
||
C
|
||
D
|
||
A
|
这样直接从虚基类继承a,而绕过B,C-----只有直接实例化的类才会真正调用虚基类的构造函数,其他中间类对A构造函数的调用都将被抑制。
之所以设置指针这个参数,只是为了让D可以当B,C使用。
也就是:
D d;
B *pb = &d
Pb->a;
实际是把图中B那块首地址给pb,调用a,其实是通过B的指针间接实现。这样我们既实现了D中只有一份a的拷贝,同时又可以将D作为B,C来使用,满足里氏替换原则(C++中子类都应当可以作为父类使用,而没有任何差异被感知)。
虚基类中将派生类的指针指向基类是不支持的,同时也是不提倡的做法,即使是该基类指针指向的实际是一个派生类对象也不可,不同编译器实现的方式不同,尽管有时先将基类指针转换成void*然后再赋值给派生类可以骗过编译器,但是有时也会出现运行时错误。还有一种方式,就是虚基类中有虚函数,那么就可以dynamic_cast<>在运行期强制转换。不过同样危险,总之要特别小心。目前认识非常初步,待完善。<C++ 对象模型>讲解较为详尽。
cin,cin.get(),cin.getline()那点小事:
(1) cin:读取以空白符结束。
(2) cin.get():读取一个字符,包括空白。
(3) cin.getline():读取一行,包括换行符(及其它空白)。
所以cin读取完事之后,缓冲区stdin还存留着一个换行符‘\n‘,而其他两个就没有。
如:
char ch;
cin>>ch;
cout<<ch<<endl;
ch = cin.get();
if(ch ==’\n‘)cout<<“ch is \\n.“<<endl;
输入:1
输出:
1
ch is \n.
这个输出有点奇怪了,我只输入了1啊,为什么会有后续输出呢,这就是我们要注意的了,其实我们不只是输入了1,那个cin的结束符enter键同样输入到了stdin缓冲区中。它不会被cin读取,而cin.get()则会读取它。这也是我们需要小心的地方,解决办法是,在使用两种同类型读取方式时,先用fflush(stdin)/cin.sync()来清除缓冲区。使得缓冲区为空,这样cin.get()才不会自动执行,才会等待用户输入。
然而在g++下这种方法确不奏效,这是因为fflush/sync函数不是标准库所有的。可移植性不强。所以我们可以取而代之为cin.ignore();
文件的几种状态:cin.rdstate()
(1)goodbit:无错误 如if(cin.rdstate() ==ios_base::goodbit)
(2)badbit:有致命错误,无法挽回。
(3)eofbit:文件末尾。
(4)failbit:输入错误,可以挽回。
几个函数:
1. cin.clear():恢复goodbit.
2. cin.sync():清空缓冲区,but,linux g++下无效。VC特有的
3. fflush():清空缓冲区,linux g++下无效。VC等支持
4. cin.ignore():清空缓冲区,g++支持。
cin.ignore(std::numeric_limits<std::streamsize>::max(),'\n');
用于准确清空缓冲区,std::numeric_limits<std::streamsize>::max()计算缓冲区大小。(需要头文件:#include <limits>)
网上摘抄一段:
其实用fflush是不对的,因为在标准C中只定义了输出流、更新流的刷新,而输入流的刷新是未定义的。当然,在vc下面的fflush(stdin)是微软自己扩展的,而GCC下面是没有的。
c语言:
while( (c=getchar())!='\n' && c != EOF)
;
c++:
cin.clear(); //这里如果用cin.clear(istream::failbit); 是不行的
cin.ignore(numeric_limits<streamsize>::max(),'\n');
组合(聚合)与继承关系:
公有继承:is-a关系,满足里氏替换原则,B继承A ,那么我们说B 是一个A,没问题,B能当作A使用。
派生类的三种继承方式小结:
公有继承public)、 私有继承(private)和保护继承(protected)是常用的三种继承方式。
1.对于公有继承方式:
·基类成员对其对象的可见性与一般类及其对象的可见性相同,公有成员可见,其他成员不可见。这里保护成员与私有成员相同。
·基类成员对派生类的可见性对派生类来说,基类的公有成员和保护成员可见:基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态;基类的私有成员不可见:基类的私有成员仍然是私有的,派生类不可访问基类中的私有成员。
·基类成员对派生类对象的可见性对派生类对象来说,基类的公有成员是可见的,其他成员是不可见。
所以,在公有继承时,派生类的对象可以访问基类中的公有成员;派生类的成员函数可以访问基类中的公有成员和保护成员。
2.对于私有继承方式:
·基类成员对其对象的可见性与一般类及其对象的可见性相同,公有成员可见,其他成员不可见。
·基类成员对派生类的可见性对派生类来说,基类的公有成员和保护成员是可见的:基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问;基类的私有成员是不可见的:派生类不可访问基类中的私有成员。
·基类成员对派生类对象的可见性对派生类对象来说,基类的所有成员都是不可见的。
所以,在私有继承时,基类的成员只能由直接派生类访问,而无法再往下继承。
3.对于保护继承方式:
这种继承方式与私有继承方式的情况相同。两者的区别仅在于对派生类的成员而言,
·基类成员对其对象的可见性与一般类及其对象的可见性相同,公有成员可见,其他成员不可见。
·基类成员对派生类的可见性对派生类来说,基类的公有成员和保护成员是可见的:基类的公有成员和保护成员都作为派生类的保护成员,并且不能被这个派生类的子类所访问;基类的私有成员是不可见的:派生类不可访问基类中的私有成员。
·基类成员对派生类对象的可见性对派生类对象来说,基类的所有成员都是不可见的。
所以,在保护继承时,基类的成员也只能由直接派生类访问,而无法再往下继承。
本章小结
C++支持多重继承,从而大大增强了面向对象程序设计的能力。
多重继承是一个类从多个基类派生而来的能力。派生类实际上获取了所有基类的特性。
当一个类是两个或多个基类的派生类时,必须在派生类名和冒号之后,列出所有基类的类名,基类间用逗号隔开。
派生类的构造函数必须激活所有基类的构造函数,并把相应的参数传递给它们。
派生类可以是另一个类的基类,这样,相当于形成了一个继承链,当派生类的构造函数被激活时,它的所有基类的构造函数也都会被激活。
在面向对象的程序设计中,继承和多重继承一般指公共继承。在无继承的类中,protected和private控制符是没有差别的。在继承中,基类的private对所有的外界都屏蔽(包括自己的派生类), 基类的protected控制符对应用程序是屏蔽的,但对其派生类是可访问的。
保护继承和私有继承只是在技术上讨论时有其一席之地。