Virtual虚函数
- 在面向对象的C++语言中,虚函数(virtual function)是一个非常重要的概念。因为它充分体现了面向对象思想中的继承和多态性这两大特性,在C++语言里应用极广。
- 多态性:其含义就是多种形式;将具有继承关系的多种类型称之为多态模型,因为使用者可以使用这种类型的多种形式,但是不需要在意他们之间的差异。归根结底,引用或指针的静态类型与动态类型的不一致是C++语言支持多态性的根本原因。
- 在派生类中覆盖了某一个虚函数,可以再一次使用virtual关键字指出该函数的性质。如果将一个函数声明为虚函数,那么这个函数在所有的派生类别中都是虚函数。一个派生类的函数如果覆盖了某个继承而来的虚函数,则它的形参类型必须要和被覆盖的函数完全一致。同样,派生类中的虚函数返回的类型必须和基类的类型是一致的,但是这个存在一个例外。当类的虚函数返回的类型是类本身的指针或者引用的时候,上述规则无效。
- 什么是虚函数呢?虚函数是指一个类中你希望重载的成员函数,当你用一个基类指针或引用指向一个继承类对象的时候,你调用一个虚函数,实际调用的是继承类的版本。 ——摘自MSDN
#include <cstring>
#include <string>
#include <iostream>
#include <stdio.h>
#include <conio.h>
using namespace std;class Parent{public:char data[20];void Function1();virtual void Function2(); //这里的Function2是虚拟函数}parent;void Parent::Function1() {printf("This is parent,function1\n");
}void Parent::Function2() {printf("This is parent,function2\n");
}class Child:public Parent{void Function1();void Function2();
}child;void Child::Function1(){printf("This is child,function1\n");
}void Child::Function2() {printf("This is child,function2\n");
}
int main(int argc,char* argv[]){Parent *p;//定义了一个基类指针if(_getch() == 'c'){p = &child; // 如果用户输入一个小写的字母c,指向继承类对象}else{p = &parent; // 否则指向一个基类对象}p->Function1(); // 这里编译时会直接给出Parent::Function1()的入口地址p->Function2(); // 注意这里,执行的是哪一个Function2?}
- 为什么会有第一行的结果呢?因为是用一个Parent类的指针调用函数Fuction1(),虽然实际上这个指针指向的是Child类的对象,但编译器无法知道这一事实(直到运行的时候,程序才可以根据用户的输入判断出指针指向的对象),它只能按照调用Parent类的函数来理解并编译,所以看到了第一行的结果。
- 那么第二行的结果又是怎么回事呢?注意到,Function2()函数在基类中被virtual关键字修饰,也就是说,它是一个虚函数。虚函数最关键的特点是“动态联编”,它可以在运行时判断指针指向的对象,并自动调用相应的函数。
- 如果在运行上面的程序时任意输入一个非c的字符,结果如下:
- 请注意看第二行,它的结果出现了变化。程序中仅仅调用了一个Function2()函数,却可以根据用户的输入自动决定到底调用基类中的Function2还是继承类中的Function2,这就是虚函数的作用。在MFC中,很多类都是需要继承的,它们的成员函数很多都要重载,比如编写MFC应用程序最常用的CView::OnDraw(CDC*)函数,就必须重载使用。把它定义为虚函数(实际上,在MFC中OnDraw不仅是虚函数,还是纯虚函数),可以保证时刻调用的是用户自己编写的OnDraw。虚函数的重要用途在这里可见一斑。
- PS:一定要注意“静态联翩 ”和“ 动态联编 ”的区别,对于我来说,若没有在VC6.0中亲自去测试,凭自己的感觉,当在键盘中输入“c”时,因为虽然实际上这个指针指向的是Child类的对象,但编译器无法知道这一事实,它只能按照调用Parent类的函数来理解并编译,所以我们看到了第一行的结果。第二行中调用了子类的function2,完全是因为virtual 的功能,virtual实现了动态联编,它可以在运行时判断指针指向的对象,并自动调用相应的函数。当然,如果执行的是p=&parent; 这一句,该指针很明显的是指向父类,那么肯定调用的是父类的方法
final和override说明符
- 派生类如果定义了一个函数与基类的虚函数的名字相同但是形参列表不同,这仍然是一个合法的行为,需要使用关键字override来说明派生类中的虚函数。
- 如果将某个函数指定为final,则之后任何尝试覆盖该函数的操作都会引发错误。
虚函数和默认实参
- 虚函数使用默认实参,则基类和派生类中定义的默认实参最好一致。
回避虚函数的机制
- 如果希望对于虚函数的调用不是动态的绑定,而是强迫其执行虚函数的某一个特定的版本。可以使用作用域运算符来实现
- double undiscounted = baseP->Quote::net_price(43);//强行调用基类中定义的函数版本,而不管baseP的动态类型是什么
- 通常情况下,只有成员函数(或友元函数)中的代码才需要使用作用域运算符来回避虚函数的机制。如果一个派生类虚函数需要调用它的基类版本,但是没有使用作用域运算符,则在运行的时候该调用会被解析成对于派生类的版本自身的调用,从而导致无限的递归。