Virtual是C++ OO机制中很重要的一个关键字。只要是学过C++的人都知道在类Base中加了Virtual关键字的函数就是虚拟函数(例如下面例子中的函数print),于是在Base的派生类Derived中就可以通过重写虚拟函数来实现对基类虚拟函数的覆盖。当基类Base的指针point指向派生类Derived的对象时,对point的print函数的调用实际上是调用了Derived的print函数而不是Base的print函数。这是面向对象中的多态性的体现。(关于虚拟机制是如何实现的,参见Inside the C++ Object Model ,Addison Wesley 1996)

class Base
{
public:Base(){}
public:virtual void print(){cout<<"Base";}
};class Derived:public Base
{
public:Derived(){}
public:void print(){cout<<"Derived";}
};int main()
{Base *point=new Derived();point->print();
} 

//---------------------------------------------------------

Output:
Derived
//---------------------------------------------------------
这也许会使人联想到函数的重载,但稍加对比就会发现两者是完全不同的:
(1)      重载的几个函数必须在同一个类中;
覆盖的函数必须在有继承关系的不同的类中
(2)      覆盖的几个函数必须函数名、参数、返回值都相同;
重载的函数必须函数名相同,参数不同。参数不同的目的就是为了在函数调用的时候编译器能够通过参数来判断程序是在调用的哪个函数。这也就很自然地解释了为什么函数不能通过返回值不同来重载,因为程序在调用函数时很有可能不关心返回值,编译器就无法从代码中看出程序在调用的是哪个函数了。
(3)      覆盖的函数前必须加关键字Virtual;
重载和Virtual没有任何瓜葛,加不加都不影响重载的运作。
关于C++的隐藏规则:
我曾经听说过C++的隐藏规则:
( 1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual
关键字,基类的函数将被隐藏(注意别与重载混淆)。
( 2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual
关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。
----------引用自《高质量C++/C 编程指南》林锐  2001
这里,林锐博士好像犯了个错误。C++并没有隐藏规则,林锐博士所总结的隐藏规则是他错误地理解C++多态性所致。下面请看林锐博士给出的隐藏规则的例证:
#include <iostream.h>
class Base
{
public:
virtual void f(float x){ cout << "Base::f(float) " << x << endl; }
void g(float x){ cout << "Base::g(float) " << x << endl; }
void h(float x){ cout << "Base::h(float) " << x << endl; }
};class Derived : public Base
{
public:
virtual void f(float x){ cout << "Derived::f(float) " << x << endl; }
void g(int x){ cout << "Derived::g(int) " << x << endl; }
void h(float x){ cout << "Derived::h(float) " << x << endl; }
};void main(void)
{
Derived d;
Base *pb = &d;
Derived *pd = &d;
// Good : behavior depends solely on type of the object
pb->f(3.14f); // Derived::f(float) 3.14
pd->f(3.14f); // Derived::f(float) 3.14
// Bad : behavior depends on type of the pointer
pb->g(3.14f); // Base::g(float) 3.14
pd->g(3.14f); // Derived::g(int) 3 (surprise!)
// Bad : behavior depends on type of the pointer
pb->h(3.14f); // Base::h(float) 3.14 (surprise!)
pd->h(3.14f); // Derived::h(float) 3.14
} 
林锐博士认为bp 和 dp 指向同一地址,按理说运行结果应该是相同的,而事实上运行结果不同,所以他把原因归结为C++的隐藏规则,其实这一观点是错的。决定bp和dp调用函数运行结果的不是他们指向的地址,而是他们的指针类型。“只有在通过基类指针或引用间接指向派生类子类型时多态性才会起作用”(C++ Primer 3 rd Edition)。pb是基类指针,pd是派生类指针,pd的所有函数调用都只是调用自己的函数,和多态性无关,所以pd的所有函数调用的结果都输出Derived::是完全正常的; pb的函数调用如果有virtual则根据多态性调用派生类的,如果没有virtual则是正常的静态函数调用,还是调用基类的,所以有virtual的f函数调用输出Derived::,其它两个没有virtual则还是输出Base::很正常啊,nothing surprise!
所以并没有所谓的隐藏规则,虽然《高质量C++/C 编程指南》是本很不错的书,可大家不要迷信哦。记住“只有在通过基类指针或引用间接指向派生类子类型时多态性才会起作用”。
纯虚函数:
C++语言为我们提供了一种语法结构,通过它可以指明,一个虚拟函数只是提供了一个可被子类型改写的接口。但是,它本身并不能通过虚拟机制被调用。这就是纯虚拟函数(pure
virtual function)。 纯虚拟函数的声明如下所示:

class Query {
public:
// 声明纯虚拟函数
virtual ostream& print( ostream&=cout ) const = 0;
// ...
};
这里函数声明后面紧跟赋值0。
包含(或继承)一个或多个纯虚拟函数的类被编译器识别为抽象基类。试图创建一个抽象基类的独立类对象会导致编译时刻错误。(类似地通过虚拟机制调用纯虚函数也是错误的)

// Query 声明了纯虚拟函数, 我们不能创建独立的 Query 类对象
// 正确: NameQuery 是 Query 的派生类
Query *pq = new NameQuery( "Nostromo" );
// 错误: new 表达式分配 Query 对象
Query *pq2 = new Query();

虚析构:
如果一个类用作基类,我们通常需要virtual来修饰它的析构函数,这点很重要。如果基类的析构函数不是虚析构,当我们用delete来释放基类指针(它其实指向的是派生类的对象实例)占用的内存的时候,只有基类的析构函数被调用,而派生类的析构函数不会被调用,这就可能引起内存泄露。如果基类的析构函数是虚析构,那么在delete基类指针时,继承树上的析构函数会被自低向上依次调用,即最底层派生类的析构函数会被首先调用,然后一层一层向上直到该指针声明的类型。

虚继承:
如果只知道virtual加在函数前,那对virtual只了解了一半,virtual还有一个重要用法是virtual public,就是虚拟继承。虚拟继承在C++ Primer中有详细的描述,下面稍作修改的阐释一下:
在缺省情况下C++中的继承是“按值组合”的一种特殊情况。当我们写
class Bear : public ZooAnimal { ... };
每个Bear 类对象都含有其ZooAnimal 基类子对象的所有非静态数据成员以及在Bear中声明的非静态数据成员。类似地当派生类自己也作为一个基类对象时如:
class PolarBear : public Bear { ... };
则PolarBear 类对象含有在PolarBear 中声明的所有非静态数据成员以及其Bear 子对象的所有非静态数据成员和ZooAnimal 子对象的所有非静态数据成员。在单继承下这种由继承支持的特殊形式的按值组合提供了最有效的最紧凑的对象表示。在多继承下当一个基类在派生层次中出现多次时就会有问题最主要的实际例子是iostream 类层次结构。ostream 和istream 类都从抽象ios 基类派生而来,而iostream 类又是从ostream 和istream 派生
class iostream :public istream, public ostream { ... };
缺省情况下,每个iostream 类对象含有两个ios 子对象:在istream 子对象中的实例以及在ostream 子对象中的实例。这为什么不好?从效率上而言,iostream只需要一个实例,但我们存储了ios 子对象的两个复本,浪费了存储区。此外,在这一过程中,ios的构造函数被调用了两次(每个子对象一次)。更严重的问题是由于两个实例引起的二义性。例如,任何未限定修饰地访问ios 的成员都将导致编译时刻错误:到底访问哪个实例?如果ostream 和istream 对其ios 子对象的初始化稍稍不同,会怎样呢?怎样通过iostream 类保证这一对ios 值的一致性?在缺省的按值组合机制下,真的没有好办法可以保证这一点。
C++语言的解决方案是,提供另一种可替代按“引用组合”的继承机制--虚拟继承(virtual inheritance)。在虚拟继承下只有一个共享的基类子对象被继承而无论该基类在派生层次中出现多少次。共享的基类子对象被称为虚拟基类。
通过用关键字virtual 修正,一个基类的声明可以将它指定为被虚拟派生。例如,下列声明使得ZooAnimal 成为Bear 和Raccoon 的虚拟基类:
// 这里关键字 public 和 virtual的顺序不重要
class Bear : public virtual ZooAnimal { ... };
class Raccoon : virtual public ZooAnimal { ... };
虚拟派生不是基类本身的一个显式特性,而是它与派生类的关系。如前面所说明的,虚拟继承提供了“按引用组合”。也就是说,对于子对象及其非静态成员的访问是间接进行的。这使得在多继承情况下,把多个虚拟基类子对象组合成派生类中的一个共享实例,从而提供了必要的灵活性。同时,即使一个基类是虚拟的,我们仍然可以通过该基类类型的指针或引用,来操纵派生类的对象。

原文地址:http://blog.csdn.net/ring0hx/article/details/1605254

C++ Virtual详解相关推荐

  1. C++ Virtual详解

    C++ Virtual详解 Virtual是C++ OO机制中很重要的一个关键字.只要是学过C++的人都知道在类Base中加了Virtual关键字的函数就是虚拟函数(例如函数print),于是在Bas ...

  2. 虚方法virtual详解(转载)

    从C#的程序编译的角度来看,它和其它一般的函数有什么区别呢?一般函数在编译时就静态地编译到了执行文件中,其相对地址在程序运行期间是不发生变化的,也就是写死了的!而虚函数在编译期间是不被静态编译的,它的 ...

  3. C++基础之关键字——virtual详解

    virtual C++中的virtual关键字主要有这样几种使用场景:第一,修饰父类中的函数 :第二,修饰继承性.注意:友元函数.构造函数.static静态函数不能用virtual关键字修饰.普通成员 ...

  4. C#中虚方法(virtual)详解

    前言;在前面的博客重写中提到了虚方法和抽象类,本博文详细介绍下虚方法: 目录: 一.虚方法的定义: 二.虚方法的声明: 三.虚方法的调用: 四.虚方法的规则: 五.虚方法注意事项: 六.虚方法的好处: ...

  5. Virtual PC 2007下虚拟机与本机双XP系统实现互联与上网详解

    Virtual PC 2007下虚拟机与本机双XP系统实现互联与上网详解 1.在虚拟机安装windowsXP系统 2.为了不影响用来上网的原网卡,所以我们选择在主机上装一个虚拟网卡来与虚拟机进行通信, ...

  6. Virtual Hard Disk(VHD)文件格式详解

    VHD 镜像格式详解 0. References windows github 1. Overview Microsoft 虚拟化产品中常用的镜像格式是虚拟硬盘 (VHD) 镜像格式.它既用于存储硬盘 ...

  7. Virtual Hard Disk version 2 (VHDX) 文件格式详解

    VHDX 镜像格式详解 0. References windows github 1. Overview Microsoft 虚拟化产品中常用的映像格式是虚拟硬盘版本 2 (VHDX) 映像格式.该规 ...

  8. docker常用命令详解

    docker常用命令详解 本文只记录docker命令在大部分情境下的使用,如果想了解每一个选项的细节,请参考官方文档,这里只作为自己以后的备忘记录下来. 根据自己的理解,总的来说分为以下几种: Doc ...

  9. arm-linux-ld中的参数,arm-linux-ld指令详解

    arm-linux-ld指令详解 我们对每个c或者汇编文件进行单独编译,但是不去连接,生成很多.o 的文件,这些.o文件首先是分散的,我们首先要考虑的如何组合起来:其次,这些.o文件存在相互调用的关系 ...

最新文章

  1. 程序员面试题100题第19题——反转链表
  2. struct x264_t 维护着CODEC的诸多重要信息
  3. jquery each函数 break和continue功能
  4. ACM模板——拓扑排序
  5. 【Elasticsearch】 es primary shard 主分片 PrimaryOperationTransportHandler
  6. linux解挂文件磁盘的命令,Linux学习笔记(4)磁盘分区(fdisk)、挂载与文件系统命令...
  7. 新零售不简单,当初马云自己都没解释清楚!
  8. 怎样做才是一个独立自主的人?
  9. Ruby的case语句
  10. 未来计算机体系结构探索,未来计算机体系结构将是什么样的发展趋势
  11. 平均年薪 15 万,超 6 成本科学历,程序员薪资调查报告大曝光!
  12. 南京大学计算机视觉博士生导师,孙正兴——著名计算机专家孙正兴——南京大学教授...
  13. 北京航空航天大学计算机学院保研率,北京航空航天大学计算机学院(专业学位)计算机技术保研条件...
  14. Atcoder题解与视频集
  15. 二进制数字的表示方法
  16. 2019最值得收藏的24个日语学习网站
  17. java坦克大战(2.0)
  18. 360类redis存储服务Pika的安装和使用
  19. TCP/IP路由协议-BGPOSPF路由抖动问题
  20. Hunter的读《高效程序员的45个习惯》

热门文章

  1. jQuery ajax 请求 和 Submit 提交 form 表单
  2. BT宝塔面板安装流程(图文教程)
  3. Kafka生产者ack和lag机制剖析
  4. Java中Scanner 的用法/ Scanner怎么使用
  5. Quartz定时器的时间设置
  6. 撰写论文时常用的研究方法有哪些?
  7. Reactor模式--VS--Proactor模式
  8. 【conda安装】mamba安装 ==conda安装记录
  9. Usage of Pseudocode
  10. SSM框架配置文件整理