虚基类
       在说明其作用前先看一段代码

class A
{
public:
   int iValue;
};

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;};
};

void main()
{
   D d;
   cout<<d.iValue<<endl; //错误,不明确的访问
   cout<<d.A::iValue<<endl; //正确
   cout<<d.B::iValue<<endl; //正确
   cout<<d.C::iValue<<endl; //正确
}

从代码中可以看出类B C都继承了类A的iValue成员,因此类B C都有一个成员变量iValue ,而类D又继承了B C,这样类D就有一个重名的成员 iValue(一个是从类B中继承过来的,一个是从类C中继承过来的).在主函数中调用d.iValue 因为类D有一个重名的成员iValue编译器不知道调用 从谁继承过来的iValue所以就产生的二义性的问题.正确的做法应该是加上作用域限定符 d.B::iValue 表示调用从B类继承过来的iValue。不过 类D的实例中就有多个iValue的实例,就会占用内存空间。所以C++中就引用了虚基类的概念,来解决这个问题。

class A
{
public:
   int iValue;
};

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;};
};

void main()
{
   D d;
   cout<<d.iValue<<endl; //正确
}

在继承的类的前面加上virtual关键字表示被继承的类是一个虚基类,它的被继承成员在派生类中只保留一个实例。例如iValue这个成员,从类 D这个角度上来看,它是从类B与类C继承过来的,而类B C又是从类A继承过来的,但它们只保留一个副本。因此在主函数中调用d.iValue时就不 会产生错误。

虚函数
       还是先看代码

class A
{
public:
   void funPrint(){cout<<"funPrint of class A"<<endl;};
};

class B:public A
{
public:
   void funPrint(){cout<<"funPrint of class B"<<endl;};
};

void main()
{
   A *p; //定义基类的指针
   A a;
   B b;
   p=&a;
   p->funPrint();
   p=&b;
   p->funPrint();
}

大家以为这段代码的输出结果是什么?有的人可能会马上回答funPrint of class A 与 funPrint of class B 因为第一次输出是引用类A的实 例啊,第二次输出是引用类B的实例啊。那么我告诉你这样想就错啦,答案是funPrint of class A 与 funPrint of class A 至于为什么输出 这样的结果不在本文讨论的范围之内;你就记住,不管引用的实例是哪个类的当你调用的时候系统会调用左值那个对象所属类的方法。比如说 上面的代码类A B都有一个funPrint 函数,因为p是一个A类的指针,所以不管你将p指针指向类A或是类B,最终调用的函数都是类A的funPrint 函数。这就是静态联篇,编译器在编译的时候就已经确定好了。可是如果我想实现跟据实例的不同来动态决定调用哪个函数呢?这就须要用到 虚函数(也就是动态联篇)。

class A
{
public:
   virtual void funPrint(){cout<<"funPrint of class A"<<endl;};
};

class B:public A
{
public:
   virtual void funPrint(){cout<<"funPrint of class B"<<endl;};
};

void main()
{
   A *p; //定义基类的指针
   A a;
   B b;
   p=&a;
   p->funPrint();
   p=&b;
   p->funPrint();
}

多态性可分为两类:静态多态和动态多态。函数重载和运算符重载实现的多态属于静态多态,动态多态性是通过虚函数实现的。

每个含有虚函数的类有一张虚函数表(vtbl),表中每一项是一个虚函数的地址, 也就是说,虚函数表的每一项是一个虚函数的指针。

没有虚函数的C++类,是不会有虚函数表的。

两张图:


在基类的成员函数前加virtual关键字表示这个函数是一个虚函数,所谓虚函数就是在编译的时候不确定要调用哪个函数,而是动态决定将要调 用哪个函数,要实现虚函数必须派生类的函数名与基类相同,参数名参数类型等也要与基类相同。但派生类中的virtual关键字可以省略,也表 示这是一个虚函数。下面来解决一下代码,声明一个基类的指针(必须是基类,反之则不行)p,把p指向类A的实例a,调用funPrint函数,这 时系统会判断p所指向的实例的类型,如果是A类的实例就调用A类的funPrint函数,如果是B类的实例就调用B类的funPrint函数。

纯虚函数 
    与其叫纯虚函数还不如叫抽象类,它只是声明一个函数但不实现它,让派生类去实现它,其实这也很好理解。

; //纯虚函数是这样定义的
};

