每个类定义自己的作用域,在作用域中定义类的成员,当存在继承关系时,派生类的作用域嵌套在其基类的作用域内。当一个成员名字在派生类的作用域内无法解析的时候,编译器将继续在外层的基类作用域中寻找该名字的定义。
举个例子:类B中有一个函数名为f,类D继承自类B,当通过D的对象调用函数f时会有如下流程:
1.首先在D中查找,这一步没有找到名字f;
2.因为D是B的派生类,所以接下来在B中查找,此时找到名字f,所以使用的f函数最终被解析为B中的f。
因此,当调用p->mem()或obj.mem()时,将依次执行以下步骤:
1.首先确定p(或obj)的静态类型。因为调用的是一个成员,因此该类型必然是类类型;
2.在p(或obj)的静态类型对应的类中查找mem().如果找不到,则依次在直接基类中不断查找直到继承链的顶端,如果仍然找不到,编译器将报错;
3.一旦找到mem,将进行常规类型检查以确定这次的调用是否合法;
4.假如调用合法,则编译器将根据调用的是不是虚函数而产生不同的代码。
名字冲突与继承:
派生类能重用定义在其直接基类或者间接基类中的名字:
struct Base{
Base():mem(0){}
protected:
int mem;
};
struct Der:Base{
Der(int i):mem(i){}
int getm(){return mem;}
//通过作用域运算符使用隐藏的成员
int get_b_m(){return Base::mem;}
protected:
int mem;
};
int main(void){
Der d(42); //Base::mem默认初始化为0
cout<<d.getm()<<endl; //输出42
}
名字检查优于类型判断:
即使在派生类成员与基类成员的参数列表不相同,基类成员依然会被隐藏:
class Base {
public:
int f(){}
};
class Sb:public Base{
public:
int f(int i){}
};
int main(void) {
Base b;
Sb s;
b.f(); //正确
s.f(12); //正确
s.f(); //报错
}
编译器首先在Sb中查找名字f,因为在Sb中确实有一个名为f的成员,因此查找过程就终止了,而Sb中的f需要一个参数,调用的语句无法提供实参,所以调用语句错误。
虚函数与作用域:
以上也是基类和派生类中的虚函数必须有相同参数列表的原因:
class Base{
public:
virtual int f();
};
class D1:public Base{
public:
int f(int); //形参不一致,这个不是虚函数
virtual void f2();
};
class D2:public D1{
public:
int f(); //覆盖Base的虚函数f
int f(int); //不是虚函数,隐藏D1的f(int)
void f2(); //覆盖D1的f2()
};
覆盖重载的函数:
和其它函数一样,成员函数无论是不是虚函数都能被重载。派生类可以覆盖重载函数的0个或多个实例。如果派生类希望所有的重载版本对它来说都是可见的,那么他就需要覆盖所有版本,或者一个也不覆盖。
有时一个类仅需覆盖重载集合中的一些而非全部函数,此时,如果不得不覆盖基类的每一个版本的话,操作将变得极为繁琐。
一种好的解决方法就是使用using关键字,因为using声明语句指定的是一个名字而不指定参数列表,所以一条基类成员函数的using语句就能把该函数的所有重载实例添加到派生类的作用域中。此时,派生类只需要定义其特有的函数就可以了,而无需为继承而来的其它函数重新定义。
class Base {
public:
int f(){}
int f(int i){}
int f(int i,int j){}
int f(double d){}
int f(double d,double e){}
void f(int a,double v){}
void f(int e,char a){}
double f(char s){}
char f(int r,char s,double a){}
char f(double g,char w){}
char f(double d,int f){}
};
class Sb:public Base{
public:
int f(){} //在这里只重载了一个函数,
};
class Sb2:public Base{
public:
//一个都不重载
};
class Sb3:public Base{
public:
using Base::f; //使用using声明
int f(){} //只重载部分
};
int main(void) {
Sb s;
Sb2 s2;
Sb3 s3;
s.f(2); //报错
s2.f(2); //正常
s3.f(2); //正确
}