继承中的类作用域
2024年1月6日 2024年1月6日
派生类作用域嵌套在基类作用域之内: 派生类可以像使用自己的成员一样使用基类成员
存在继承关系时, 如果一个名字在派生类作用域内无法正确解析, 编译器将继续在外层的基类作用域中寻找该名字的定义
不能通过基类的引用/指针访问派生类非基类成员
1class Disc_quote : public Quote 2{ 3public: 4 pair<size_t, double> discount_policy() const { return {quantity, discount}; } 5}; 6 7Bulk_quote bulk; 8Bulk_quote *bulkP = &bulk; 9Quote *itemP = &bulk; 10bulkP->discount_policy(); // 正确: 派生类指针 11itemP->discount_policy(); // 错误: 指针的静态类型决定可以使用的名字
定义在内层作用域的名字将隐藏定义在外层作用域的名字
派生类的成员将隐藏同名的基类成员
1struct Base 2{ 3 Base() : mem(0) { } 4protected: 5 int mem; 6}; 7 8struct Derived : Base 9{ 10 Derived(int i) : mem(i) { } 11 12 int get_mem() { return mem; } 13protected: 14 int mem; // 隐藏基类中的mem 15}; 16 17Derived d(42); 18cout << d.get_mem() << endl; // 42
可以通过作用域运算符来使用隐藏的成员: 作用域运算符将覆盖掉原有的查找规则, 指示编译器从给定类作用域开始查找名字
1struct Derived : Base 2{ 3 // ... 4 int get_base_mem() { return Base::mem; } 5}; 6 7Derived d(42); 8cout << d.get_base_mem() << endl; // 0
除了覆盖继承而来的虚函数,派生类最好不要重用其他定义在基类中的名字
名字查找与继承
就通过指针/引用调用函数而言
-
确定指针/引用的静态类型, 于静态类型中查找名字
如果查找不到, 依次在直接基类中不断查找直至到达继承链的顶端如果仍未找到, 编译器报错
-
找到名字后, 进行类型检查, 判断调用是否合法
如果不合法, 编译器报错 -
如果调用合法, 编译器根据调用的是否为虚函数产生不同的代码:
- 如果是虚函数, 根据动态类型确定虚函数版本
- 如果是非虚函数, 编译器产生一个常规函数调用
- 如果是虚函数, 根据动态类型确定虚函数版本
名字查找先于类型检查
1struct Base 2{ 3 int memfcn(); 4}; 5 6struct Derived : Base 7{ 8 int memfcn(int); // 隐藏基类的memfcn, 而不是作为重载函数 9}; 10 11Derived d; 12Base b; 13b.memfcn(); // 正确 14d.memfcn(10); // 正确: 派生类作用域中有名字memfcn 15d.memfcn(); // 错误: 名字查找先得到int memfcn(int), 未通过类型检查 16d.Base::memfcn(); // 正确: 通过作用域运算符指示开始查找的位置为外层的基类作用域
虚函数与作用域
建议覆写虚函数时, 同时给出override和virtual关键字 :
- override用于供编译器判断是否为覆写虚函数, 避免隐藏基类名字
- virtual用于提高代码可读性
1class Base 2{ 3public: 4 virtual int fcn(); 5}; 6 7class D1 : public Base 8{ 9public: 10 int fcn(int); // 隐藏基类fcn 11 virtual void f2(); 12}; 13 14class D2 : public D1 15{ 16public: 17 int fcn(int); // 隐藏D1::fcn 18 int fcn(); // 覆写虚函数Base::fcn 19 void f2(); // 覆写虚函数D1::f2 20}; 21 22Base bobj; 23D1 d1obj; 24D2 d2obj; 25 26Base *bp1 = &bobj, *bp2 = &d1obj, *bp3 = &d2obj; 27 28bp1->fcn(); // 调用Base::fcn, 由指针的静态类型Base决定名字查找的作用域 29bp2->fcn(); // 调用Base::fcn, 由指针的静态类型Base决定名字查找的作用域, 而D1中fcn不是虚函数 30bp3->fcn(); // 调用D2::fcn, 由指针的静态类型Base决定名字查找的作用域, 使用D2中的虚函数版本 31 32D1 *d1p = &d1obj; 33D2 *d2p = &d2obj; 34 35bp2->f2(); // 错误: 在Base作用域查找名字f2失败 36d1p->f2(); // 调用D1::f2, 虚函数动态绑定 37d2p->f2(); // 调用D1::f2, 虚函数动态绑定 38 39Base *p1 = &d2obj; 40D1 *p2 = &d2obj; 41D2 *p3 = &d2obj; 42 43p1->fcn(42); // 错误: 在Base作用域查找fcn得到int fcn(), 类型检查失败 44p2->fcn(42); // 正确: 调用D1::fcn, 静态绑定 45p3->fcn(42); // 正确: 调用D2::fcn, 静态绑定
在派生类中重载基类成员函数
- 将基类成员函数的所有重载实例添加到派生类作用域中
使用using声明语句 - 在派生类中定义重载函数
using声明会改变成员函数的访问属性 : 无法改变基类private成员的访问属性