class Camion:public Vehicle
{
public:
   virtual void PrintTyre(){cout<<"Camion tyre four"<<endl;};
};

class Bike:public Vehicle
{
public:
   virtual void PrintTyre(){cout<<"Bike tyre two"<<endl;};
};

void main()
{
   Camion c;
   Bike b;
   b.PrintTyre();
   c.PrintTyre();
}

如上代码,定义了一个交通工具类(Vehicle),类中有一函数可打印出交通工具的轮胎个数,但交通工具很多轮胎个数自然也就不确定,所以 就把它定义为纯虚函数,也就是光定义函数名不去实现它,类Camion继承了Vehicle并实现了里面的代码,打印出有4个轮胎。Bike类也是一样。 有一点须要注意一下,纯虚函数不能实化化,但可以声明指针。

总结
    虚基类 
    1, 一个类可以在一个类族中既被用作虚基类,也被用作非虚基类。 
    2, 在派生类的对象中,同名的虚基类只产生一个虚基类子对象,而某个非虚基类产生各自的子对象。 
    3, 虚基类子对象是由最派生类的构造函数通过调用虚基类的构造函数进行初始化的。 
    4, 最派生类是指在继承结构中建立对象时所指定的类。 
    5, 派生类的构造函数的成员初始化列表中必须列出对虚基类构造函数的调用;如果未列出,则表示使用该虚基类的缺省构造函数。 
    6, 从虚基类直接或间接派生的派生类中的构造函数的成员初始化列表中都要列出对虚基类构造函数的调用。但只有用于建立对象的最派生 类的构造函数调用虚基类的构造函数,而该派生类的所有基类中列出的对虚基类的构造函数的调用在执行中被忽略,从而保证对虚基类子对象 只初始化一次。 
    7, 在一个成员初始化列表中同时出现对虚基类和非虚基类构造函数的调用时,虚基类的构造函数先于非虚基类的构造函数执行。

虚函数 
    1, 虚函数是非静态的、非内联的成员函数,而不能是友元函数,但虚函数可以在另一个类中被声明为友元函数。 
    2, 虚函数声明只能出现在类定义的函数原型声明中,而不能在成员函数的函数体实现的时候声明。 
    3, 一个虚函数无论被公有继承多少次,它仍然保持其虚函数的特性。 
    4, 若类中一个成员函数被说明为虚函数,则该成员函数在派生类中可能有不同的实现。当使用该成员函数操作指针或引用所标识的对象时 ,对该成员函数调用可采用动态联编。 
    5, 定义了虚函数后,程序中声明的指向基类的指针就可以指向其派生类。在执行过程中,该函数可以不断改变它所指向的对象,调用不同 版本的成员函数,而且这些动作都是在运行时动态实现的。虚函数充分体现了面向对象程序设计的动态多态性。 纯虚函数 版本的成员函数,而且这些动作都是在运行时动态实现的。虚函数充分体现了面向对象程序设计的动态多态性。

纯虚函数 
    1, 当在基类中不能为虚函数给出一个有意义的实现时,可以将其声明为纯虚函数,其实现留待派生类完成。 
    2, 纯虚函数的作用是为派生类提供一个一致的接口。 
    3, 纯虚函数不能实化化,但可以声明指针。

