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#程序员,那么请着重注意这个条款,因为 ...
随机推荐
-
一些基本的C/C++数据类型
size_t size_t. A basic unsigned integer C/C++ type. It is the type of the result returned by sizeof ...
-
mrtg
centos6.5-64-minimal http://oss.oetiker.ch/mrtg/doc/cfgmaker.en.htmlhttp://www.cnblogs.com/see7di/ar ...
-
is not mapped 解决方法
1.确定是否已配置相关XML 2.注意类名大小写问题 3.如果是注解 第一种方式 @Entity(name = "Tb_User") public class User { ...
-
关于线程池ThreadPool的学习
学习重点ThreadPool.SetMinThreads(out workerThreads, out completionPortThreads).这是整个线程池的关键. 而ThreadPool. ...
-
C#位移运算符
代码如下: /// <summary> /// 位移运算符"<<"左位移运算符,">>"右位移运算符 /// 在进行位移运算 ...
-
【C++小白成长撸】--(续)双偶数N阶魔阵
原理: 把双偶数N阶魔阵均分为(N/4)^2个4阶魔阵(4*4) 每个魔阵的对角线都标为"-1",其余位置标为"0" 从第一个位置(a[0][0])从左到右,从 ...
-
SSE图像算法优化系列十七:多个图像处理中常用函数的SSE实现。
在做图像处理的SSE优化时,也会经常遇到一些小的过程.数值优化等代码,本文分享一些个人收藏或实现的代码片段给大家. 一.快速求对数运算 对数运算在图像处理中也是个经常会遇到的过程,特备是在一些数据压缩 ...
-
eclipse配置环境变量 (特别是输入javac无显示问题)
下载JDK:http://www.oracle.com/technetwork/java/javase/downloads/index.html 最近win10恢复了一下系统,重新给eclipse配一 ...
-
2018阿里云短信发送DEMO接入简单实例
以下更新2018-04-2309:57:54 后续不再更新, 基本类: app/SignatureHelper.php <?php namespace aliyun_mns; /** * 签名助 ...
-
对于input 框限定输入值为正整数,浮点型的js
<input type="text" value="" onkeyup="only_num(this)" onblur="o ...