1. 虚函数
1.1 虚函数的定义
- 虚函数:就是在基类的成员函数前加关键字virtual(即被virtual关键字修饰的成员函数),并在一个或多个派生类中被重新定义的成员函数;
- 虚函数:就是在编译的时候不确定要调用哪个函数,而是动态决定将要调用哪个函数。它的作用就是为了能让这个函数在它的子类里面可以被重载,这样的话,编译器就可以使用后期绑定来达到多态了,也就是用基类的指针来调用子类的这个函数;
- 虚函数的作用:在于用专业术语来解释就是实现多态性,多态性是将接口与实现进行分离,通过指向派生类的基类指针或引用,访问派生类中同名覆盖成员函数;用形象的语言来解释就是实现以共同的方法,但因个体差异,而采用不同的策略;
- 虚函数用法格式为:virtual 函数返回类型 函数名(参数表) {函数体};
1.2 定义虚函数的限制
- 非类的成员函数不能定义为虚函数,类的成员函数中静态成员函数和构造函数也不能定义为虚函数,但可以将析构函数定义为虚函数。
注:实际上,常常把基类的析构函数定义为虚函数。因为,将基类的析构函数定义为虚函数后,当利用delete删除一个指向派生类定义的对象指针时,系统会调用相应的类的析构函数。而不将析构函数定义为虚函数时,只调用基类的析构函数。 - 只需要在声明函数的类体中使用关键字“virtual”将函数声明为虚函数,而定义函数时不需要使用关键字“virtual”。
- 当将基类中的某一成员函数声明为虚函数后,派生类中的同名函数(函数名相同、参数列表完全一致、返回值类型相关)自动成为虚函数。
- 如果声明了某个成员函数为虚函数,则在该类中不能出现和这个成员函数同名并且返回值、参数个数、类型都相同的非虚函数。在以该类为基类的派生类中,也不能出现这种同名函数。
注:虚函数联系到多态,多态联系到继承。被继承的虚函数仍然是虚函数。没了继承,什么都没得谈。
1.3 调用虚函数的方式
- 下面演示分别用:对象、指针、引用的方式来调用虚函数。
(1)利用类的对象调用虚函数,虚函数没有起到运行时的多态作用;
(2)利用类的对象指针调用虚函数,虚函数起到了运行时的多态作用;
(3)利用类的对象引用调用虚函数,虚函数起到了运行时的多态作用; 代码示例:
#include <iostream> using namespace std; class father { public: father(){} virtual ~father(){} public: virtual void run() const{cout<<"父亲可以跑万米"<<endl;} }; class son : public father { public: son(){} virtual ~son(){} public: virtual void run() const{cout<<"儿子亲可以跑十万米"<<endl;} }; class daughter : public father { public: daughter(){} virtual ~daughter(){} public: virtual void run() const{cout<<"女儿亲可以跑十万米"<<endl;} }; void one(father one);//该函数原型中声明一个指向father类的对象 void two(father* two);//该函数原型中声明一个指向father类的指针 void three(father& three);//该函数原型中声明一个指向father类的引用 int main() { father *p=0; int choice = 0; while(1) { bool quit = false; cout<<"(0)quit(1)father(2)son(3)daughter:"; cin>>choice; switch(choice) { case 0: { quit = true; break; } case 1 : { p = new father; break; } case 2: { p = new son; break; } case 3 : { p = new daughter; break; } default: { cout<<"请输入0~3之间的数字"<<endl; break; } } if(true == quit) { break; } one(*p);//将对象作为参数传递给函数one()中 two(p);//将对象的内存地址传递到函数two()中 three(*p);//将对象的引用传递到函数three()中 } system("pause"); return 0; } void one(father one) { one.run();//函数头中接收一个指向father的对象,用该对象访问run函数 } void two(father* two) { two->run();//函数头中接收一个指向father的指针,用该指针访问run函数 } void three(father& three) { three.run();//函数头中接收一个指向father的引用,用引用访问run函数 } =>(0)quit(1)father(2)son(3)daughter:1 父亲可以跑万米 父亲可以跑万米 父亲可以跑万米 (0)quit(1)father(2)son(3)daughter:2 父亲可以跑万米 儿子亲可以跑十万米 儿子亲可以跑十万米 (0)quit(1)father(2)son(3)daughter:3 父亲可以跑万米 女儿亲可以跑十万米 女儿亲可以跑十万米 (0)quit(1)father(2)son(3)daughter:0
注:
(1)在继承中,只有当使用指针或引用的方式来调用虚函数时,虚函数才能发挥多态性的作用。
(2)只有被说明为虚函数的那个成员函数才具有多态性。
(3)被继承的虚函数仍然是虚函数。
1.4 系统调用虚函数的原理
- 每个对象创建虚函数时,对象都得记录这个虚函数,因此编译器建立了一个叫做T表的虚函数表;
- 每个对象都有一个指向该表的指针,叫做虚表指针,该指针用来指向虚函数表;
- 虚函数表也有一个指针指向该对象,当创建派生类对象的基类部分时,该对象的指针就会自动初始化为指向虚函数表的正确部分,当调用派生类对象的构造函数时,这个对象就会添加到虚函数表中去,并且将指针指向该对象的重载函数;
- 当使用指向基类的指针时,根据对象的实际类型,将该对象的指针继续指向正确的函数;
1.5 解除动态联编
- 在虚函数中使用成员名限定可以强行解除动态联编
代码示例:
#include <iostream> using namespace std; class father { public: father(){} virtual ~father(){} public: virtual void run() const{cout<<"父亲可以跑万米"<<endl;} }; class son : public father { public: son(){} virtual ~son(){} public: virtual void run() const{cout<<"儿子亲可以跑十万米"<<endl;} }; int main() { son s; son *p = &s; p->run(); p->father::run();//成员名限定会强制使用静态联编来调用类father的成员函数 system("pause"); return 0; } =>儿子亲可以跑十万米 父亲可以跑万米
1.6 虚函数实现多态
代码示例:
#include <iostream> using namespace std; class poser { public: poser(int age):_age(age){} virtual ~poser(){} public: virtual void beat() const{cout<<"一般选手一拳的力量为260磅"<<endl;} void jump() const{cout<<"一般选手可以跳1米高"<<endl;} void print()const{cout<<"一般选手的年龄:"<<_age<<endl;} protected: int _age; }; class Ali : public poser { public: Ali(int age):poser(age){} virtual ~Ali(){} public: virtual void beat() const{cout<<"Ali一拳的力量为420磅"<<endl;} void jump() const{cout<<"Ali可以跳1.1米高"<<endl;} void print()const{cout<<"Ali的年龄:"<<_age<<endl;} }; class Lewis : public poser { public: Lewis(int age):poser(age){} virtual ~Lewis(){} public: virtual void beat() const{cout<<"Lewis一拳的力量为480磅"<<endl;} void jump() const{cout<<"Lewis可以跳1.2米高"<<endl;} void print()const{cout<<"Lewis的年龄:"<<_age<<endl;} }; class Tyson : public poser { public: Tyson(int age):poser(age){} virtual ~Tyson(){} public: virtual void beat() const{cout<<"Tyson一拳的力量为500磅"<<endl;} void jump() const{cout<<"Tyson可以跳1.3米高"<<endl;} void print()const{cout<<"Tyson的年龄:"<<_age<<endl;} }; class Holy : public poser { public: Holy(int age):poser(age){} virtual ~Holy(){} public: virtual void beat() const{cout<<"Holy一拳的力量为350磅"<<endl;} void jump() const{cout<<"Holy可以跳1.4米高"<<endl;} void print()const{cout<<"Holy的年龄:"<<_age<<endl;} }; int main() { poser *a[5]; poser *p; int choice = 0; for(int i=0; i<5; i++) { cout<<"(1)Ali(2)Lewis(3)Tyson(4)Holy:"; cin>>choice; switch(choice) { case 1 : { p = new Ali(20); break; } case 2: { p = new Lewis(21); break; } case 3 : { p = new Tyson(22); break; } case 4: { p = new Holy(23); break; } default: { p = new poser(24); break; } } a[i] = p; a[i]->beat(); a[i]->jump(); a[i]->print(); } system("pause"); return 0; } =>(1)Ali(2)Lewis(3)Tyson(4)Holy:1 Ali一拳的力量为420磅 一般选手可以跳1米高 一般选手的年龄:20 (1)Ali(2)Lewis(3)Tyson(4)Holy:2 Lewis一拳的力量为480磅 一般选手可以跳1米高 一般选手的年龄:21 (1)Ali(2)Lewis(3)Tyson(4)Holy:3 Tyson一拳的力量为500磅 一般选手可以跳1米高 一般选手的年龄:22 (1)Ali(2)Lewis(3)Tyson(4)Holy:4 Holy一拳的力量为350磅 一般选手可以跳1米高 一般选手的年龄:23 (1)Ali(2)Lewis(3)Tyson(4)Holy:5 一般选手一拳的力量为260磅 一般选手可以跳1米高 一般选手的年龄:24
注:
(1)因为在函数beat()前面加了关键字virtual,表示该函数是有多种形态的,即该函数可能被多个对象所拥有,而且功能不一,换句话说,多个对象在调用同一名字的函数时产生的效果也不一样。
(2)一个函数被说明为虚函数,在派生类中覆盖了该函数,那么该函数也是个虚函数,不过也可以把它说明为虚函数,这样看起来更好懂些。
(3)由于jump()和print()函数,没有说明为虚函数,因此,调用了父类的对应函数。
参考文献:
[1]《C++全方位学习》范磊——第十三章
[2]《C++程序设计教程(第二版)》钱能——第五章、第六章、第七章
[3]《C++ Primer(第5版)》王刚 杨巨峰——第一章、第六章
[4] 百度搜索关键字:C++函数、虚函数