Java中有四种访问控制:public、protected、default、private,它们的使用范围可以用下面一张表概括:
类内部 | 本包 | 子类 | 外部包 | |
---|---|---|---|---|
public | 是 | 是 | 是 | 是 |
protected | 是 | 是 | 是 | 否 |
default | 是 | 是 | 否 | 否 |
private | 是 | 否 | 否 | 否 |
整个结构还是比较简单的,从类内部到本包到子类到外部包权限越来越小,比较好理解也比较好记忆。但是在C++中访问控制要复杂很多,因为不仅有属性和方法的访问控制,还有继承时的派生列表访问说明符。今天我们着重了解访问控制。
在Java中我们一般默认子类可以访问父类的protected,但是C++中要更复杂些。它有三个特征:
怎么理解第三点呢?举个例子:
class Base{
protected:
int num;
}
class Sub:public Base{
friend void set(Sub&);
friend void set(Base&);
int i;
}
void set(Sub& sub){
sub.i = sub.num = 0;//正确,可以访问基类和派生类的成员
}
void set(Base &base){
base.num = 0;//错误,不能访问基类的成员
}
为什么要这样设计呢?如果派生类的友元可以直接访问基类的受保护成员,那么我们对任何类,只要设计它的子类,在子类的友元函数中就可以访问基类受保护的成员了,破坏了设计访问控制的目的,这一点比Java要严谨一些,任何访问控制都会被反射虐的体无完肤。
所以这里的结论是:派生类的成员和友元只能访问派生类对象中的基类部分的受保护成员,对于普通的基类对象中的成员不具有特殊的访问权限。
派生访问说明符的目的是控制抱愧派生类的派生类在内的派生类用户对于基类成员的访问权限:
派生类向基类的转换是否可访问由使用该转换的代码决定,同时派生类的派生访问说明符也会有影响:
我们经常在代码中看到using Base::size
这样的语句,很蒙圈不知道是干什么。这里是为了改变派生类继承的某个名字的访问级别。比如我们大部分内容想要私有继承,那么派生类列表访问类型使用私有就行。但是如果私有继承时某个或者某几个字段想要公开就可以使用using语句。举个例子:
class Base{
public:
int size() const{
return n;
}
protected:
int n;
};
class Sub:private Base{
public:
//保持size函数的可访问性
using Base::size;
protected:
using Base::n;
}
有时候我们很困惑strcut和class关键字的区别,其实它们唯一的区别就是具有不同的默认访问说明符和默认派生运算符。但是我们规范书写代码的话都尽量不适用默认访问权限,所以很多时候struct和class区别不大。
派生类的成员将隐藏同名的基类成员,因为编译器查找类属性时,会先从自己的作用域内找,如果找不到再查找直接基类,如果还是查不到再查找直接基类的基类...。
class Base{
public:
Base():num(0){}
protected:
int num;
}
class Sub:public Base{
public:
Sub(int i):num(i){}
int getNum(){
return num;
}
protected:
int num;
}
void main(){
Sub s(20);
cout << s.getNum <<end;//打印结果为20,而不是基类0
}
我们可以通过作用域运算符来使用一个被隐藏的基类成员。
int Sub::getNum(){
return Base::num;
}
void main(){
Sub s(20);
cout << s.getNum <<end;//打印结果就变成基类的0了
}
最佳实践:除了覆盖基类继承而来的虚函数之外,子类最好不要重用其他定义在基类中的名字。
注意:如果子类的成员和基类的成员同名,则子类将在它作用域内隐藏这个基类的成员。即使子类成员和基类成员的形参列表不一致也会被隐藏。因为类中的名字查找是先找子类再找基类,当编译器在子类找到了该名字的成员就不会再向父类去找。
文本介绍了Java和C++访问控制权限的区别,以及C++派生访问说明符、派生类向基类转换的可访问性、改变个别成员的可访问性、默认的继承保护级别的内容。