c++虚函数介绍
一、虚函数
前面已经简单介绍过虚函数了,这里再稍微带下:虚函数就是那些被声明为virtual,并在派生类中重新定义的成员函数。
1.1、虚函数的作用
虚函数让派生类可以对基类中的函数进行动态覆盖,是实现c++多态性的一大工程
1.2、虚函数是怎么实现的
虚函数的实现是依赖虚拟表。用比较通俗的话解释,就是有一个隐藏的指向基类的指针,我们称为vptr,vptr在创建类实例时自动设置,以便指向该类的虚拟表。这里需要特别注意的是:vptr是一个真正的指针,这代表它会占一个指针大小的内存,也意味着vptr会被派生类继承。 而虚拟表中,原理是一个静态数组,保存着一个虚函数对应的其他实例的地址。
虚拟表的实现原理:如果你创建一个类,并且类中有定义了虚函数,那么编译器编译的时候就会生成该类的虚函数表,并且为它分配指针,而虚函数表的大小,取决于你定义了多少个虚函数,定义得越多,占用内存越大。不管是基类还是派生类,每个类都有一个虚函数表,并且这个虚函数表中指向的函数都是自己类的函数位置。对于派生类来说,会复制一份基类的虚函数表,如果其中某个虚函数重写了,则会使用新的地址替换旧地址。
1.3、虚函数传参
先看看下面例子
1 |
|
默认参数是静态绑定的,虚函数的是动态绑定的,所以调用的是派生类的fun,但是打印出来的值是基类上fun的参数值
- 在
main()中,bp是Base*类型的指针,尽管它指向的是Derived类型的对象,但它的静态类型是Base*。 - 因此,编译器会使用
Base::fun()的默认参数,而不是Derived::fun()的默认参数。编译器选择Base::fun()时,默认参数x = 10会被采用。
1.4、虚函数与其他函数的关系
1.4.1、静态函数可以声明为虚函数吗?
不行
之所以虚函数不可以为static函数,是因为虚函数可以实现多态性,也就是同一个函数可能有不同的实现,这个是在调用时才决定调用哪个类的函数,但是如果定义为static,那么在调用类的时候就已经分配好内存了,无论怎么调用都是调用的同一个函数
1.4.2、构造函数可以是虚函数吗?
不行
构造函数不可以声明为虚函数。同时除了inline和explicit之外,构造函数不允许使用其它任何关键字。
如果类中有虚函数,那么编译器会在构造函数中添加代码来创建vptr。如果构造函数时虚的,那么它也需要vptr来访问vtable,可这个时候vptr还没有产生。
1.4.3、析构函数可以是虚函数吗?
可以
析构函数可以声明为虚函数。甚至可以说,析构函数最好都定义为虚函数。如果我们需要删除一个指向派生类的基类指针时,应该把析构函数声明为虚函数。事实上,只要一个类有可能会被其他类继承,就应该声明虚析构函数。为什么??
先看下面这个例子来帮助理解
1 |
|
输入结果如下:只调用到了基类的析构函数去释放资源,而没有调用派生类的析构函数
Base destroyed
只有将基类的析构函数定位为虚函数,才可以同时调用到基类和派生类的析构函数。
这里衍生出一个题外话:那为什么默认的析构函数并没有定义为虚函数呢?
前面我们已经介绍过,一个类有虚函数时,在构造函数中会生成一个vptr指针和vtable,而vptr的一个真实存在的指针类型,会占用内存,如果默认析构函数就定义为虚函数,会造成额外的内存损耗。
1.4.4、友元函数可以是虚函数吗?
不行
因为友元函数不属于类函数,只有类函数才可以为虚函数。
