长文干货预警
虚机制,一个听起来不好惹的角色,却是C++面向对象部分的精髓。不得不承认的是,这部分的内容有些多,也不太好理解。但同样不得不承认的是,虚机制在面向对象部分准确说是多态特性发挥着举足轻重的作用。可以说没有了虚机制,C++的面向对象就没有了灵魂。
因此,这一篇,我们就来整理有关虚机制的内容。本文的内容思维导图如下:
有关静态编联和动态编联的内容,可以参考另一篇博文:
啃书《C++ Primer Plus》面向对象部分 静态联编与动态联编
虚函数的创建和实现
这虚函数也是函数啊,唯一不同的就是它比较虚~~
因此,只需要在成员函数前面加上“虚”就完事了。
虚函数必须是成员函数
- 这里,需要强调,虚函数必须是成员函数!!!
对于这样的定义:
class A{
public:
static virtual void f(){
} //企图定义一个静态的虚函数
};
编译器会给予报错:
告知静态的和虚的不允许同时出现。
这样的规定不难理解,只需要认识到,静态函数是仅属于类的,而成员函数“属于”对象。
虚函数提供了一种父类声明,子类实现的机制,这与静态函数仅属于类是冲突的。
虚函数的声明
刚刚说到,在成员函数前加上“虚”的修饰,就可以让它变成虚函数。这个"虚"就是英文单词virtual
class A{
public:
virtual void f(){
}//在A类中定义了一个虚函数f
};
在子类中重写这个虚函数时,则可以省略掉virtual
关键字
class A{
public:
virtual void f(){
}//基类中定义了一个虚函数
};
class B: public A{
public:
void f(){
}//派生类中重写虚函数可以省略virtual
};
override与overwrite
这里介绍两个概念:override
和overwrite
override
的意思是重写虚函数,代表着子类给父类的某些虚函数提供一种实现,这是虚函数实现多态最常见的途径,即父类声明函数,子类提供实现。
在刚刚的例子中,B类重新实现了A类的虚函数f的行为就是一种重写。
overwrite
译作中文应该叫“覆盖”,(不过由于翻译的五花八门,因此博主还是建议改概念使用英文来记忆比较妥当。)这个概念的并不仅仅出现在虚函数的范围中,在一般成员函数中也有所体现。
具体的说,就是当子类使用了父类中出现过的函数名称时,不论其返回类型、参数列表,是否被const修饰等因素是否相同,子类的该函数会覆盖所有的同名父类函数。
虚函数也不例外,也难逃overwrite
的魔爪
#include<iostream>
using namespace std;
class A{
public:
/*基类定义两个版本的函数f*/
virtual void f(){
cout << "func f in class A" << endl;}
virtual void f(int k){
cout << "func f in class A and " << k << endl;}
};
class B:public A{
public:
/*派生类重写了其中一个函数f*/
virtual void f(){
cout << "func f in class B" << endl;}
//overwrite了另一个版本的函数,在B类作用域中没有带有一个整型参数的f函数
};
int main()
{
A* pa = new B();
pa->f();
pa->f(10);
B pb = new B();
pb->f();
pb->f(10); //这个调用将会报错,B类中没有这个函数
delete p;
}
虚函数表和虚指针
说了这么多有关虚函数的使用,是时候来介绍虚函数的原理了,书中在介绍动态联编处理虚函数的时候用到了这样的描述:
编译器必须生成能够在程序运行时选择正确虚方法的代码,这称为动态联编(dynamic binding),又称为晚联编(late binding)
这段用来进行动态联编的代码便是虚函数的原理,这其中就包含了虚函数表和虚指针。
虚函数表
虚函数表叫做Virtual Table,简称V-Table
简单些说,就是放置了一个类中所有虚函数指针的表,是一个函数指针数组。
下面来说明各种情况下虚函数表的形态
无继承时
例如对于下面的类:
class