C++虚函数virtual详解

嵌入式技术

1368人已加入

描述

有无虚函数的对比

C++ 中的虚函数用于解决动态多态问题,虚函数的作用是允许在派生类中重新定义与积累同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。

首先写两个简单的类,类 B 继承自类 A,即 A 是基类,B 是派生类。

classA{public:voidprint(){cout<<"A"<<endl;}};classB:publicA {public:voidprint(){cout<<"B"<<endl;}};intmain(){B b;//创建一个 B 类对象 b;A &a = b;//a 是 b 的一个 A 类引用;A *pa = &b;//pa 是一个指向 A 类对象的指针;a.print();pa->print();b.print();return0;}

程序中,A 类和 B 类均定义了一个同名函数 print ,但两个函数的功能不同,编译系统按照同名覆盖原则决定调用对象。

另外一点,引用的本质是指针常量,可以认为 a,pa 都指向了 b。( 注意区分常量指针与指针常量,常量指针可以类比于整型指针,即指向一个常量的指针,指针的指向可以修改;指针常量类比于整型常量,即一个指针是个常量,也就是指针只能固定的指向某一单元,指针常量的指向不可改而指向的值可以修改。)

inta, b;int*constp1 = &a;//指针常量constint*p2 = &b;//常量指针

执行函数后,我们发现结果为

Virtual

因为 a 是 A 类的一个引用,所以 a 的 print( ) 依旧是 A 类的成员函数;pa 是 A 类的指针,同理;而 b 是 B 类的对象,调用的 print( ) 为 B 类的成员函数。简言之就是,没有 virtual 时,调用哪一类的成员函数取决于调用对象 a ,pa,b 在定义时的类型。而此时,若 B 类对象 b 想调用直接基类 A 的 print 函数,则应当 b.A::print( )。

这种 a,pa,b 能调用哪个同名函数在对象定义时已经确定好了的多态,我们称之为静态多态。什么是多态?同一个 print 函数在不同的对象中有不同的作用,这就呈现了多态。

这里再提一点,原本基类指针是用来指向基类对象的,如果用它指向派生类对象,此时基类指针指向的是派生类对象中的基类部分。在没有虚函数时,基类指针是无法调用派生类对象中的成员函数的。

而当我们在 A 类中 print( ) 前加上关键字 virtual,变成虚函数时

classA{public:virtualvoidprint(){cout<<"A"<<endl;}};classB:publicA{public:voidprint(){cout<<"B"<<endl;}};

再次执行主函数,结果为

Virtual

这是因为 virtual 跟着对象走,即调用的 print( ) 究竟是 A 类还是 B 类的成员函数取决于“ 调用者 ” a,pa 所指的对象 b 属于哪一类,而不再是取决于 a,pa 本身在定义时的类型了。

这种用基类指针或引用指向某一派生类对象,从而能够调用指针指向的派生类对象中的函数的多态,我们称为动态多态,virtual 正是实现动态多态的关键字。

虚函数表

接着刚才的话题,在 A 类中有虚函数的前提下,我们继续讨论

classC{public:voidprint(){cout<<"C"<<endl;}};intmain(){cout<<"sizeof(A): "<<sizeof(A) <<endl;cout<<"sizeof(B): "<<sizeof(B) <<endl;cout<<"sizeof(C): "<<sizeof(C) <<endl;A a;B b;C c;cout<<"sizeof(a): "<<sizeof(a) <<endl;cout<<"sizeof(b): "<<sizeof(b) <<endl;cout<<"sizeof(c): "<<sizeof(c) <<endl;return0;}

执行结果为

Virtual

为什么有虚函数的 A 类大小为 8 字节,继承 A 的 B 为 8 字节,而没有虚函数的 C 类是 1 字节呢?联想到 64 位操作系统下指针占8个字节内存,而 A 大小也是 8 字节,是巧合吗?

事实上,在包含虚函数的类中,在该类的存储空间中,会有一个指向虚函数表的指针,正是这个指针使 A 的大小变为 8 字节。而指针所指的虚函数表本质上是 A 类中定义的所有虚函数名构成的列表。

A 中只定义了一个虚函数 print( ) ,所以虚函数表中也只有一个虚函数名 print,通过这个虚函数名,再找到整个虚函数 print( ) 在内存中的存储位置 ( B 同理 ) 。

验证虚函数表的存在性

虚函数表看不见摸不着,怎么确定它的存在呢?

intmain(){A a;B b;a.print();b.print();cout <<"-------------------"<< endl;typedef void (*func)(); //利用函数指针func((func**)(&a))[0][0](); //((func**)(&a))[0] 代表对象a的内存空间中的第一个元素:指向虚函数表的指针;((func**)(&b))[0][0](); //((func**)(&a))[0][0] 表示虚函数表中第一个函数名;return0;}

Virtual

从结果中我们可以发现,((func**)(&a))[0][0]();等效于a.print();,即确实证明了对象 a 的内存空间中存有一个指向虚函数表的指针,虚函数表的第一个函数名正是 print 。

  审核编辑:汤梓红

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表德赢Vwin官网 网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分