一、前言
C++标准中定义了三种类成员访问控制符:public、protected和private,分别译为公有的、保护的、私有的。当然,这是在一个类的内部声明这三种控制符的意思,那要是在一个继承体系中会是怎么样的呢?
二、控制符访问特点
首先说明这三种访问控制符各自的访问控制特点,指出这三种控制符声明的变量可以被哪些实体访问:
(1)public成员:能被本类的成员函数(不管是哪一种控制符声明的成员函数,都可以)、友元函数、本类的对象、其派生类的成员函数(不管是哪一种控制符声明的成员函数,都可以);
(2)protected成员:能被本类的成员函数(不管是哪一种控制符声明的成员函数,都可以)、友元函数、其派生类的成员函数(不管是哪一种控制符声明的成员函数,都可以);
(3)private成员:能被本类的成员函数(不管是哪一种控制符声明的成员函数,都可以)、友元函数;
通过以上介绍,可以总结为:
(1)对类的用户而言,即使用类对象实例的人(还有一种说法就是指只能看到类的头文件的人,因为设计类的人都将他写的实现文件编译成了二进制文件,在Windows下是.lib、.obj后缀的文件),通过类对象实例,只能访问到类的public成员(函数或变量);
(2)对类本身而言,即类的设计者,在设计类的过程中,他可以通过类的所有的成员函数(三种控制符声明),或者友元函数访问其类内部所有的成员(函数或变量),不管其要该成员(函数或变量)是用哪一种控制符声明的,都可以访问。
(3)对该类的派生类而言,到这里只能说基类的public、protected的成员对它来说是可见的,即可以访问,但具体怎么访问,这涉及到了C++类继承的知识,接下来会重点解释这一条。
三、C++访问控制与继承
在一个继承体系中,会涉及到两层访问控制符,一层在类的派生列表中,另一层则是在基类中。庞大而复杂的继承体系看起来是不好理解的(其实也确实不好理解,哈哈),但它归根结底是单层继承的扩展,所以这里我就以单层public继承结构来说明C++访问控制与继承的关系,继承结构为Drived :public Base。
既然这里涉及到继承,那先必须要理解的一点就是C++继承体系中派生类的对象模型,这在C++Primer和《深度探索C++对象模型》都有解释,即派生类的内存模型主要包括两部分:基类部分和派生类部分,其中基类部分是从基类继承而来的,派生类部分是自己定义的。
上述第一层访问控制符(即派生列表中)影响的是派生类中基类部分成员(函数或变量)的访问控制权限(也仅仅只有这一点影响),具体的影响为:
(1)public继承(保持不变):派生类中基类部分的成员(函数或变量)的访问控制权限和其在基类中声明的权限一样;
(2)protected继承(各自降低一个权限):基类中public成员在派生类中变成protected权限,其他成员都变为private权限;
(3)private继承(全部私有化):派生类中基类部分的成员(函数或变量)的访问控制权限全部变为private成员。
特别注意:基类中的private成员在派生类中任何控制类型的成员函数中都不能访问(具体可见后面的程序编译结果)。
好,现在解释了第一层访问控制符,那么派生类中所有成员(基类部分由上面的可以解释,派生类部分本身就已经声明)的访问控制权限都确定了,那现在的问题就是能不能访问了。
针对这里的单层public继承结构的例子,基类部分的成员的访问控制权限保持不变,下面分三种情况进行解释:
(1)基类部分的public成员。请注意,尽管派生类的内存模型分为两部分,但这两部分都属于派生类对象。所以派生类(类本身)的所有成员函数(不管哪一种访问控制符)都能访问基类部分的public成员(函数或变量),同时派生类对象也直接可以访问(通过成员访问操作符)。
(2)基类部分的protected成员。派生类(类本身)的所有成员函数(不管哪一种访问控制符)都能访问基类部分的protected成员(函数或变量),但派生类对象不能直接访问(基类对象自己都不能访问它的protected成员,派生类当然不可以)。
(3)基类部分的private成员。派生类(类本身)的所有成员函数(不管哪一种访问控制符)都不能访问基类部分的private成员(函数或变量),同样派生类对象也不能直接访问。
下面对以上三种情况在VS2010中进行测试,这里只进行编辑,VS2010自动检错功能就能提示相应错误
class Base
{
public:
int pub_mem;
protected:
int pro_mem;
private:
int pri_mem;
//对类自身来说,其成员函数,无论何种权限说明,都可以访问类中的任何权限的成员变量
public:
int base_fun_pub() {}
protected:
int base_fun_pro() {}
private:
int base_fun_pri() {}
};
class Drived: public Base
{
//pub、pro、pri成员函数都能访问其基类部分的public、protected成员变量,但都不能访问private成员
public:
int drived_fun_pub()
{
pub_mem = 0; //情况(1)
pro_mem = 0; //情况(2)
pri_mem = 0; //情况(3)(错误,不能访问)
}
protected:
int drived_fun_pro()
{
pub_mem = 0; //情况(1)
pro_mem = 0; //情况(2)
pri_mem = 0; //情况(3)(错误,不能访问)
}
private:
int drived_fun_pri()
{
pub_mem = 0; //情况(1)
pro_mem = 0; //情况(2)
pri_mem = 0; //情况(3)(错误,不能访问)
}
private:
int j;
};
void main()
{
/******************单层public继承为例**********************/
Drived drivedobj;
drivedobj.pub_mem; //情况(1)
drivedobj.pro_mem; //情况(2)(错误,不能访问)
drivedobj.pri_mem; //情况(3)(错误,不能访问)
}
在VS2010中显示的结果是(自动下划红线提示的就是访问权限错误):
四、总结
其实不管继承还是不继承,都可以当做是在一个单层类(没有继承)中进行访问控制权限分析,因为在继承情况下无非是多了一个继承列表中的访问控制符,这个控制符影响的是基类中的成员在派生类中是什么样的访问权限(只有这一点影响),而这点影响非常好解释(见上面第三节解释),一旦解释了派生列表中的访问控制符,就可以视为一个单一类的成员来访问,所以,解释一个类的成员访问权限的步骤为:
1、如果是派生类,记住两点
(1)先解释派生列表中的控制符,确定基类中各成员在派生类中是什么属性;
(2)对派生类而言,派生类对象只能访问基类中原有属性为public的成员,派生类本身(成员函数)只能访问基类中原有属性为protected和public的成员(注意,是原有属性,不管经过派生列表访问控制符解释之后的属性是什么)。
2、如果不是派生类
(1)其对象只能访问public属性的成员;
(2)对于类的接口实现代码(所有三种属性的成员函数)来说,它能访问所有访问权限的成员。
所以说,对于继承体系来说,无疑就多出了一层疑惑:即基类中成员到了派生类中是什么访问属性的?一旦解决了这个问题,那就变成了单一类的访问问题了。