C++中的虚函数解析[The explanation for virtual function of CPlusPlus]
1.什么是虚函数?
答:在C++的类中,使用virtual修饰的函数。
例如: virtual void speak() const { std::cout << "Mammal speak!\n"; }
2.虚函数有什么作用?
答:虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。
例如:定义一个父类Mammal类
1: class Mammal2: {3: public:4: Mammal():age(1) { std::cout << "Mammal constructor ...\n"; }5: ~Mammal() { std::cout << "Mammal destructor ...\n"; }6: void move() const { std::cout << "Mammal, move one step\n"; }7: virtual void speak() const { std::cout << "Mammal speak!\n"; }8:9: protected:10: int age;11: };
再定义一个子类Dog类,继承Mammal类,子类Dog类中重新定义了speak()函数
1: class Dog : public Mammal2: {3: public:4: Dog() { std::cout << "Dog constructor ...\n"; }5: ~Dog() { std::cout << "Dog destructor ..\n"; }6: void wagTail() { std::cout << "Wagging tail ...\n"; }7: virtual void speak() const { std::cout << "Woof!\n"; }8: void move() const { std::cout << "Dog moves 5 steps ...\n"; }9: };
现在通过指针来访问基类和子类中的同名函数
1: int main()2: {3: Mammal *pMam = new Mammal;4: Mammal *pDog = new Dog;5: pMam->speak();6: pDog->move();7: pDog->speak();8: return 0;9: }
运行结果:

3.虚函数的使用?
首先,明确一点,虚函数是用来实现多态的,如果类继承中无需实现多态,请不要使用虚函数,后面会讲到,使用虚函数实际上会增加开销。
3.1单继承的形式

由Mammal派生出多个子类,每个子类唯一的继承Mammal父类,可以看到,每个子类都有一个虚函数 void speak()重写了父类中的虚函数,这是因为就这种动物类而言,每一种子类的发声都不同,需要重新定义。
1: #include <iostream>2:3: class Mammal4: {5: public:6: Mammal():age(1) { }7: ~Mammal() { }8: virtual void speak() const { std::cout << "Mammal speak!\n"; }9: protected:10: int age;11: };12:13: class Dog : public Mammal14: {15: public:16: void speak() const { std::cout << "Woof!\n"; }17: };18:19: class Cat : public Mammal20: {21: public:22: void speak() const { std::cout << "Meow!\n"; }23: };24:25: class Horse : public Mammal26: {27: public:28: void speak() const { std::cout << "Whinny!\n"; }29: };30:31: class Pig : public Mammal32: {33: public:34: void speak() const { std::cout << "Oink!\n"; }35: };
访问各类:
1: int main()2: {3: Mammal* array[5];4: Mammal* ptr;5: int choice, i;6: for (i = 0; i < 5; i++)7: {8: std::cout << "(1) dog (2) cat (3) horse (4) pig: ";9: std::cin >> choice;10: switch (choice)11: {12: case 1:13: ptr = new Dog;14: break;15: case 2:16: ptr = new Cat;17: break;18: case 3:19: ptr = new Horse;20: break;21: case 4:22: ptr = new Pig;23: break;24: default:25: ptr = new Mammal;26: break;27: }28: array[i] = ptr;29: }30: for (i=0; i < 5; i++)31: {32: array[i]->speak();33: }34: return 0;35: }
访问结果:

小结:
- 当在类中引入虚函数的时候,这个类的对象必须跟踪它,每个对象会添加 vptr,其指向的一个虚拟函数表v-table,从而增加额外的空间。
- 当出现继承关系时,虚拟函数表可能需要改写,即当用基类的指针指向一个派生类的实体地址,然后通过这个指针来调用虚函数。这里要分两种情况,当派生类已经改写同名虚函数时,那么此时调用的结果是派生类的实现;而如果派生类没有实现,那么调用依然是基类的虚函数实现,而且只在多态、虚函数上表现。
- 多态仅仅在虚函数上表现,意即倘若同样用基类的指针指向一个派生类的实体地址,那么这个指针将不能访问和调用派生类的成员变量和成员函数。
- 对于上述代码,在编译阶段,无法知道将创建什么类型的对象,因此无法知道将调用哪个speak()。ptr指向的对象是在运行阶段确定的,这被称为后期绑定或运行阶段绑定,与此相对的是静态绑定或编译阶段绑定。
3.2多继承的情况

C既继承了A,也继承了B,类定义的代码如下:
1: #include <iostream>2: using namespace std;3: class A4: {5: public:6: A() { cout << "A construction" << endl; }7: virtual ~A() { cout << "A destruction" << endl; }8: int a;9: void fooA() {}10: virtual void func(){ cout << "A func." << endl; };11: virtual void funcA() { cout << "funcA." << endl; }12: };13:14: class B15: {16: public:17: B() { cout << "B construction" << endl; }18: virtual ~B() { cout << "B destruction" << endl; }19: int b;20: void fooB() {}21: virtual void func() { cout << "B func." << endl; };22: virtual void funcB() { cout << "funcB." << endl; }23: };24:25: class C : public A, public B26: {27: public:28: C() { cout << "C construction" << endl; }29: virtual ~C() { cout << "C destruction" << endl; }30: int c;31: void fooC() {}32: virtual void func() { cout << "C func." << endl; };33: virtual void funcC() { cout << "funcC." << endl; }34: };35:36: int main()37: {38: A *pa = new A();39: pa->func();40:41: B *pb = new B();42: pb->func();43:44: C *pc = new C();45: pc->func();46:47: A *pac = new C();48: pac->func();49:50: system("pause");51: return 0;52: }
可以看到A,B,C三个类的构造函数和虚函数都不同,下面测试一下,创建对象以及调用虚函数时,派生类及父类的函数是如何执行的。
运行一下,观察结果:

