15.2 继承中的类作用域
每一个类都有自己的作用域,派生类的作用域在基类的作用域中。从派生类的对象访问某一个成员如果在派生类中没有找到该名字,则从直接基类中查找,一直往继承链的上面找,如果一直没有找到则报错。
对于名字查找,这依赖于变量(普通变量,指针或者引用)的静态类型,即:变量的静态类型决定了这个变量只能访问哪些成员。这就很好理解为什么用基类的引用或者指针只能访问基类的成员(虚函数除外),另一个理解的角度是基类的引用或者指针实际上绑定的是派生类中基类的对象。
如果基类和派生类的成员的名字有重复的,则派生类的成员会隐藏基类的成员。这和之前作用域中进行名字查找其实是一样的。如果想访问被隐藏的成员则使用作用域运算符::来访问。
继承体系中名字查找的顺序是下面这样的。
1.首先由静态类型确定可以访问的成员
2.从派生中的作用域中开始查找,如果没有找到则从直接基类中查找名字
3.如果还没有找到则,再从该类的直接基类中找,重复这个步骤,直到找到或者没有找到位置
4.如果没有找到则报错
5.如果找到了,且这个名字是一个虚函数且我们通过指针或者引用调用,则运行的时候根据动态类型来调用对应的函数,如果这个名字不是虚函数或者我们是使用普通的方式(非指针和引用)来访问的,则在编译时产生一个常规函数调用。
第5点尤其重要,分清楚什么时候运行时确定调用,什么时候编译时确定调用的函数。
之前说了同名的派生类成员会覆盖基类的成员,对于函数来说只要名字是一样的基类的同名函数就会被隐藏。所以在重写虚函数的时候,如果形参列表不一样,也会覆盖基类的函数,但是派生类其实是定义了一个新的虚函数(如果加了virtual关键字的话),所以override的重要性体现出来了。
如果基类有函数是重载的,派生类可以重写这些函数,但是因为只要名字相同基类的同名函数就会被隐藏,所以如果想要基类所有的重载版本对于派生类来说都是可见的,那么我们只能全部重写或者一个都不重写。
但是可能我们对于多个重载函数,只有少数几个需要修改一下,其余的可以全部照抄。这个时候我们可以使用using关键字,我们只要写using 基类名::函数名;即可,这就表示基类所有的同名函数都添加到派生类的作用域中,接下来直接写我们想要修改的函数即可。
struct A
{
void func() {
cout<<"A-->func()"<<endl;
};
void func(int) {
cout << "A-->func(int)" << endl;
};
void func(double, double) {
cout << "A-->func(double, double)" << endl;
};
};
struct B:public A
{
using A::func;
void func(double, double) {
cout << "B-->func(double, double)" << endl;
}
};
---
B b;
b.func();
b.func(1);
b.func(0.0,0.0);
A-->func()
A-->func(int)
B-->func(double, double)
练习
15.23
将D1中int fcn(int);变为virtual int func() override;
bp2->func()调用的是D1自己的版本
注意下图这种方式,D2的fcn()是一个虚函数,虽然D1中的fcn(int)隐藏了fcn(),但是后续还是可以重写虚函数。