🥒继承中的类作用域
2022-6-10
| 2023-8-2
0  |  阅读时长 0 分钟
type
status
date
slug
summary
tags
category
icon
password
Property
 
每个类定义其自己的作用域,在其中它的成员被定义。在继承下,派生类的作用域被嵌套在基类的作用域中。如果一个名字在派生类中无法被解析,那么将继续查找基类作用域。由于继承嵌套了类作用域,所以派生类的成员可以使用基类的成员就像是其自己的成员一样。
 
名称查找发生在编译期间
对象、引用、指针的静态类型决定了哪些对象的成员是可见的。即便静态类型和动态类型不一样,也是静态类型决定哪些成员是可以使用的。假设 Disc_quote 有一个名为 discount_policy 的成员:
 
名称冲突和继承
与其它内嵌作用域一样,派生类可以复用其直接或间接基类的名字。与往常一样,定义在内部作用域的名字将隐藏定义在外部作用域的名字。
在派生类中定义与基类同名的成员,将隐藏基类成员的直接使用。
 
 
使用作用域操作符来访问被隐藏的成员
通过使用作用域操作符可以访问被隐藏的基类成员:
作用域操作符覆盖了常规的名称查找规则,编译器将从Base类的作用域开始查找名字mem除了覆盖继承的虚函数外,派生类通常不应该复用基类中的名字。
 
 
名称查找和继承
理解函数调用在 C++ 中是如何解析的对于理解 C++ 的继承非常重要。例如:p->mem() 的调用过程将发生以下步骤:
  • 首先查看 p 的静态类型,调用成员函数的对象必须是类类型;
  • 在静态类型的类中查找 mem 成员,如果没有找到,继续从其直接基类向上查找,直到找到或者最后一个基类被查找过,如果依然没有找到则产生编译错误;
  • 一旦 mem 被找到了,执行常规的类型检查,查看函数原型与调用是否匹配,以及重载函数解析;
  • 如果 mem 是虚函数,并且调用是通过引用或指针发生的,那么编译器将产生代码,其将执行在运行时根据被绑定的对象的动态类型调用不同的 mem 版本;
  • 否则,如果 mem 是非虚函数,或者调用是通过对象实体(非引用或指针)发生的,编译器产生常规的函数调用代码;
 
通常,名称查找发生在类型检查前
声明在内部作用域的函数将不会重载定义在外部作用域的函数。因而,定义在派生类中的函数不会重载基类中的成员函数。如果派生类中的成员与基类成员同名,那么派生成员将隐藏基类成员。即便是函数拥有不同的形参列表基类成员也会被隐藏。即便在派生类中同名的成员是数据成员,也会隐藏基类中的同名函数。如:
Derived 中声明的 memfcn 隐藏了 Base 中声明的 memfcn。第三个调用之所以是无法完成的,原因在于,为了解析这个调用,编译器将在 Derived 中查找名字 memfcn,并且找到了。此时将不再继续往上查找。而 Derived 中的 memfcn 是一个接收一个 int 参数的函数,此调用与之函数不匹配,所以无法调用完成。
 
虚函数和作用域
如果基类和派生类的同名成员的参数不一样,那么将无法通过基类的指针或引用调用派生类的版本。如:
以上 D1 中的 fcn 并没有覆盖 Base 中的虚函数 fcn,因为他们具有不同的参数列表。相反却隐藏了基类中的 fcn。D1 有两个名为 fcn 的函数:基类的虚函数 fcn,和自己定义的非虚函数 fcn(int) ,但基类的虚函数被隐藏了,因而,无法直接调用。
 
通过基类调用隐藏的虚函数
以下有几个复杂的调用过程,如果理解了这几个对于理解 C++ 的隐藏特性有帮助:
上面 p1, p2, p3 恰巧都指向 D2 类型的对象。然后,由于 fcn(int) 并不是虚函数,所以调用时解析是由指针所指对象的静态类型决定的。
 
覆盖重载函数
与其它函数一样,不管成员函数是否为虚函数都可以进行重载。派生类可以覆盖其继承的零个或多个重载函数。如果派生类想让所有继承来的重载函数都能够通过派生类访问,就需要覆盖基类中的所有重载函数,或者一个都不覆盖。
 
有时派生类仅想覆盖一部分但不是所有基类的重载函数。如果为了这个目标而必须覆盖所有的基类函数就不划算了。除了覆盖所有基类的重载函数,派生类可以使用 using 声明来使得所有基类重载函数可以被派生类的用户代码访问。在引入了派生类的所有的重载函数之后,派生类只需要定义特定于本类的行为的函数,并且可以使用基类定义的其它函数。
 
using 声明的规则依然适用于现在的重载函数;只有派生类可以访问的基类成员才能被 using 声明导入;派生类的用户代码是否可以访问这些继承过来的名字取决于 using 声明所在的位置。
  • C++
  • 访问控制与继承构造函数与拷贝控制
    目录