名称的遮掩可以分成变量的遮掩与函数的遮掩两类,本质都是名字的查找方式导致的,当编译器要去查找一个名字时,它一旦找到一个相符的名字,就不会再往下去找了,因此遮掩本质上是优先查找哪个名字的问题。

而查找是分作用域的,虽然本条款的命名是打着“继承”的旗子来说的,但我觉得其实与继承并不是很有关系,关键是作用域。

举例子说明这个问题会比较好理解。

 //例1:普通变量遮掩
int i = ; int main()
{
int i = ;
cout << i << endl; // 输出4
}

这是一个局部变量遮掩全局变量的例子,编译器在查找名字时,优先查找的是局部变量名,找到了就不会再找,所以不会有warning,不会有error,只会是这个结果。

 //例2:成员变量遮掩
class Base
{
public:
int x;
Base(int _x):x(_x){}
}; class Derived: public Base
{
public:
int x;
Derived(int _x):Base(_x),x(_x + ){}
}; int main()
{
Derived d();
cout << d.x << endl; //输出4
}

因为定义的是子类的对象,所以会优先查找子类独有的作用域,这里已经找到了x,所以不会再查找父类的作用域,因此输出的是4,如果子类里没有另行声明x成员变量,那么才会去查找父类的作用域。那么这种情况下如果想要访问父类的x,怎么办呢?

可以在子类里面添加一个方法:

int GetBaseX() {return Base::x;}

利用Base::x,可以使查找指定为父类的作用域,这样就能返回父类的x的值了。

 //例3:函数的遮掩
class Base
{
public:
void CommonFunction(){cout << "Base::CommonFunction()" << endl;}
void virtual VirtualFunction(){cout << "Base::VirturalFunction()" << endl;}
void virtual PureVirtualFunction() = ;
}; class Derived: public Base
{
public:
void CommonFunction(){cout << "Derived::CommonFunction()" << endl;}
void virtual VirtualFunction(){cout << "Derived::VirturalFunction()" << endl;}
void virtual PureVirtualFunction(){cout << "Derived::PureVirtualFunction()" << endl;}
}; int main()
{
Derived d;
d.CommonFunction(); // Derived::CommonFunction()
d.VirtualFunction(); // Derived::VirtualFunction()
d.PureVirtualFunction(); // Derived::PureVirtualFunction()
return ;
}

与变量遮掩类似,函数名的查找也是先从子类独有的作用域开始查找的,一旦找到,就不再继续找下去了。这里无论是普通函数,虚函数,还是纯虚函数,结果都是输出子类的函数调用。

下面我们来一个难点的例子。

 //例4:重载函数的遮掩
class Base
{
public:
void CommonFunction(){cout << "Base::CommonFunction()" << endl;}
void virtual VirtualFunction(){cout << "Base::VirturalFunction()" << endl;}
void virtual VirtualFunction(int x){cout << "Base::VirtualFunction() With Parms" << endl;}
void virtual PureVirtualFunction() = ;
}; class Derived: public Base
{
public:
void CommonFunction(){cout << "Derived::CommonFunction()" << endl;}
void virtual VirtualFunction(){cout << "Derived::VirturalFunction()" << endl;}
void virtual PureVirtualFunction(){cout << "Derived::PureVirtualFunction()" << endl;}
}; int main()
{
Derived d;
d.VirtualFunction(3); // ?
return ;
}

很多人都会认为输出的是Base::VirtualFunction() With Parms,实际上这段代码却是编译不过的。因为编译器在查找名字时,并没有“为重载而走的很远”,C++的确是支持重载的,编译器在发现函数重载时,会去寻找相同函数名中最为匹配的一个函数(从形参个数,形参类型两个方面考虑,与返回值没有关系),如果大家的匹配程度都差不多,那么编译器会报歧义的错。

但以上法则成立的条件是这些函数位于相同的作用域中,而这里是不同的域!编译器先查找子类独有的域,一旦发现了完全相同的函数名,它就已经不再往父类中找了!在核查函数参数时,发现了没有带整型形参,所以直接报编译错了。

如果去掉子类的VirualFunction(),那么才会找到父类的VirtualFunction(int)。

