定义: 在某基类中声明为virtual并在一个活多个派生类中被重新定义的成员函数.
语法: virtual 函数返回类型 函数名(参数表) {函数体}
用途: 实现多态性,通过指向派生类的基类指针,访问派生类中同名覆盖成员函数
虚函数必须是基类的非静态成员函数,其访问权限可以是protected或public,在基类的类定义中定义虚函数的一般形式:
class 基类名{
......
virtual 返回值类型 将要在派生类中重载的函数名(参数列表);
};
作用: 虚函数的作用是实现动态联编,也就是在程序的运行阶段动态地选择合适的成员函数,在定义了虚函数后,可以再基类的派生类中对虚函数重新定义,在派生类中重新定义的函数应与虚函数具有相同的形参个数和形参类型,以实现统一的借口,不同的定义过程.如果在派生类中没有对虚函数宠幸定义,则它继承其基类的虚函数.
调用: 只能通过指向基类的指针或基类对象的引用来调用虚函数,其格式:
1、指向基类的指针变量名->虚函数名(实参表)2、基类对象的引用名. 虚函数名(实参表)
(经VS2005测试,可以通过派生类的引用或者指针调用虚函数)
注意事项:
1. 非类的成员函数不能定义为虚函数
2. 类的成员函数中静态函数和构造函数不能定义为虚函数
3. 但析构函数可以定义为虚函数. 实际上,优秀的程序员常常把基类的析构函数定义为虚函数.将基类的析构函数定义为虚函数后,当利用delete删除一个指向派生类的对象指针
时,系统将会调用相应的派生类的析构函数.
4. 只需要在声明函数的类体中实用关键字"virtual"将函数声明为虚函数,而定义函数时不需要使用"virtual"
5. 将基类中的某一成员函数声明为虚函数后,派生类中的同名函数自动成为虚函数(即派生类中不需要再用virtual声明)
6. 如果声明了某个成员函数为虚函数,则在该类中不能出现和这个成员函数同名并且返回值,参数个数,类型都相同的非虚函数.在以该类为基类的派生类中,也不能出现这种同名
函数. (但经VS2005测试,可以在基类和派生类中定义重载函数)
7. 经VS2005测试,基类和派生类中可以定义同名的非虚函数,基类引用的该函数调用将使用基类的函数,派生类引用的该函数调用将使用派生类函数,但如果使用指向派生类的
基类指针调用该函数,将使用基类函数
8. 如果要在派生类中覆盖虚函数机制并强制使用虚函数的特定版本,这时可以使用域操作符(通常是让基类虚函数完成所有类型的公共任务,然后在派生类虚函数中先调用基类
虚函数,再完成特定类的任务)
9. 通过基类的引用或指针调用虚函数时,默认实参为在基类虚函数声明中指定的值,如果通过派生类的指针或引用调用虚函数,则默认实参是在派生类的版本中声明的值.
纯虚函数: 在虚函数形参表后面写上=0以指定虚函数为纯虚函数.含有(或继承)一个或多个纯虚函数的类是抽象基类.
纯虚函数表明:该函数为后代类型提供了可以覆盖的借口,但是这个类中的版本绝不会调用,重要的是,用户不能创建抽象基类的对象.
虚函数的实现:
虚函数是如何做到因对象的不同而调用其相应的函数的呢?现在我们就来剖析虚函数。我们先定义两个类
class A{ //虚函数示例代码 public: virtual void fun(){cout<<1<<endl;} virtual void fun2(){cout<<2<<endl;} }; class B:public A{ public: void fun(){cout<<3<<endl;} void fun2(){cout<<4<<endl;} }; 由于这两个类中有虚函数存在,所以编译器就会为他们两个分别插入一段你不知道的数据,并为他们分别创建一个表。那段数据叫做vptr指针,指向那个表。那个表叫做vtbl,每个类都有自己的vtbl,vtbl的作用就是保存自己类中虚函数的地址,我们可以把vtbl形象地看成一个数组,这个数组的每个元素存放的就是虚函数的地址,请看图 通过上图,可以看到这两个vtbl分别为class A和class B服务。现在有了这个模型之后,我们来分析下面的代码 A *p=new A; p->fun(); 毫无疑问,调用了A::fun(),但是A::fun()是如何被调用的呢?它像普通函数那样直接跳转到函数的代码处吗?No,其实是这样的,首先是取出vptr的值,这个值就是vtbl的地址,再根据这个值来到vtbl这里,由于调用的函数A::fun()是第一个虚函数,所以取出vtbl第一个slot里的值,这个值就是A::fun()的地址了,最后调用这个函数。现在我们可以看出来了,只要vptr不同,指向的vtbl就不同,而不同的vtbl里装着对应类的虚函数地址,所以这样虚函数就可以完成它的任务。 而对于class A和class B来说,他们的vptr指针存放在何处呢?其实这个指针就放在他们各自的实例对象里。由于class A和class B都没有数据成员,所以他们的实例对象里就只有一个vptr指针。由上可以推论出,在取虚函数的地址时,取到的并不是函数的地址,而是一段间接获得虚函数地址的一段代码的地址,因此,在用指向派生类的基类指针去取函数地址,取到的也是派生类的指针.