继承中的类作用域

每个类定义自己的作用域,在这个作用域内定义类的成员。

当存在继承关系时,派生类的作用域嵌套在其基类的作用域之内。

如果一个名字在派生类的作用域内无法正确解析,则编译器将继续在外层的基类作用域中寻找该名字的定义。

在编译时进行名字查找

一个对象、引用或指针的静态类型决定了该对象的哪些成员是可见的。

即使静态类型与动态类型可能不一致(当使用基类的引用或指针时会发生这种情况),但是能使用哪些成员仍然由静态类型决定。

名字冲突与继承

和其他作用域一样,派生类也能重新定义在其直接基类或间接基类中的名字,此时定义在内层作用域(即派生类)的名字将隐藏定义在外层作用域(即基类)的名字。

通过作用域运算符来使用隐藏的成员

作用域运算符将覆盖掉原来的查找规则,并指示编译器从指定作用域开始查找名字。

名字查找与继承:

调用 p->mem()(或者 obj.mem()),依次执行以下4个步骤:

  • 首先确定 p(或 obj)的静态类型。因为调用的是一个成员,所以该类型必须是类类型。
  • 在 p(或 obj)的静态类型对应的类中查找 mem。如果找不到,则依次在直接基类中不断查找直至到达继承链的顶端。如果找遍了该类及其基类仍然找不到,则编译器将报错。
  • 一旦找到了 mem,就进行常规的类型检查以确认对于当前找到的 mem,本次调用是否合法。
  • 假设调用合法,则编译器将根据调用的是否是虚函数而产生不同的代码:
    • 如果 mem 是虚函数且通过引用或指针进行的调用,则编译器产生的代码将在运行时确定到底运行该虚函数的哪个版本,依据是对象的动态类型。
    • 反之,如果 mem 不是虚函数或者通过对象(而非引用或指针)进行的调用,则编译器将产生一个常规函数调用。

名字查找优先于类型查找

声明在内层作用域的函数并不会重载声明在外层作用域的函数。

定义派生类中的函数也不会重载其基类中的成员。

和其他作用域一样,如果派生类(即内层作用域)的成员与基类(即外层作用域)的某个成员同名,则派生类将在其作用域内隐藏该基类成员。

即使派生类成员和基类成员的形参列表不一致,基类成员也仍然会被隐藏掉。

虚函数与作用域

如果基类与派生类的虚函数接受的实参不同,则无法通过基类的引用或指针调用派生类的虚函数。

通过基类调用隐藏的虚函数

通过基类的指针调用虚函数,编译器产生的代码将在运行时确定使用虚函数的哪个版本,判断的依据是该指针所绑定对象的真实类型。

覆盖重载的函数

为重载的成员提供一条 using 声明语句,这样无须覆盖基类中的每一个重载版本。

using 声明语句指定一个名字而不指定形参列表,所以一条基类成员函数的 using 声明语句就可以把该函数的所有重载实例添加到派生类作用域中。

派生类只需要定义其特有的函数就可以了,而无须为继承而来的其他函数重新定义。

类内 using 声明的一般规则同样适用于重载函数的名字。

基类函数的每个实例在派生类中都必须是可访问的。

对派生类没有重新定义的重载版本的访问实际上是对 using 声明点的访问。

posted @ 2020-08-12 11:47  CodeWithMe  阅读(181)  评论(0)    收藏  举报