C++ 中的重载,隐藏与重写
首先, 这里重载指 overwrite, 即普通的函数重载, 重写指的是 override, 即虚函数的重写,隐藏:这里特指派生类成员隐藏基类成员的情况
一、重载
说到重载首先得说名字查找, C++在处理函数调用的方式是这样的: 首先根据函数的名字在当前作用于进行名字查找,如果没找到,则往上层作用域接着找,直到找到为止,但是对于函数参数类型为类类型时,还会将该类所在的命名空间加入查找的范围。
这里要说的重点是,一旦编译器在某个作用域找到了该名字,就不会往上继续找了,比如如下代码:
namespace out { void f(int) {} namespace in { void f(int, int) {} void g () { f(1); } //编译错误,参数不匹配 } }
这里在定义 g() 时,调用了外层作用域的 f (int) ,然而编译器在内层作用域就找到了 f 这个名字,于是就看不见外层作用域的重载了。所以除非我们显式的指定作用域名字或者使用using声明,否则会一直报错。
二、隐藏
其实上面的情况已经说明了内层作用域会"隐藏" (这里的隐藏并不是指派生类隐藏基类成员的情况) 外层作用域的名字这一情况了,为了避免这种情况发生,我们通常使用using 声明来声明外层作用域中的其他重载,如:
namespace out { void f(int) { cout << "out" << endl; } namespace in { using out::f; void f(int, int) {} void f(int) { cout << "in" << endl; } void g () { f(1); } //出错,对f(1)的调用有二义性 } } int main() { out::in::g(); return 0; }
由于引入了外层的 f 的同时,内层定义的重载和外层的 f 的函数签名重合了,所以调用二义性。但是,如果 f 分别是子类和基类的成员函数, 则不会产生二义性调用,因为派生类会隐藏基类的成员。
1) 先看第一种情况,我们知道派生类的作用域是处于基类的包围之中的,这里就重现一下类版本的"隐藏"(其实这里的隐藏并不算类中的隐藏) :
void f() { cout << "global: f()" << endl; } //全局作用域的 f class base { public: void f () { cout << "base: f() " << endl; } }; class drived : public base { public: void f(int, int) { cout << "drived: f(int, int) " << endl; } // 这里"隐藏"了基类的 f 这个名字,所以接下来派生类将看不见基类的任何f的重载 void g() { f(); } //由于基类的 f 这个名字被隐藏了,而派生类中有 f 这个名字,所以名字查找在派生类作用域就停止了, 所以发生编译错误:找不到匹配的函数调用 };
2) 上面说到,为了避免这种情况,使用using 声明, 但是这样可能引入签名相同的两个函数,我们来看看类类型中的这种情况:
void x(); void f() { cout << "global: f()" << endl; } //全局作用域的 f class base { public: void f () { cout << "base: f() " << endl; } void f (int, int, int) { cout << "base: f(int, int, int);" << endl; } }; class drived : public base { friend void x(); //将x声明为友元 public: using base::f; //这里其实还隐藏一层含义:由于 using 声明在public 下面,所以基类的 f 是public的,如果using 声明在 private下,则基类的 f 会变成私有的 private: void f() { cout << "drived: f()" << endl; } //将 f 声明为私有的, 由于使用了using 声明,只隐藏了base::f()这一个重载版本 void f(int, int) { cout << "drived: f(int, int) " << endl; } void g() { f(); } //也正是由于隐藏了base::f(), 没有出现调用二义性 }; void x() { drived d; d.f(); //输出 drived f, 基类的 f() 被隐藏 d.g(); //输出 drived f, base::f() 被隐藏 } int main() { drived d; // d.f(); // 错误,派生类隐藏了base::f(), drived::f() 是私有的,除非显式的调用 d.base::f(); d.f(1,1,1); // 由于子类只隐藏了base::f(), 所以调用正确, 但是如果把using 声明置于private下,则依旧会缺少访问权限 x(); return 0; }
总结一下就是:
1, 在类中,隐藏的只是某个固定的重载版本,隐藏所有的重载的情况是由于C++名字查找的机制所导致的
2,可以使用using 声明来避免由于名字查找而丢失的基类的函数重载候选集,同时using声明的位置决定了引入的名字是public还是private
三、重写
这里就没有太多可以讲的了,就是只有返回值,参数类型和个数,名字,以及virtual标记全部都满足的时候才是重写,其他一律按照重载和隐藏处理就可以了,注意如果返回的是实际类型的指针的话,只要指针类型可以转换,是允许不同的。
如 base 的虚函数返回 base*, drived 返回 dirved*, 只要 drived 向 base 的转换是允许的(也就是非私有继承啦,魂淡), 这种返回值的不同也是被允许的。
浙公网安备 33010602011771号