在C++语言中,虚函数是非常重要的概念,虚函数是实现C++面向对象中多态性和继承性的基石。而多态性和继承性则是面向对象语言的精髓。掌握虚函数才算是真正掌握C++语言,而C++语言中虚函数的继承覆盖与函数重载有些类似,很多初学者搞不清他们之间的关系。
首先要明确覆盖(override)与重载(overload)的定义,区别出什么是覆盖和重载:
覆盖就是派生类中虚成员函数覆盖基类中同名且参数相同的成员函数。
如下例:
class CBase { public: CBase(); virtual void Walk(){cout<<"CBase:Walk"<<endl;} virtual void Jump(){cout<<"CBase:Jump"<<endl;} }; class CDerivedA : public CBase { public: CDerivedA(); void Walk(){cout<<"CDerivedA:Walk"<<endl;} void Jump(){cout<<"CDerivedA:Jump"<<endl;} }; int main() { CBase *pTmp1=new CDerivedA ; pTmp1->Walk(); return 0; }
上例中,CDerived和CBase之间就是函数覆盖关系,Walk和Jump函数被派生类覆盖,输出肯定是派生类函数的输出。下面就这几者关系着重学习。再看看下例:
class CBase { public: CBase(); virtual void Walk(){cout<<"CBase:Walk"<<endl;} virtual void Jump(){cout<<"CBase:Jump"<<endl;} void Run(int speed){cout<<"CBase:Run:"<<"Speed="<<speed<<endl;} }; class CDerivedA : public CBase { public: CDerivedA(); void Walk(){cout<<"CDerivedA:Walk"<<endl;} void Jump(){cout<<"CDerivedA:Jump"<<endl;} void Run(int speed) {cout<<"CDerivedA:Run"<<"Speed="<<speed<<endl;} }; int main() { CBase *pTmp1=new CDerivedA ; pTmp1->Walk(); pTmp1->Run(20); return 0; }
这里基类中Run与派生类中Run函数就是重载(overload)关系,当使用基类来调用派生类对象中重载函数,会根据其作用域来判断,如基类CBase,其作用域就是CBase这个类的大括号包含的内容,派生类CDeriverdA其作用域就是CDerivedA大括号中包含的内容。因此在使用基类调用派生类对象中的Run方法时,会执行基类的Run方法。
关于作用域可见性规则(摘于网上:http://www.51laifu.cn/archives/2012/610/article-31092.html)如下:
如果存在两个或多个具有包含关系的作用域,外层声明了一个标识符,而内层没有再次声明同名标识符,那么外层标识符在内层依然可见,如果在内层声明了同名标识符,则外层标识符在内层不可见,这时称内层标识符隐藏了外层同名标识符,这种现象称为隐藏规则。
在类的派生层次结构中,基类的成员和派生类新增的成员都具有类作用域。二者的作用范围不同,是相互包含的两个层,派生类在内层。这时,如果派生类声明了一个和某个基类成员同名的新成员,派生的新成员就隐藏了外层同名成员,直接使用成员名只能访问到派生类的成员。如果派生类中声明了与基类同名的新函数,即使函数的参数表不同,从基类继承的同名函数的所有重载形式也都被隐藏。如果要访问被隐藏的成员,就需要使用类作用域分辨符和基类名来限定。
根据上面的解释,CBase类处于作用域的外层,因此派生类的方法对于其将是不可见的,即隐藏的。而在派生类中,如果有重载函数时,基类函数将会被隐藏,否则基类函数就不被隐藏。
函数重载和虚函数都能实现对象的多态特性,但是函数重载只能实现静态的多态,也就是在编译时多态,即编译器会给重载的函数在编译时加上不同的前缀,是函数名在运行时不同。而虚函数可以实现动态多态,即运行时多态,但是虚函数实现需要增加额外的虚函数指针等,会增加相应空间。
再看下例:
class CBase { public: CBase(); virtual void Walk(){cout<<"CBase:Walk"<<endl;} virtual void Jump(){cout<<"CBase:Jump"<<endl;} void Run(int speed){cout<<"CBase:Run:"<<"Speed="<<speed<<endl;} }; class CDerivedA : public CBase { public: CDerivedA(); void Walk(){cout<<"CDerivedA:Walk"<<endl;} void Jump(){cout<<"CDerivedA:Jump"<<endl;} void Run(int speed) {cout<<"CDerivedA:Run"<<"Speed="<<speed<<endl;} void Run(int speed,int direction){cout<<"CDerivedA:Run"<<"Speed="<<speed<<";Direction="<<direction<<endl;} }; int main() { CBase *pTmp1=new CDerivedA ; pTmp1->Walk(); pTmp1->Run(20); CDerivedA *pTmp2=(CDerivedA*)pTmp1 ; pTmp2->Run(20,3);//合法的 //pTmp1->Run(20,3);//不合法 return 0; }
以上代码就可以看出,派生类中的重载方法,在基类是不能调用的,编译的时候就会出错。