一.结论
二.详细说明
为了说明以上的结论,首先需要说明隐藏、覆盖以及作用域等概念,接着举例说明隐藏、覆盖以及作用域等带来的问题,最后以解决方法解决以上问题。
2.1 隐藏、覆盖和作用域
作用域:从字面上理解,某事物起作用的地方,比如说白天太阳能够照射的地方,能看到太阳,而照不到的地方就看不到太阳,能够看到太阳的地方就是太阳的作用域。比较官方的定义如下:
作用域:程序设计概念,通常来说,一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域 —百度百科
隐藏:从字面上理解就是,因为某事物的到来,一些事物被隐藏起来了,但是可以通过一些办法让它显现出来,比如还以上面的例子,太阳照射不到的地方通过镜子将阳关反射到被隐藏事物的地方(转调),隐藏的事物亦可以看到太阳了。官方的定义:
派生类将继承的基类的同名的成员变量和成员方法隐藏起来,通过派生类只能访问到自己的成员变量和成员方法。
覆盖: 从字面上理解,某些事物因为有冲突,在一些作用域下不能同时出现,在某一片阳光下,要么只能出现事物A,要么只能出现事物B。
覆盖往往与继承的类中有virtual修饰的函数有关。virtual修饰的函数为虚函数。而覆盖就是基类中有virtual修饰的函数,且在派生类中有与基类中的虚函数同名且同参数列表的函数,那么派生类中的的该函数就会将基类中的函数覆盖,调用时无法调用基类中的函数
2.2.局部变量和全局变量选择
int val = 1; // global valuevoid main(){ double val = 2.5; // local value std::cout << val << std::endl; // out 2.5 std::cout << ::val << std::endl; // out 1}
运行结果:
2.51
当编译器在cout处输出val时,将首先查找 TestFun 的作用域中是否有名字 val ,如果有则它将直接输出这个变量的值,如果没有则会向TestFun之外的地方查找val符号,找到则直接输出。此处输出2.5,局部的val变量覆盖了全局的val变量。如果要想使用全局变量val,需要添加作用域::val。
此处局部变量掩盖全局变量,若想使用全局变量可添加作用域的形式使用
2.3. 继承时变量选择
有如下类继承关系,基类中包含纯虚函数、虚函数和非虚函数,派生类公有继承基类,包含基类中非虚函数的同名同参函数和独有函数,如下:
class Base {private: int val; public: virtual void mFun1() = 0; virtual void mFun1(double) { std::cout << "Base mFun1 with param double" << std::endl; } virtual void mFun2() { std::cout << "Base mFun2" << std::endl; } void mFun3() { std::cout << "Base mFun3" << std::endl; } void mFun3(float) { std::cout << "Base mFun3 with param float" << std::endl; }};class Derived: public Base {public: virtual void mFun1() { std::cout << "Derieved mFun1" << std::endl; } void mFun3() { std::cout << "Derieved mFun3" << std::endl; mFun2(); } void mFun4() { std::cout << "Derieved mFun4" << std::endl; mFun3(); // mFun3(1.25); // no matching function }};
1.在构建派生类实例时,调用同名同参的非虚函数mFun3时,其搜索路径是,先搜索mFun3内部是否找到mFun2,如果没有找到则在类Derived中查找mFun2,如果类内有找到则继续向上Base类中查找mFun2,如果没有找到则继续向本文件中查找mFun2等等。此处mFun2在Base类中找到,如下例子:
int main() { Derived der; der.mFun3(); return 0;}
运行结果:
Derieved mFun3Base mFun2
2.在构建派生类实例时,调用函数mFun4时,mFun4中现调用的mFun3只会调用派生类Derived中的mFun3,且无法调用带参数的mFun3,否则编译时会报no matching function错误,实例中调用带参的mFun3一样报错,因为派生类中的函数覆盖了基类中的同名函数包括不同参数,换句话说派生类中的同名函数覆盖了了Base::mFun1和Base::mFun3。 如下例子:
int main() { Derived der; der.mFun1(); // call Derived mFun1 der.mFun4(); // call Derived mFun4 // der.mFun3(1.25); // no matching function return 0;}
运行结果
Derieved mFun4Derieved mFun3Base mFun2
从上面的两个例子中可以发现一个很明显的问题,由于同名覆盖的情况,导致在派生类中无法调用基类中同名不同参数的函数, 以下就是一些解决办法
2.4 使用using声明
使用using 声明可以将作用域从原先限定的地方拓展到声明的地方,增加其使用范围,例子如下:
// Base基类同上class Derived: public Base {public: using Base::mFun3; virtual void mFun1() { std::cout << "Derieved mFun1" << std::endl; } void mFun3() { std::cout << "Derieved mFun3" << std::endl; mFun2(); } void mFun4() { std::cout << "Derieved mFun4" << std::endl; mFun3(); // mFun3(1.25); // no matching function }};
使用Demo进行测试:
int main() { Derived der; der.mFun1(); // call Derived mFun1 der.mFun1(1.0); // call Base mFun1 der.mFun4(); // call Derived mFun4 der.mFun3(); // call Derived mFun3 der.mFun3(1.25); // call Base mFun3 return 0;}
运行结果:
Derieved mFun1 // der.mFun1();Base mFun1 with param double // der.mFun1(1.0);Derieved mFun4 // der.mFun4();Derieved mFun3Base mFun2Derieved mFun3 // der.mFun3();Base mFun2Base mFun3 with param float // der.mFun3(1.25);
从结果中可以看出使用using声明将基类中的函数作用域拓展到了派生类中,在派生类中可以直接使用,假如派生类中只想继承基类中不带参数的函数该如何呢?这时使用using声明可能会将带参数的函数同时给引入进来,而无法解决此类问题。此处可以通过转调函数实现,如下。
2.5 使用转掉函数
私有继承加上转调函数可以完美解决using声明不能解决的只继承带不参数的函数,例子如下:
// Base基类同上void Base::mFun1(){ std::cout << "Base mFun1" << std::endl;}class Derived: private Base {public: virtual void mFun1() { std::cout << "Derieved mFun1" << std::endl; Base::mFun1(); }};
使用Demo进行测试
int main() { Derived der; der.mFun1(); // call Derived mFun1 // der.mFun1(1.0); // error no matching function return 0;}
运行结果:
Derieved mFun1Base mFun1
参考:
1.Effective C++ Item33
2.解析C++隐藏与覆盖Oragen的博客-CSDN博客c++隐藏和覆盖
3.作用域_百度百科 (baidu.com)
备注:
欢迎大家多多指教,喜欢的大家可以点击关注本账号或者微信公众号:软件系统安全,后续将不定期更新C++相关的内容