先明确一下基本出发点:一个类的访问者有四类:自己、友元、子类、普通用户。
“自己”和“友元”可以随意访问该类,不管它们。
只需要讨论子类和普通用户。
子类又分两种:儿子、孙子(孙子的儿子同理递推即可)。
我们先说能不能访问的问题,从简单的说起。
先说一级继承:
class Dad {
public:
string pub_text = "pub_text";
protected:
string prot_text = "pro_text";
private:
string pirv_text = "priv_text";
};
上面的很简单,public
能被儿子和普通用户,protected
能让儿子访问,privtae
只能Dad
自己访问。
我们把这里的public
、protected
、private
称为基类访问说明符。
成员类型(基类访问说明符) | 可访问者 | 不可访问者 |
---|---|---|
public | 儿子 ,普通用户 | 无 |
protected | 儿子 | 普通用户 |
private | 无 | 儿子 ,普通用户 |
下来,有三个儿子继承了Dad
:
class Public_son: public Dad {
};
class Protected_son: protected Dad {
};
class Private_son : private Dad{
};
把这里冒号后面的public
、protected
、private
称为派生访问说明符。
这里三种儿子,对Dad
的访问没有任何差异,它们都能访问(基类部分)Dad
的public
部分和protectde
部分,不能访问Dad
的private
部分。
所以容易明白,这里的派生访问说明符不是针对儿子的,而是针对其他人的。
下面我们来讨论这些“其他人”。
其他人分两种,第一种是儿子的儿子,也就是Dad
的孙子;另一种是其他用户。
public
继承,则相当于Dad
没有额外限制.
Dad
的public
成员人人可访问,孙子还可以访问protected
成员,private
成员只有Dad
自己可以访问。protected
继承,则孙子可以访问爷爷的public
和protected
成员,但是不能访问private
;普通用户什么都不能访问——包括Dad
的public
成员。
private
继承,爷爷,也就是前面的Dad
类,对派生类、用户都不可见。
总结下来,其实就相当于叠加,取两权限的“最小范围”。在子类的两类用户看来就像下面一样:
public继承
:
Dad
的public
成员 = 儿子的public
成员;Dad
的protected
成员 = 儿子的protected
成员;Dad
的private
成员 = 儿子的private
成员;protected继承
:
Dad
的public
成员 = 儿子的protected
成员;Dad
的protected
成员 = 儿子的protected
成员;Dad
的private
成员 = 儿子的private
成员;private继承
:
Dad
的public
成员 = 儿子的private
成员;Dad
的protected
成员 = 儿子的private
成员;Dad
的private
成员 = 儿子的private
成员;看起来就像分类“合并”。
用表格描述,孙子和其他用户,对于基类部分的可访问性:
儿子对父亲的继承类型 | 可访问者 | 不可访问者 |
---|---|---|
public | 孙子,普通用户 | 无 |
protected | 孙子 | 用户 |
private | 无 | 用户,孙子 |
改变访问方式有两种,友元、using声明。
先说友元:
比如,子类的友元不能随意访问基类部分。
下面的代码是错的:
class Dad {
public:
string pub_text = "pub_text";
protected:
string prot_text = "pro_text";
private:
string pirv_text = "priv_text";
};
class Son : Dad {
friend void func (Son son);
};
void
func (Son son)
{
cout << son.priv_text << endl;//不可以!
}
这很合理,毕竟连Son
自己都不能随意访问Dad
。
struct
默认访问权限是public,默认public继承;
class
默认访问权限是private,默认private继承。
using声明可以将子类能够访问的成员的权限进行更改。
using
声明位于public
,则被声明的对其他人就变成public
的;放在private
就变成private
的;protected
同理。
看下面的例子,Son
私有继承Dad
,本来用户不能访问prot_text
,能访问pub_text
,现在将它们反了过来:
class Dad {
public:
string pub_text = "pub_text";
protected:
string prot_text = "pro_text";
private:
string priv_text = "priv_text";
};
class Son : private Dad {
public:
using Dad::prot_text;
private:
using Dad::pub_text;
};
int
main (void)
{
Son son;
cout << son.prot_text << endl; //可访问
cout << son.pub_text << endl;//错误,不可访问
}
子类可以访问父类,但并不是说可以直接访问。
看下面的代码:
class Dad {
public:
string pub_text = "pub_text";
protected:
string prot_text = "pro_text";
private:
string pirv_text = "priv_text";
};
class Son : Dad {
public:
void func (Dad dad) {
cout << dad.prot_text << endl; //错误
}
};
编译器会报错,说Dad
的pro_text
是protected
的,所以你不能访问。
初学者看到这里很容易发懵:protected
不是能被子类访问吗?
你需要细细品味一下C++ Primer 5,p543的一句话:
此外,
protected
还有另外一条重要的性质。
派生类的成员或友元只能通过派生类对象来访问基类的受保护成员。派生类对于一个基类对象中的受保护成员没有任何访问特权。
如果你这样做,那是对的:
class Son : Dad {
public:
void
func (Son &son) {
cout << son.prot_text << endl;
}
};
至于为什么要这样设计,我想大概是为了降低各类型之间的耦合度,保持相对独立吧。
你完全可以提供protected
的getter
和setter
来达到目的。
派生类向基类的转换,或者说,把派生类绑定到基类的指针/引用,会受到派生说明符的影响。
(以下转换均视为引用/指针之间的转换)
假设有两个类,Dad
和Son
,
只有Son
公有继承Dad
时,用户才能将Son
转换为Dad
,换句话说,将Son
(的引用/指针)当作Dad
(的引用/指针)使用。
不管哪种继承方式,Son
的成员和友元 始终 可以使用Son
向Dad
的转换。
只有Son
在protected
/ public
继承Dad
时,Son
的派生类(也就是Dad
的孙子类)的成员、派生类的友元可以使用 Son
向Dad
的转换。
从类设计者意图来看,也很好理解,Son
将Dad
私有继承,就意味着Son
不希望让用户使用它的基类部分(Dad
)。
而将Son
转为Dad
,静态类型还是Son
,用户就能偶藉由Dad
来访问Son
的基类部分,违背了类设计者的意图,不合理。
然而子类不能向父类转换,违背了里氏代换原则,大概不是一种很好的设计。
或许这是private
和protected
继承比较少见的原因吧,平时使用的都是public
继承。
private
继承的意义仅仅是实现上的继承,不牵扯面向对象设计理念。
相关内容见《Effective C++第三版 》条款39:明智而审慎地使用private继承
参考书籍《C++ primer 5》p542 ~ p546