您的位置:首页 > 编程语言 > C语言/C++

C++中函数重载、隐藏、覆盖和重写的区别

2015-11-12 23:48 399 查看
代码编译运行环境:VS2012+Debug+Win32

1.函数重载(Function Overload)

1.1定义

C++规定在同一作用域中,同名函数的形式参数(指参数的个数、类型或者顺序)不同时,构成函数重载。

1.2用法

比如,要从两个变量中返回其中较大的一个值,可以编写如下两个构成重载的函数。

int max(int a,int b){
return a>b?a:b;
};

double max(double a,double b){
return a>b?a:b;
}


1.3注意事项

(1)函数返回值类型与构成函数重载无任何关系;

(2)类的静态成员函数与普通成员函数可以形成重载;

(3)函数重载发生在同一作用域,如类成员函数之间的重载、全局函数之间的重载。

2.函数隐藏(Function Hiding)

2.1定义

函数隐藏指不同作用域中定义的同名函数构成函数隐藏(不要求函数返回值和函数参数类型相同)。比如派生类成员函数屏蔽与其同名的基类成员函数、类成员函数屏蔽全局外部函数。请注意,如果在派生类中存在与基类虚函数同返回值、同名且同形参的函数,则构成函数重写。

2.2用法用例

请仔细研读以下代码。

#include <iostream>
using namespace std;

void func(char* s){
cout<<"global function with name:"<<s<<endl;
}

class A{
void func(){
cout<<"member function of A"<<endl;
}
public:
void useFunc(){
//func("lvlv");//A::func()将外部函数func(char*)隐藏
func();
::func("lvlv");
}
virtual void print(){
cout<<"A's print"<<endl;
}
};

class B:public A{
public:
void useFunc(){          //隐藏A::vodi useFunc()
cout<<"B's useFunc"<<endl;
}
int useFunc(int i){      //隐藏A::vodi useFunc()
cout<<"In B's useFunc(),i="<<i<<endl;
return 0;
}
virtual int print(char* a){
cout<<"B's print:"<<a<<endl;
return 1;
}

//下面编译不通过,因为对父类虚函数重写时,需要函数返回值类型,函数名称和参数类型全部相同才行
// virtual int print(){
// cout<<"B's print:"<<a<<endl;
// }
};

int main(){
A a;
a.useFunc();
B b;
b.useFunc();//A::useFunc()被B::useFunc()隐藏
b.A::useFunc();
b.useFunc(2);
//b.print();//编译出错,A::print()被B::print(char* a)隐藏
b.A::print();
b.print("jf");
}


程序执行结果:



2.3注意事项

对比函数隐藏与函数重载的定义可知:

(1)派生类成员函数与基类成员函数同名但参数不同。此时基类成员函数将被隐藏(注意别与重载混淆,重载发生在同一个类中);

(2)函数重载发生在同一作用域,函数隐藏发生在不同作用域。

3.函数覆盖与函数重写(Function Override)

网上和很多书籍多都会涉及函数覆盖的概念,众说纷纭,加大了许多初学者的学习难度,甚至产生误导。事实上,函数覆盖就是函数重写。

3.1定义

派生类中与基类同返回值类型、同名和同参数的虚函数重定义,构成虚函数覆盖,也叫虚函数重写。

关于返回值类型存在一种特殊情况,即协变返回类型(covariant return type)。

3.2虚函数重写与协变返回类型

如果虚函数函数返回指针或者引用时(不包括value语义),子类中重写的函数返回的指针或者引用是父类中被重写函数所返回指针或引用的子类型(这就是所谓的协变返回类型)[4]。看示例代码:

#include <iostream>
using namespace std;

class A{};
class B:public A{};

class Base{
public:
virtual A& show(){
cout<<"In Base"<<endl;
return *(new A);
}
};

class Derived:public Base{
public:
//返回值协变,构成虚函数重写
B& show(){
cout<<"In Derived"<<endl;
return *(new B);
}
};


3.3注意事项

(1)函数覆盖就是虚函数重写,而不是函数被”覆盖”。

从上面的代码可以看出,函数是不可能被“覆盖”的。有些人可能会错误地认为函数覆盖会导致函数被”覆盖”而”消失”,将不能被访问,事实上只要通过作用域运算符::就可以访问到被覆盖的函数。因此,不存在被”覆盖“的函数。

(2)函数覆盖是函数隐藏的特殊情况。

对比函数覆盖和函数隐藏的定义,不难发现函数覆盖其实是函数隐藏的特例。

如果派生类中定义了一个与基类虚函数同名但参数列表不同的非virtual函数,则此函数是一个普通成员函数(非虚函数),并形成对基类中同名虚函数的隐藏,而非虚函数覆盖(重写)。

《C++高级进阶教程》中认为函数的隐藏与覆盖是两个不同的概念。隐藏是一个静态概念,它代表了标识符之间的一种屏蔽现象,而覆盖则是为了实现动态联编,是一个动态概念。但隐藏和覆盖也有联系:形成覆盖的两个函数之间一定形成隐藏。例如,可以对虚函数采用“实调用”,即尽管被调用的是虚函数,但是被调用函数的地址还是在编译阶段静态确定的,那么派生类中的虚函数仍然形成对基类中虚函数的同名隐藏。

参考如下代码,考察虚函数的实调用和虚调用。

#include <iostream>
using namespace std;

class Base{
public:
virtual void show(){
cout<<"In Base"<<endl;
}
};

class Derived:public Base{
public:
void show(){
cout<<"In Derived"<<endl;
}
};

int main(){
Base b;
b.show();
Derived d;
d.show();          //对函数show()的实调用
d.Base::show();    //对函数show()的实调用
Base *pb=NULL;
pb=&d;
pb->show();        //对函数show()的虚调用
pb->Base::show();  //对函数show()的实调用
}


程序运行结果:

In Base

In Derived

In Base

In Derived

In Base

4.总结

在讨论相关概念的区别时,抓住定义才能区别开来。C++中函数重载隐藏和覆盖的区别,并不难,难就难在没弄清定义,被网上各种说法弄的云里雾里而又没有自己的理解。

在这里,牢记以下几点,就可区分函数重载、函数隐藏、函数覆盖和函数重写的区别:

(1)函数重载发生在相同作用域;

(2)函数隐藏发生在不同作用域;

(3)函数覆盖就是函数重写。准确地叫作虚函数覆盖和虚函数重写,也是函数隐藏的特例。

关于三者的对比,李健老师在《编写高质量代码:改善C++程序的150个建议》给出了较为详细的总结,如下表所示:

三者作用域有无virtual函数名形参列表返回值类型
重载相同可有可无相同不同可同可不同
隐藏不同可有可无相同可同可不同可同可不同
重写不同相同相同相同(协变)
本文为个人观点,欢迎留言批评指正。

参考文献

[1]陈刚.C++高级进阶教程[M].第一版.武汉:武汉大学出版社,2008:110-P112

[2]百度百科.函数隐藏

[3]李健.编写高质量代码:改善C++程序的150个建议.第一版.北京:机械工业出版社,2012.1:122-125

[4]C++基础:函数重写(override)与协变返回类型(covariant return type)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: