171228—虚继承&虚基类、虚函数、纯虚函数&抽象类 这一家人

虚继承&虚基类

定义:

虚继承:在继承定义中包含了virtual关键字的继承关系,它描述了一种无关于公有、私有、保护继承的继承方式。
虚基类:在虚继承体系中的通过virtual继承而来的基类,即被虚继承的基类。
但是!
没有任何类天生就是虚基类。
需要注意的是:

class B: public virtual A 

其中A称之为B的虚基类,而不是说 A 就是个虚基类,因为 A 还可以作为不是虚继承体系中的基类。事实上,“A 就是个虚基类”这一类说法是不正确的。虚基类是建立在一种关系上的概念。而这种关系构成了虚继承体系

用意:

虚拟继承是多重继承中特有的概念。
这里写图片描述
类D继承自类B1、B2,而类B1、B2都继承自类A,因此出现如右图所示的局面。假设类A中有数据成员a,则D中将有至少两个a(不排除B1、B2中再次定义同名数据成员的可能)。
虚继承也叫共享继承
由一个基类将其成员数据实例共享从这个基类型直接或间接派生的其它类。
- 为了节省内存空间,
- 也为了消除在访问数据时不必要的麻烦(二义性
我们需要把类D对类B1、B2的继承说明为虚继承。通过关键字 VIRTUAL 来实现。

class B: public virtual A 
继承方式可以是:pulic、privateprotected。
也可以写成:
class B: virtual public A 
初始化:

虚基类的初始化与一般多继承的初始化在语法上是一样的,但构造函数的调用次序不同。
派生类构造函数的调用次序有四个原则:

  1. 虚基类的构造函数在非虚基类之前调用;
  2. 若同一层次中包含多个虚基类,这些虚基类的构造函数按它们说明的次序调用;
  3. 若虚基类由非虚基类派生而来,则仍先调用基类构造函数,再调用派生类的构造函数。
  4. 若一个多继承体系中有多个派生类来自同一虚基类,则只调用一次虚基类

依上图,假设有:

class A
{}
class B:virtual public A
{}
class C:virtual public A
{}
class D:public B,public C
{}
......
int main()
{
  D a;
}

类D继承自类B、C,而类B、C都虚继承自类A。
则在定义D类的对象a时,将依次调用:
A->B->C->D
(原则四)
如果不写成虚继承,则将:
A->B->A->C->D
在虚继承之后,A将直接“被继承”到D中,且A为B、C共用,这更能体现“共享性”
(图片来自百度)
这里写图片描述

特点:
  • 虚继承不改变既有基类内各个成员的原有特性,也不需要将被虚继承的基类具备任何先有条件。虚 继承只影响继承之后产生的新基类,这和“虚”族其他成员不同。
  • 虚继承实际上在一般的应用中很少用到,主要是因为在C++中,多重继承是不推荐的,也并不常用,而一旦离开了多重继承,虚拟继承就完全失去了存在的必要(“因为这样只会降低效率和占用更多的空间”?存疑)

虚函数

用途:

此前提到过,虚函数的存在是为了实现运行时的多态性。虚函数的作用,就是实现覆盖
所谓覆盖,是指派生类函数覆盖基类函数,只作用于派生类函数。
(当子类和父类出现同名函数,甚至原型完全一样的函数时,尽管可以像之前一样,通过名访问直接调用,但是这个“多态性”也不是可以追求来花里胡哨的,后面会给出例子。)

方法:

一个virtual。

void virtual func()
特性:

虚函数具有遗传性
当“祖宗类”的某个函数被定义为虚,则其派生类相同界面的同原型成员函数也默认具有虚特性。
因而可以在后续类定义之后省略“virtual”说明符。

注意事项:

如果想实现一个函数的动态联编,定义为虚函数,则必须保证函数名、返回类型、参数个数、顺序、类型,即整个函数头部完全一致。
也就是说:
如果声明了某个成员函数为虚函数,则在该类中不能出现和这个成员函数同名并且返回值、参数个数、类型都相同的非虚函数。在以该类为基类的派生类中,也不能出现和这个成员函数同名并且返回值、参数个数、类型都相同的非虚函数。
否则将出现:

  1. 如果仅返回值类型不同,则将识别为错误重载,“因为靠返回值类型不同的信息来进行函数匹配是模糊的”。
  2. 如果原型不同但同名,则将认为是普通重载,该函数将失去虚特性。(此时在派生类中,基类的同名函数被隐藏。调用同名函数时默认调派生类新增的)
定义虚函数的限制:
  • 非类的成员函数不能定义为虚函数,类的成员函数中静态成员函数构造函数也不能定义为虚函数,**但可以将析构函数定义为虚函数。实际上,优秀的程序员常常把基类的析构函数定义为虚函数。
    因为,将基类的析构函数定义为虚函数后
    当利用delete删除一个指向派生类定义的对象指针时,系统会调用相应的类的析构函数。
    而不将析构函数定义为虚函数时,只调用基类的析构函数

    最起码,当基类中有虚函数的时候,应当把它的析构函数也定义为虚析构函数,这样的话,它的派生类的析构函数就算不同名也同样遗传虚特性。使得在delete删除一个对象时可以正确调用析构函数。

  • 只需要在声明函数的类体中使用关键字“virtual”将函数声明为虚函数,而定义函数时不需要使用关键字“virtual”。

  • 当将基类中的某一成员函数声明为虚函数后,派生类中的同名函数(函数名相同、参数列表完全一致、返回值类型相关)自动成为虚函数。
  • 如果声明了某个成员函数为虚函数,则在该类中不能出现和这个成员函数同名并且返回值、参数个数、类型都相同的非虚函数。在以该类为基类的派生类中,也不能出现和这个成员函数同名并且返回值、参数个数、类型都相同的非虚函数。
前面挖的坑:
class A
{
public:
   virtual void fun () 
    { cout << "A"; }
   void xxx ( void )  
    { fun(); }
};

class B: public A
{

public:
   void fun () 
    { cout << "B" ; }

};
void main()
{
...
   b.xxx(); //虚函数时输出"B", 否则输出"A"
}

这里,基类的成员函数fun被说明为虚函数,成员函数xxx调用了fun,派生类添加了新的fun。
当调用派生类的对象b中的xxx函数时。显然xxx执行了新fun的内容而非基类fun。也实现了重载。但是没有使用书上一直在强调的需要一个指向派生类对象的基类指针。原因在于:
在成员函数内部再调其它成员函数或者成员变量,都默认加有this指针。
也就是说,这个基类的this指针是在的。当这个函数为虚时,继承给派生类之后,在派生类中,这个指针得以动态地选定我们想要的fun版本。

有关重载、覆盖、隐藏的问题,他日再论。。

纯虚函数&抽象类

作用:

纯虚函数的存在是在虚函数上的加强。
纯虚函数是一种特殊的虚函数,在许多情况下,为了让基类具有普遍性,而不影响其子类的排他性功能。在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。
含有纯虚函数的类成为抽象类

定义格式:

纯虚函数应该写在最基的基类里(车速自己把控),只是个声明,它的定义应该在子类中实现。

void virtual fun()(int ,int, double)=0;
//只是个声明而已
注意事项:

抽象类由于含有虚函数,即没有具体实现功能和意义的function。不能用于声明对象,只是作为基类为派生类服务。除非在派生类中完全实现基类中所有的的纯虚函数,否则,派生类还是个抽象类,依旧不能实例化对象。
抽象类的使用有如下限制:

  • 抽象类只能用作其他类的基类。
  • 抽象类不能建立对象。
  • 抽象类不能用作参数类型、函数返回类型、或显示类型转换(这些都是需要暂时建立一个实例化对象的)。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值