C++ - 虚基类、虚函数与纯虚函数的更多相关文章

  1. 不要在基类析构函数中调用纯虚函数,否则运行时会报错“pure virtual method called”

    如上. 这是因为:delete派生类对象时,先调用派生类的析构函数,然后再调用基类的析构函数:此时如果调用纯虚函数的话,派生类的对象已经被破坏了,所以会报错. http://www.cnblogs.c ...

  2. c++ 虚函数多态、纯虚函数、虚函数表指针、虚基类表指针详解

    静态多态.动态多态 静态多态:程序在编译阶段就可以确定调用哪个函数.这种情况叫做静态多态.比如重载,编译器根据传递给函数的参数和函数名决定具体要使用哪一个函数.动态多态:在运行期间才可以确定最终调用的 ...

  3. RTTI、虚函数和虚基类的实现方式、开销分析及使用指导(虚函数的开销很小,就2次操作而已)

    白杨 http://baiy.cn “在正确的场合使用恰当的特性” 对称职的C++程序员来说是一个基本标准.想要做到这点,首先要了解语言中每个特性的实现方式及其开销.本文主要讨论相对于传统 C 而言, ...

  4. 【c++】虚基类

    何要使用虚基类: 为何避免多层继承中出项多个公共基类所造成的歧义现象 虚基类用法 派生类继承基类时,加上一个virtual关键词则为虚拟基类继承. 在上图程序运行中,我们发现class bass的构造 ...

  5. C++学习20 虚基类详解

    多继承时很容易产生命名冲突,即使我们很小心地将所有类中的成员变量和成员函数都命名为不同的名字,命名冲突依然有可能发生,比如非常经典的菱形继承层次.如下图所示: 类A派生出类B和类C,类D继承自类B和类 ...

  6. C++ (P160—)多继承 二义性 虚基类 “向上转型”

    1 多继承中,必须给每个基类指定一种派生类型,如果缺省,相应的基类则取私有派生类型,而不是和前一个基类取相同的派生类型 2 一个类的保护成员只能被本类的成员函数或者它的派生类成员函数访问 3 由于c+ ...

  7. C++虚基类详解(转)

    我们知道,如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留该间接共同基类数据成员的多份同名成员.在引用这些同名的成员时,必须在派生类对象名后增加直接基类名,以避 ...

  8. 从汇编看c++中含有虚基类对象的析构

    c++中,当继承结构中含有虚基类时,在构造对象时编译器会通过将一个标志位置1(表示调用虚基类构造函数),或者置0(表示不调用虚基类构造函数)来防止重复构造虚基类子对象.如下图菱形结构所示: 当构造类B ...

  9. C++ 多继承与虚基类

    转载来自:CSDN insistGoGo  (http://blog.csdn.net/insistgogo) 多继承的定义:派生类的基类大于一个 语法: class  派生类名:继承方式1 基类名1 ...

随机推荐

  1. 《你必须知道的.NET》读书笔记二:小OO有大原则

    此篇已收录至<你必须知道的.Net>读书笔记目录贴,点击访问该目录可以获取更多内容. 一.单一职责原则 (1)核心思想:一个类最好只做一件事,只有一个引起它变化的原因 (2)常用模式:Fa ...

  2. 防止开发人员获取到敏感数据(SQL Server的数据加密简介)

    背景 有时候,我们还真的会碰到这样的需求:防止开发人员获取到敏感数据.也许你觉得很简单,把开发和运营分开不就可以了吗?是的,如果公司有专门的运营团队的话,但对于很多小公司来说,几个人的开发团队就兼顾了 ...

  3. mac命令

    mac下卸载nodesudo rm -rf /usr/local/{bin/{node,npm},lib/node_modules/npm,lib/node,share/man/*/node.*}xc ...

  4. Atitit. 类与对象的存储实现

    Atitit. 类与对象的存储实现 1. 类的结构和实现1 2. 类的方法属性都是hashtable存储的.2 3. Class的分类 常规类(T_CLASS), 抽象类(T_ABSTRACT T_C ...

  5. react2 react 遍历数组

    <body><!-- React 真实 DOM 将会插入到这里 --><div id="example"></div> <!- ...

  6. CCNA网络工程师学习进程(4)网络设备的基本配置和详细介绍

        网络设备(路由器.交换机和防火墙等)与计算机一样需要操作系统.网络设备采用专用的操作系统,统称为IOS(Internetwork Operating System,网络操作系统).     ( ...

  7. 蓝牙4.0 BLE基础之vdd检测new

    外部ADC通道,我们现在用的是A0脚,也就是P00通道 把它设置成输出的一个模式.在程序中设置,代码如下: #include <ioCC2540.h> #define HAL_ADC_RE ...

  8. Windows 系统下Git安装图解

    简单来说Git是一个免费的.开源的版本控制软件,从功能上讲,跟我们比较熟悉的Subversion(SVN)这类版本控制软件没什么两样.由于工作的需求,需要在WinXP下安装git配合团队完成相应的工作 ...

  9. javascript中Date对象的应用——简易日历的实现

    × 目录 [1]效果 [2]HTML [3]CSS[4]JS 前面的话 简易日历作为javascript中Date对象的常见应用,用途较广泛.本文将详细说明简易日历的实现思路 效果演示 HTML说明 ...

  10. Mina、Netty、Twisted一起学(六):session

    开发过Web应用的同学应该都会使用session.由于HTTP协议本身是无状态的,所以一个客户端多次访问这个web应用的多个页面,服务器无法判断多次访问的客户端是否是同一个客户端.有了session就 ...