提醒一下,千万不要被前面的Virtual关键字所误导,你可以试一个普通函数,结果是一样的,只要子类中有同名函数,不管形参是什么,编译器都不会再往父类作用域里面找了。

好,如果现在你非要访问父类里面的方法,那也可以,书上给出了两种作法,一种是采用using声明,另一种是定义转交函数。

 //例5:采用using声明,使查找范围扩大至父类指定的函数:
class Base
{
public:
void CommonFunction(){cout << "Base::CommonFunction()" << endl;}
void virtual VirtualFunction(){cout << "Base::VirturalFunction()" << endl;}
void virtual VirtualFunction(int x){cout << "Base::VirtualFunction() With Parms" << endl;}
void virtual PureVirtualFunction() = ;
}; class Derived: public Base
{
public:
using Base::VirtualFunction; // 第一级查找也要包括Base::VirtualFunction
void CommonFunction(){cout << "Derived::CommonFunction()" << endl;}
void virtual VirtualFunction(){cout << "Derived::VirturalFunction()" << endl;}
void virtual PureVirtualFunction(){cout << "Derived::PureVirtualFunction()" << endl;}
}; int main()
{
Derived d;
d.VirtualFunction(); // 这样没问题了,编译器会把父类作用域里面的函数名VirtualFunciton也纳入第一批查找范围,这样就能发现其实是父类的函数与main中的调用匹配得更好(因为有一个形参),这样会输出Base::VirtualFunction() With Parms
return ;
}

用了using,实际上是告诉编译器,把父类的那个函数也纳入第一批查找范围里面,这样就能发现匹配得更好的重载函数了。

 //例6:使用转交函数强制指定父类的作用域
class Base
{
public:
void CommonFunction(){cout << "Base::CommonFunction()" << endl;}
void virtual VirtualFunction(){cout << "Base::VirturalFunction()" << endl;}
void virtual VirtualFunction(int x){cout << "Base::VirtualFunction() With Parms" << endl;}
void virtual PureVirtualFunction() = ;
}; class Derived: public Base
{
public:
using Base::VirtualFunction;
void CommonFunction(){cout << "Derived::CommonFunction()" << endl;}
void virtual VirtualFunction(){cout << "Derived::VirturalFunction()" << endl;}
void virtual PureVirtualFunction(int x){cout << "Derived::PureVirtualFunction()" << endl;}
void virtual VirtualFunction(int x){Base::VirtualFunction(x)};
}; int main()
{
Derived d;
d.VirtualFunction(); // 输出Base::VirtualFunction() With Parms
return ;
}

采用这种做法,需要在子类中再定义一个带int参的同名函数,在这个函数里面用Base进行作用域的指定,从而调用到父类的同名函数。

快结尾了,这里还是声明一下,在不同作用域内定义相同的名字,无论是发生在变量还是函数身上,都是非常无聊也是不好的做法。除了考试试卷上,还是不要把名字遮掩问题带到任何地方,命个不同的名字真的有那么难吗?

最后总结一下:

1. derived classses内的名称会遮掩base classes内的名称。在public继承下从来没有人希望如此。

2. 为了让被遮掩的名称再见天日,可使用using声明式或转交函数(forwarding functions)。