分析小结:
- 当对一个多继承的类实例化的时候,调用了多个父类的构造函数,且也调用了子类的构造函数。
- 当出现继承关系时,子类的虚拟函数直接覆盖了父类的续写函数,相当于重写了该函数,实际上,在编译的时候,子类的虚拟函数列表指针就直接把继承的父类的虚拟函数的地址覆盖掉了。
4.总结
创建第一个虚函数时候就会创建一个v-table,包含虚函数成员的类必须维护v-table,因此会带来一些开销。如果类很小,并且不打算从它派生出其他类,就根本没必要使用虚函数。
C++中的虚函数解析[The explanation for virtual function of CPlusPlus]的更多相关文章
- C++中纯虚函数
1.纯虚函数 virtual ReturnType Function()= 0; 纯虚函数可以让类先具有一个操作名称,而没有操作内容,让派生类在继承时再去具体地给出定义.凡是含有纯虚函数的类叫做抽象类 ...
- 谈谈c++中继承中的虚函数
c++继承中的虚函数 c++是一种面向对象的编程语言的一个很明显的体现就是对继承机制的支持,c++中继承分很多种,按不同的分类有不同分类方法,比如可以按照基类的个数分为多继承和单继承,可以按照访问 ...
- C++中的虚函数以及虚函数表
一.虚函数的定义 被virtual关键字修饰的成员函数,目的是为了实现多态 ps: 关于多态[接口和实现分离,父类指针指向子类的实例,然后通过父类指针调用子类的成员函数,这样可以让父类指针拥有多种形态 ...
- EC笔记,第二部分:9.不在构造、析构函数中调用虚函数
9.不在构造.析构函数中调用虚函数 1.在构造函数和析构函数中调用虚函数会产生什么结果呢? #; } 上述程序会产生什么样的输出呢? 你一定会以为会输出: cls2 make cls2 delete ...
- 关于在C#中构造函数中调用虚函数的问题
在C#中如果存在类的继承关系,应避免在构造函数中调用虚函数.这是由于C#的运行机制造成的,原因如下: 新建一个类实例时,C#会先初始化该类(对类变量赋值,并将函数记在函数表中),然后再初始化父类.构造 ...
- C++ 构造函数中调用虚函数
我们知道:C++中的多态使得可以根据对象的真实类型(动态类型)调用不同的虚函数.这种调用都是对象已经构建完成的情况.那如果在构造函数中调用虚函数,会怎么样呢? 有这么一段代码: class A { p ...
- 【转】C++虚函数解析
本文转自陈皓大叔(左耳朵耗子)的博客www.coolshell.com. 文章是很久之前所写,去年还在写C++时有幸拜读,现在想起来还是相当有价值一转的,如果有一定C++基础(特别是读过<深度探 ...
- C++箴言:避免构造或析构函数中调用虚函数
如果你已经从另外一种语言如C#或者Java转向了C++,你会觉得,避免在类的构造函数或者析构函数中调用虚函数这一原则有点违背直觉.但是在C++中,违反这个原则会给你带来难以预料的后果和无尽的烦恼. 正 ...
- 读书笔记 effective c++ Item 9 绝不要在构造函数或者析构函数中调用虚函数
关于构造函数的一个违反直觉的行为 我会以重复标题开始:你不应该在构造或者析构的过程中调用虚函数,因为这些调用的结果会和你想的不一样.如果你同时是一个java或者c#程序员,那么请着重注意这个条款,因为 ...
随机推荐
- The 7th Zhejiang Provincial Collegiate Programming Contest->Problem G:G - Wu Xing
http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3328 至今未看懂题意,未编译直接提交,然后 A了.莫名AC总感觉怪怪的. ...
- 成功解决Tomcat-JDBC-MySQL乱码
0.MySQL-JDBC驱动文档 官方解释 1.数据库的字符编码和表内字段的编码 在MySQL中数据库的字符编码和表内字段的编码的要指定为utf8(utf8_general_ci) 2.jsp中 pa ...
- Maven的Dependency怎么找?
用了Maven,所需的JAR包就不能再像往常一样,自己找到并下载下来,用IDE导进去就完事了,Maven用了一个项目依赖(Dependency)的概念,用俗话说,就是我的项目需要用你这个jar包,就称 ...
- Interface Serializable
public interface Serializable Serializability of a class is enabled by the class implementing the ja ...
- 阿里云PHP Redis代码示例
测试代码示例 <?php /* 这里替换为连接的实例host和port */ $host = "localhost"; $port = 6379; /* 这里替换为实例id和 ...
- linux 使用文本编辑器编写shell脚本执行权限不够
在linux下,自己编写的脚本需要执行的时候,需要加上执行的权限 解决方式:chmod 777 test.sh
- 手机金属外壳加工工艺:铸造、锻造、冲压、CNC
现如今金属手机成为行业的热点,在消费电子产品中应用越来越广,本文详细介绍几种金属加工工艺及相关产品应用. 1.CNC+阳极:iPhone 5/6, HTC M7 2.锻造+CNC:华为P8,HTC M ...
- Qt之四种等待提示框
http://blog.csdn.net/u011012932/article/details/51029602http://blog.csdn.net/u011012932/article/deta ...
- Android:@id和@+id
@id代表引用已有的id,而@+id是新增加一个id 如果使用@+id/name形式,当R.java中存在名为name变量时,则该组件会使用该变量的值作为标识.如果不存在该变量,则添加一个新的变量,并 ...
- linux非阻塞的socket EAGAIN的错误处理
http://blog.csdn.net/tianmohust/article/details/8691644 在Linux中使用非阻塞的socket的情形下. (一)发送时 当客户通过Socket提 ...