每个类定义自己的作用域,在这个作用域内我们定义自己的成员。当存在继承关系时,派生类的作用域嵌套在其基类的作用域内。如果一个名字在派生类的作用域内无法解析,则编译器继续在外层的基类作用域中继续寻找该名字的定义。
派生类的作用域位于基类的作用域之内这一事实可能有点出人意料,毕竟在我们的程序文本中派生类和基类的定义是相互分离开来的。不过也恰恰因为类的作用域有这种继承嵌套的关系,所以派生类才能像使用自己成员一样使用基类的成员。例如,当我们编写下面的额代码时:
Bulk_quote bulk;
cout << bulk.isbn();
名字isbn的解析将按照下述过程所示:
- 因为我们是通过Bulk_quote的对象调用isbn的,所以首先在Bulk_quote中查找,这一步没有找到isbn。
- 因为Bulk_quote是Disc_quote的派生类,所以接下来在Disc_quote中查找,仍然找不到。
- 因为Disc_quote是Quote的派生类,所以接着查找Quote;此时找到了名字isbn,所以我们使用的isbn最终被解析为Quote中的isbn。
在编译器时进行名字查找
一个对象、引用或指针的静态类型决定了该对象的哪些成员是可见的。即使静态类型和动态类型可能不一致(当使用基类的引用或指针时会发生这种情况),但是我们能使用哪些成员仍然由静态类型决定的。举个例子,我们可以给Disc_quote添加一个新成员,该成员返回一个存有最小(或最大)数量及折扣价格的pair:
class Disc_quote : public Quote { public: pair<size_t, double> discount_policy() count { return {quantity, discount}; } }; int main() { Bulk_quote bulk; Bulk_quote bulkP = &bulk; //静态类型与动态类型一致 Quote *itemP = &bulk; //静态类型和动态类型不一致 bulkP->discount_policy(); //正确:bulkP的类型是Bulk_quote* itemP->discount_policy(); //错误:itemP的类型是Quote* }
尽管bulk中确实含有一个名为discount_policy的成员,但是该成员对于itemP却是不可见的。itemP的类型是Quote的指针,意味着对discount_policy的搜索将从Quote开始。显然Quote不包含名为discount_policy的成员,所以我们无法通过Quote的对象、引用或指针调用discount_policy。
名字冲突和继承
和其它作用域一样,派生类也能重新定义在其直接基类或间接基类中的名字,此时定义在内层的作用域(即派生类)的名字将隐藏定义在外层作用域(即基类)的名字:
class Base { public: Base() : mem(0) {} protected: int mem; }; class Derived : Base { public: Derived(int i) : mem(i) {} int get_mem() {return mem;} protected: int mem; }; int main() { Derived d(42); cout << d.get_mem() << endl; }
通过作用域解析符来使用被隐藏的成员
我们可以通过作用域解析符来使用一个被隐藏的基类成员:
class Derived : Base { public: int get_base_mem() {return Base::mem;} };