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#程序员,那么请着重注意这个条款,因为 ...
随机推荐
- c_str 以及atoi
const char *c_str();c_str()函数返回一个指向正规C字符串的指针, 内容与本string串相同. 这是为了与c语言兼容,在c语言中没有string类型,故必须通过string类 ...
- stl map高效遍历删除的方法
for(:iter!=mapStudent.end():) { if((iter->second)>=aa) { //满足删除条件,删除当前结点,并指 ...
- 学点PYTHON基础的东东--数据结构,算法,设计模式---访问者模式
说实话,感觉不是特别多,可能没遇到过多场面, 所以对应用场景没感觉吧. 反正,各种模式就是把类的实例传来传去,久而久之,产生了一些规律...:) # 轮子,引擎, 车身这些定义好了都不需要变动 cla ...
- UR #13 Yist
第一次打UR,打了一个半小时就弃疗了QAQ 这是我唯一一道考试的时候做出来的题目,其他两道连暴力都懒得写了 很容易发现对于每个要删除的点 我们找到左边第一个比他小的不用删除的点,右边第一个比他小的不用 ...
- Qt 二进制文件读写(使用“魔术数字”)
今天开始进入 Qt 的另一个部分:文件读写,也就是 IO.文件读写在很多应用程序中都是需要的.Qt 通过 QIODevice 提供了IO的抽象,这种设备(device)具有读写字节块的能力.常用的IO ...
- php curl下载图片 URL地址
<?php $url = 'http://mf1905.com/upload/video_img/df3074c98ec5124ad47c52ff59f74e04_middle.jpeg'; f ...
- cisco vpn client for win10 x64 setup package
win10 x64安装cisco vpn client报错,解决方法如下: 1.卸载以前安装的所有cisco vpn client,并重启电脑. 2.运行winfix.exe 3.安装Global V ...
- VC 设置 Stack Overflow
C/C++ stack overflow, 怎样设置stack大小?解决方案 (1) vc6.0: project->setting->link->project options-& ...
- 如何把Excel另存为XML格式文件(快速转换)
这时,我们尝试另存为另一种文件类型: XML电子表格2003(*.xml)
- Webform——Repeater多表联合显示
对于一个表里,通过外键连接如何显示另一个表的数据,前Winform里可以用封装类来实现. 对于Webform,可以用封装类,也可以用Repeater的ItemDataBound事件(//在项被绑定数据 ...