读书笔记_Effective_C++_条款三十三:避免遮掩继承而来的名称的更多相关文章

  1. 读书笔记_Effective_C++_条款三十:了解inline的里里外外

    学过基本程序课的同学都知道,inline是内联的关键字,它可以建议编译器将函数的每一个调用都用函数本体替换.这是一种以空间换时间的做法.把每一次调用都用本体替换,无疑会使代码膨胀,但可以节省函数调用的 ...

  2. 读书笔记_Effective_C++_条款三十六:绝不重新定义继承而来的non-virtual函数

    这个条款的内容很简单,见下面的示例: class BaseClass { public: void NonVirtualFunction() { cout << "BaseCla ...

  3. 读书笔记_Effective_C++_条款三十九:明智而审慎地使用private继承

    private继承的意义在于“be implemented in turns of”,这个与上一条款中说的复合模型的第二层含义是相同的,这也意味着通常我们可以在这两种设计方法之间转换,但书上还是更提倡 ...

  4. 读书笔记_Effective_C++_条款三十四:区分接口继承和实现继承

    这个条款书上内容说的篇幅比较多,但其实思想并不复杂.只要能理解三句话即可,第一句话是:纯虚函数只继承接口:第二句话是:虚函数既继承接口,也提供了一份默认实现:第三句话是:普通函数既继承接口,也强制继承 ...

  5. 读书笔记_Effective_C++_条款三十二:确定你的public继承继承塑模出is-a关系

    这一条款是说的是公有继承的逻辑,如果使用继承,而且继承是公有继承的话,一定要确保子类是一种父类(is-a关系).这种逻辑可能与生活中的常理不相符,比如企鹅是生蛋的,所有企鹅是鸟类的一种,直观来看,我们 ...

  6. 读书笔记_Effective_C++_条款四十三:学习处理模板化基类的名称

    背景是这样的,有两个不同的公司,然后想设计一个MessageSender,为这两个公司发送不同的消息,既支持明文发送SendClearText,也支持密文发送SendEncryptedText.一种思 ...

  7. 读书笔记_Effective_C++_条款三十七:绝不重新定义继承而来的缺省参数值

    先看下面的例子: enum MyColor { RED, GREEN, BLUE, }; class Shape { public: ; }; class Rectangle: public Shap ...

  8. 读书笔记_Effective_C++_条款二十三:宁以non-member、non-friend替换member函数

    有下面一种情况 class A { private: int a; int b; public: A(int x, int y) :a(x), b(y){} void a_display(){ cou ...

  9. 读书笔记_Effective_C++_条款三:尽可能使用const

    const是常量的意思,它可以定义一个不可改变的量,主要用于以下几个地方: 1. 修饰变量,使之不可改变 举个例子: const int var = 3; 此时var的值就不能改变了.也正是因为con ...

随机推荐

  1. linux强制踢掉登录用户【转】

    [root@Wang ~]# w :: up :, users, load average: 0.71, 0.58, 0.57 USER TTY FROM LOGIN@ IDLE JCPU PCPU ...

  2. 「要买车网」免费获取汽车电商要买车网购车优惠券 - 持续更新(2016-03-12)www.fortunelab.cn

    汽车电商要买车网简介 “要买车”(www.yaomaiche.com)网站是上海运图投资有限公司旗下网站,是首家真正打通交易闭环的汽车电商网站,由中国电子商务成功探索者——卜广齐于2014年10月在上 ...

  3. js自定义鼠标右键菜单

    document.oncontextmenu = function(e) { return false; } document.onmousedown = function(e) { switch(e ...

  4. day04作业

    1.for(初始化表达式:条件表达式:循环后的操作表达式){ 循环体: } class Test_Sum { public static void main(String[] args) { int ...

  5. C++类指针类型的成员变量的浅复制与深复制

    本篇文章旨在阐述C++类的构造,拷贝构造,析构机制,以及指针成员变量指针悬空问题的解决.需要读者有较好的C++基础,熟悉引用,const的相关知识. 引言: 类作为C++语言的一种数据类型,是对C语言 ...

  6. Gitflow工作流

    什么是Gitflow工作流 Gitflow工作流定义了一个围绕项目发布的严格分支模型.虽然比功能分支工作流复杂几分,但提供了用于一个健壮的用于管理大型项目的框架. Gitflow工作流没有用超出功能分 ...

  7. 20165301 2017-2018-2《Java程序设计》课程总结

    20165301 2017-2018-2<Java程序设计>课程总结 每周作业链接汇总 预备作业1:我期待的师生关系 预备作业2:学习基础与c语言学习心得 预备作业3: Linux安装及命 ...

  8. Java事务管理之JDBC

    前言 关于Java中JDBC的一些使用可以参见: Java 中使用JDBC连接数据库例程与注意事项 在使用JDBC的使用, 如何进行事务的管理.直接看一下代码 示例代码 /** * @Title: J ...

  9. IDEA简单部署MVC项目

    本文章主要参考七小栈主的文章,特此说明: 前提 在配置好JDK,配置好Maven,配置链接如下:如何配置Java环境,包括JDK,Maven等 建项目 IEDA中File->New->Pr ...

  10. svn错误 svnserve.conf:12: Option expected解决办法

    经常有新手配置基于svnserve的subversion服务器后,在客户端访问subversion版本库时出现这个错误:svnserve.conf:12: Option expected为什么会出现这 ...