8.1.1. 派生类与作用域
问题:假设基类B有一个成员函数名为foo,然后B的派生类D也定义了一个同名的foo函数,事情会怎样?
- class B
- {
- public:
- void foo() { cout << "B::foo" << endl; }
- };
- class D : public B
- {
- public:
- void foo() { cout << "D::foo" << endl; }
- };
注意:D::foo和B::foo同名,并且它们都是类的公开成员。如果我们写这样的代码:
- void test()
- {
- D d;
- d.foo(); // <-- 这里调用的是哪个类的foo?
- }
这问题简单,确定一定以及肯定……这里调用的是派生类,也就是D类的foo成员函数。还记得在《感受篇》里:“美人类/Beauty”派生自“普通人类(Person)”,然后可以拥有美人特定的“自我介绍函数/Introduction”过程——这背后反应的逻辑是自然而然的:派生类可以改掉基类的某个过程,但如果代码是这样:
- class D : public B
- {
- private: // <- 注意这里,foo在派生类里变成私有的
- void foo() { cout << "D::foo" << endl; }
- };
基类定义没有变,但派生类中,foo函数被改成私有,此时,若还是这样调用:
- void test()
- {
- D d;
- d.foo(); // <- 会怎样?
- }
结果是编译不通过,因为foo在D中是私有的。此时, 尽管在基类里,就有一个公开着的同名foo函数,编译器也会对派生类“从一而终”,决不改弦更张换成对基类同名函数的调用(除非主动要求就要用基类的那个foo)。
〖轻松一刻〗:父亲的狗,儿子的狗,重名了……
这可以打个(又是粗俗)的比喻:
故事背景:父亲(基类)有一只名为“黑太狼”的小狗,并且父亲说,这只狗对外公开(谁都可以借);儿子(派生类)继承了老爸。这时来了一个儿子的客人……
情况一:儿子自己根本没有养狗(或者有养,但不叫黑太狼)。客人说:“我想用你那只叫黑太狼的狗”。儿子想:“我又没有这样的狗,噢,对了,老爸那有一只”。于是乎儿子把他从父亲继承而来黑太狼,借给客人一用。
情况二:儿子自己也养了一只狗,并且也把它取名为“黑太狼”。客人说:“我想用你那只叫黑太狼的狗”。儿子很乐意地说:好的,我的这只黑太郎也是public的,你拿走吧!(父亲的黑太郎在一边偷偷地哭泣)。
情况三:儿子自己也养了一只狗,并且也把它取名为“黑太狼”。客人说:“我想用你那只叫黑太狼的狗”。可是儿子认为狗狗是私有的,不应外借,于是乎,儿子道声歉(编译出错信息),拒了客人走掉。父亲的的黑太狼暗自悲伤:“有了新欢也就罢了,偏还要把它和我取一样的名!你是当我死了吗?你是当我死了吗?”
情况四:儿子自己也养了一只狗,并且也把它取名为“黑太狼”。但客人说:“我想借你爸爸那只叫‘黑太狼’的狗狗”。儿子点点头,父亲的“黑太狼”热泪盈眶:“这样的夜晚你是这样特意想起我了”,于是乎它高高兴兴地出场。
当一个派生类搞了一个和基类中存在同名的成员,这个事件被称为:“派生过程的名字覆盖”。事件的影响是:在派生类的世界里,基的那个同名成员突然失效了;哪怕其实基类的那个成员有着比派生类同名成员更宽松的访问权限;哪怕……
没错,还有一个哪怕!并且这个哪怕显然比前一个哪怕还要令人觉得“哪怕”!翠花,上代码:
- class B
- {
- public:
- void foo() { cout << "B::foo" << endl; }
- };
- class D : public B
- {
- private:
- int foo(int a) // <- foo在派生类里需要入参
- { return a + a; }
- void test()
- {
- D d;
- d.foo(); // <- 会怎样?
- }
现在情况是:派生类D中,虽然也有一个同名的foo函数,可以它却长得基类的那个同名函数完全不一样。然后客人(test函数)来了,它想调用基类的那个foo函数——为什么我能猜出它要的是基类的?因为它没有给foo写任何入参嘛……
〖轻松一刻〗:父亲的狗,儿子的鹦鹉,重名了……
这回是:父亲有一只叫“黑太郎”的狗狗,儿子有一只叫“黑太郎”的……鹦鹉!来了一个客人,手上拿着狗粮,说“哎,把你家那只‘黑太郎’借我一下,你看,我连狗粮都带来了……”不料儿子道歉一声:“不好意思,你所传入的数据,和我家黑太狼所要的入参,不匹配!”
这是一种严谨的做法,主要用以预防因为派生类的某些修改,造成程序的行为偷偷地发生了变化(编译通过,但程序逻辑发生变化)。
那么,怎么让代码显式地指明要调基类的同名函数呢?很简单,在所要调用成员之前,加上基类的类名域前缀即可:
- void test()
- {
- D d;
- d.B::foo(); //明确调用B的foo函数,而不是D的...
- }
-------------------------------------------------------------------------------
您理解本文提及的知识吗?,想知道有多少同学和你有同样的理解呢?请参看相关测试题:
http://student.csdn.net/space.php?do=question&ac=detail&qid=1835
-------------------------------------------------------------------------------
如果您想与我交流,请点击如下链接成为我的好友:
http://student.csdn.net/invite.php?u=112600&c=f635b3cf130f350c
-------------------------------------------------------------------------------