1.虚基类
当在多条继承路径上有一个公共的基类,在这些路径中的某几条汇合处,这个公共的基类就会产生多个实例(或多个副本),若只想保存这个基类的一个实例,可以将这个公共基类说明为虚基类
在继承中产生歧义的原因有可能是继承类继承了基类多次,从而产生了多个拷贝,即不止一次的通过多个路径继承类在内存中创建了基类成员的多份拷贝。虚基类的基本原则是在内存中只有基类成员的一份拷贝。这样,通过把基类继承声明为虚拟的,就只能继承基类的一份拷贝,从而消除歧义。用virtual限定符把基类继承说明为虚拟的。
看一下代码来解释一下:
#include <iostream>
using namespace std;
class A
{
public:
int data;
void f(){cout << " hehe ";}
};
class B:public A
{
public:
void bPrintf(){cout<<"This is class B"<<endl;};
};
class C:public A
{
public:
void cPrintf(){cout<<"This is class C"<<endl;};
};
class D:public B,public C
{
public:
void dPrintf(){cout<<"This is class D"<<endl;};
};
int main()
{
D d;
cout<<d.data<<endl; //错误,不明确的访问
cout<<d.A::data<<endl; //正确
cout<<d.B::data<<endl; //正确
cout<<d.C::data<<endl; //正确
return 0;
}
看一下错误的信息:
错误信息包括,请求 数据成员data 是 歧义的, 就是我们下面分析的情况
首先画一下继承关系,
从代码中可以看出类B C都继承了类A的data 成员,因此类B C都有一个成员变量data ,而类D又继承了B C,这样类D就会从 B和C 中个继承 一个 data成员,这样就会出现就有一个重名的成员 data(一个是从类B中继承过来的,一个是从类C中继承过来的).在主函数中调用d.data 因为类D有一个重名的成员data编译器不知道调用 从谁继承过来的data所以就产生的二义性的问题.正确的做法应该是加上作用域限定符 d.B::iValue 表示调用从B类继承过来的iValue。不过 类D的实例中就有多个iValue的实例,就会占用内存空间。所以C++中就引用了虚基类的概念,来解决这个问题。
类通过虚继承来指定它希望共享虚基类的状态,在虚继承下,对给定的虚基类,无论该类在派生类层次中作为虚基类出现多少次都只继承一个共享的基类子对象。共享的基类只对象叫做虚基类。
代码:
#include <iostream>
using namespace std;
class A
{
public:
int data;
void f(){cout << " hehe " << endl ;}
};
class B:virtual public A
{
public:
void bPrintf(){cout<<"This is class B"<<endl;};
};
class C:virtual public A
{
public:
void cPrintf(){cout<<"This is class C"<<endl;};
};
class D:public B,public C
{
public:
void dPrintf(){cout<< "This is class D"<<endl;};
};
int main()
{
D d;
d.f(); //错误,不明确的访问
d.A::f(); //正确
d.B::f(); //正确
d.C::f(); //正确*/
return 0;
}
2.虚函数和纯虚函数
定义一个函数为虚函数,不代表函数为不被实现的函数。定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。
定义一个函数为纯虚函数,才代表函数没有被实现。定义他是为了实现一个接口,起到一个规范的作用,规范继承这个。类的程序员必须实现这个函数。
纯虚函数很好理解,我们如果首先定义了一个交通工具的基类, 派生类为 自行车 汽车 之类的,需要有一个 关于打印出来交通工具轮子的函数,因为单存的交通工具是打印不出来轮子个数的,这个时候我们在 交通工具类中定义一个 函数,声明为 纯虚函数,由 派生类实现这个函数。
包含纯虚函数的类叫做抽象类,抽象类是不能实例话对象的,就像交通工具类。