C++函数重载、隐藏与覆盖重写的精通指南
前言
对于C++函数而言,多个函数如果同名会有很多有意思的事情,从声明的作用域来看,在横向上同一个可访问作用域里面的同名函数可以进行重载;而纵向上作用域对于父子继承的派生类来说,同样的函数名称可以实现隐藏与覆盖。(如果基类成员函数是虚函数,可以基于虚函数实现多态,进行动态联编)下面就详细介绍下函数的重载、隐藏与覆盖重写。
1 函数重载
- 定义:
C++规定在同一作用域中,例如一个类的成员函数之间,多个函数的名称相同,但是各个函数的形式参数(指参数的个数、类型或者顺序)不同时,构成函数重载。
- 代码示例
int test(int a); int test(int a, double b); int test(double b, int a); int test(int a, const char ** c); void test(int a, const char ** c); // 非重载,一起编译会提示错误,仅仅返回值不同编译无法区分使用的是那个重载函数
- 总结
- 前提:函数名称相同,即要求是同名函数;
- 重载作用域:函数重载发生在横向水平的同一作用域,例如一个类成员函数之间的重载、全局函数之间的重载;
- 重载类型:无论是类的静态成员函数,还是类的普通成员函数,亦或是普通的函数,都可以形成重载;
- 重载要素:函数返回值类型函数重载无任何关系,仅仅返回值不同,形参相同的情况,会被禁止重载;
2 函数隐藏
- 定义
函数隐藏是说,在不同作用域中,定义的同名函数构成函数隐藏(仅仅要求函数名称相同,对于返回值和形式参数不做更多要求,并且对于是否是虚函数也不做要求)。例如派生类同名成员函数屏蔽与其基类的同名成员函数,以及屏蔽同名全局外部函数。(经常有人隐藏和覆盖重写弄混,所以提前说下,如果在派生类中存在与基类同名的虚函数,并且返回值、形参都相同,则构成函数重写)。
- 代码示例
#include <iostream> using namespace std; class Parent { public: void test(int a) { cout<<"this is Parent"<<endl; } }; class Son: public Parent { public: void test(int a) { cout<<"this is Son hide Parent function"<<endl; } }; int main(int argc, char ** argv) { Son son; son.test(1); return 0; }
? 输出如下
root@localhost override [master] $ g++ --std=c++11 test_hide.cpp root@localhost override [master] $ ./a.out this is Son hide Parent function
- 总结
- 前提:函数名称相同,即要求是同名函数;
- 作用域:不在同一个横向的作用域(分别位于派生类与基类的纵向作用域);
- 要素:返回类型可同可不同,参数亦可同可不同;
- 虚函数:
参数不同,此时无论有无virtual关键字,基类的函数将被隐藏;
参数相同的情况下此时基类函数无virtual则属于函数隐藏,后续无法继续基于此利用这个函数的多态性;
如果是virtual则属于函数重写,继续多态性的保留;
3 函数重写
- 定义
函数的覆盖和重写是一个意思的两个叫法,同时他的作用域也和函数隐藏相同,其实可以这么看,函数覆盖和函数隐藏共同构建了在具有集成关系的纵向作用域里面的同名函数的不同衍变,只不过函数覆盖的条件更加严格些。
在介绍函数隐藏的时候,为了弄清楚函数隐藏与覆盖重写,也简单描述了函数覆盖。这里再进一步进行描述下:派生类中与基类中,同名函数的返回值类型、参数的都相同,并且基类中定义为虚函数的情况下,构成虚函数覆盖,也叫虚函数重写。
- 代码示例
#include <iostream> using namespace std; class Parent { public: virtual void test(int a) { cout<<"this is Parent"<<endl; } }; class Son: public Parent { public: void test(int a) { cout<<"this is Son Override Parent function"<<endl; } }; int main(int argc, char ** argv) { Son son; son.test(1); return 0; }
输出如下:
root@localhost override [master] $ g++ --std=c++11 test_override2.cpp root@localhost override [master] $ ./a.out this is Son Override Parent function
附:令人迷惑的隐藏规则
C++的隐藏规则使问题复杂性陡然增加,这里“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。
总结
咋一看,感觉重写的功能基于隐藏是都可以实现,那么为什么要区分重写和隐藏呢?其实这是C++语言层面的问题了,C++基于virtual函数实现了多态性,并且可以进行动态联编,但是隐藏其实是破坏了这种多态性,也就是说父类成员函数的virtual性,在被子类成员函数的隐藏破坏后,无法传递给孙子类了,所以还需要重写来遗产的家族传递。
- 前提:函数名称相同,即要求是同名函数;
- 作用域:不在同一个横向的作用域(分别位于派生类与基类的纵向作用域);
- 要素:返回类型可同可不同,参数亦可同可不同;
- 是否虚函数:前提是虚函数,并且参数相同;