15.5 访问控制与继承
受保护的成员
派生类中的成员或友元只能通过派生类对象来访问基类的受保护成员。派生类对于一个基类对象中的受保护成员没有任何访问特权。
class son: public father
{
public:
son() = default;
son(const string &_name): father(_name) { }
void show() const override
{
father ff("ff");
// 下列语句会报错,因为name在基类father中是protected的,只能通过派生类对象来访问。
cout << ff.name << endl;
}
};
公有、私有和受保护成员
派生访问说明符不会影响派生类成员对基类成员的访问权限,派生类中仍然只能访问基类中的public和protected成员。
派生访问说明符的目的是控制派生类用户(或者说类对象)(包括派生类的派生类在内)对于基类成员的访问权限。
- public继承方式:
- 基类中的public成员在派生类中仍为public;
- 基类中的protected成员在派生类中仍为protected;
- 基类中的private成员在派生类中被继承下来,但是不可访问;
- protected继承方式:
- 基类中的public成员在派生类中变为protected属性;
- 基类中的protected成员在派生类中变为protected属性;
- 基类中的private成员在派生类中被继承下来,但是仍不可访问;
- private继承方式:
- 基类中的public成员在派生类中变为private属性;
- 基类中的protected成员在派生类中变为private属性;
- 基类中的private成员在派生类中被继承下来,但是仍不可访问
派生类向基类转换的可访问性
感觉没啥好看的。
友元与继承
友元关系不能继承。
改变个别成员的可访问性
使用using声明可以改变成员的访问权限。注意派生类只能为它可以访问的名字提供声明,基类中的private不可以。
默认的继承保护级别
默认情况下,使用class关键字定义的派生类是私有继承的,使用struct关键字定义的派生类是公有继承的。
struct和class唯二的差别:
- 默认成员访问说明符:struct public,class private。
- 默认派生访问说明符:struct public,class private。
15.6 继承中的类作用域
当存在继承关系时,派生类的作用域嵌套在其基类的作用域之内。如果一个名字在派生类的作用域内无法正确解析,则编译器会继续在外层的基类中寻找该名字的定义。
Bulk_quote bulk;
cout << bulk.isbn();
比如上述isbn()
的解析过程将如下:
- 先在
Bulk_quote
中查找isbn
,没找到,去基类Disc_quote
中找。 - 在
Disc_quote
也没找到,继续去基类Quote
中找。 - 在
Quote
找到,所以使用的isbn
最终被解析为Quote
中的isbn
。
在编译时进行名字查找
一个对象、引用或指针的静态类型决定了该对象的哪些成员是可见的。所以说是在编译时进行名字查找。
Bulk_quote bulk;
Quote *itemP = &bulk;
itemP->discount_policy(); // 错误
通过itemP
调用discount_policy()
非法,itemP
的静态类型是Quote
,通过itemP
只能访问到bulk
中的Quote
部分。
可以理解为item
只绑定到了bulk
的Quote
部分,自然访问不到其它部分。
名字冲突与继承
派生类的成员将隐藏同名的基类成员。可以通过作用域运算符来使用隐藏的成员。
名字查找优先于类型检查
如果派生类(即内层作用域)的成员与基类(即外层作用域)的某个成员同名,则派生类将在其作用域内隐藏该基类成员。即使形参列表不一样,也会被隐藏。
struct Base{
int memfcn();
}
struct Derived: Base {
int memfcn(int n); // 会隐藏基类的memfcn
}
Derived d; Base b;
d.memfcn(); // 错误
当在派生类中找到memfcn时,名字查找就已经结束,当前调用的版本需要一个int,而当前调用没有提供参数,错误。
虚函数与作用域
现在可以理解为什么基类与派生类的虚函数必须有相同的实参列表了。假如基类与派生类的虚函数接受的实参不同,则我们就无法通过基类的引用或指针调用派生类的虚函数了。
个人理解:当通过一个基类的指针或引用调用派生类的虚函数时,相当于这个基类指针绑定到了派生类中的基类部分。
- 调用虚函数时会发生动态绑定,会选择派生类的版本。因为派生类对基类中的虚函数进行了重写(“覆盖”),此时派生类中没有基类版本的虚函数,而基类指针则会把这个当成是基类的虚函数进行绑定。
- 调用非虚函数时不会发生动态绑定,实际调用的版本由指针的静态类型决定。
粗略地说调用虚函数时也是由静态类型决定的,只不过派生类对象中基类的虚函数被重写成了派生类版本。