在C++程序设计中,只要有一定程序设计经验的程序员。一般都有这样的意识:尽量将成员变量声明为private成员。本实用经验就讲述一下关于成员变量的访问权限。我们都知道成员变量的访问权限有三种:public访问权限、private访问权限、protected访问权限。
访问权限说明
- public访问权限是权限最高的一种权限。如果将一个变量声明为public访问权限,用户可随意访问和设置此成员变量。这在一定程度上增加了程序的安全风险(本实用经验后续部分将讨论)。
- private访问权限是权限最低一种权限。将变量声明为private访问权限,如果不提供控制接口,用户无法访问和设置此变量,这可在一定程度上提供安全性。
- protected权限介于public和private之间。在成员访问方面其呈现出private权限,在继承机制的派生类中其呈现出public访问权限。
C++中所谓数据的访问权限即我们通常所述的数据的封装,其也是C++区别于C的重要标准。C语言中数据仅仅是配角,其服务于过程设计。然而C++则不然,数据是C++程序设计的核心,是主角。这种思想最具特色的就是struct和class:struct提供无封装的数据访问权限,class可提供我们自定义的数据访问权限。
在C++程序设计时,思考和创造全部围绕着数据进行。这也是从C程序设计到C++程序设计的重要转变。按照这种设计理念,大多数情况下,我们建议将数据成员声明为private。现在我们就分析将数据声明为private有什么优势;为何又不将数据声明为public访问权限?
1.提供精确的访问控制
我们思考,如果将数据声明为public会给我们带来什么样的影响。我们都知道的,如果一个数据声明为public权限,每个人都可以按照自己的期望,随意的访问和修改它。这回给我们带来什么呢?第一、数据失去了保护和封装,这种情况下class和struct几乎无异。第二、数据成员采用struct形式访问,如果class中这个数据成员发生了变化,这将导致整个程序只要使用其的地方,都需要进行修改。第三、外部对class内部数据的访问class无法感知。
但是,如果我们把数据成员声明为private,就可以将数据隐藏起来。然后提供数据的set和get访问控制函数。这样就可实现精确的数据访问可读、可写控制。我们对比下面两段代码,分析一下两者区别。
代码段一:
// 访问类型实现类
class AccessLevels
{
public:
// 获得只读属性接口
int getReadOnlay() const {return readOnly;}
// 设置读写属性接口
void setReadWrite(int value){readWrite = value;}
// 获得读写属性接口
int getReadWrite()const {return readWrite;}
// 设置只写属性接口
void setWriteOnly(int value){writeOnly = value;}
private:
int noAccess;
int readOnly;
int readWrite;
int writeOnly;
};
代码段二:
class AccessLevels
{
public:
int noAccess;
int readOnly;
int readWrite;
int writeOnly;
};
代码段一:通过函数接口实现数据的读和写,我们可以方便的实现只读访问和读写访问。这样实现可以很好的体现设计的设计意图。而且也保护了数据成员。
代码段二:将所有数据成员的访问权限均为public。这种实现,使得任何人都可以对class进行操作。但有时并不是设计人员所期望的。假设我们对AccessLevels中noAccess数据成员进行操作。例如:
AccessLevels access;
Access. noAccess = 10;
如果由于需求的变化导致noAccess数据成员不在需要了。这样会导致程序中所有使用noAccess数据成员的地方都需要进行修改。如果你的程序代码量超过了百万行代码,这种修改将是致命的。
2.增加封装性,为后续实现提供弹性
如果通过函数访问数据成员,后续就可以修改这个函数的实现。而作为客户的使用者却并不知道class内部发生了什么变化。
如下面的测速程序,汽车通过,其速度被计算并填入一个速度收集器内:
class StreamDataCollection
{
public:
void AddValue(int iStream); //添加一笔新数据
double AverageSteam() const; //返回平均速度
};
考虑AverageSteam。做法之一是在class内设计一个成员变量,记录至今以来所有流量的平均值。当AverageSteam被调用,只返回那个成员变量就好。
另一个做法是令AvrerageSteam每次被调用时,重新计算流量的平均值,此函数有权力调取流量数据收集器内的每一笔流量数据值。
哪一种做法更好,在内存吃紧的机器上,第一种做法会增加StreamDataCollection对象的占用空间,在频繁需要平均值的应用程序中,内存不是重点。
所以,成员变量隐藏在函数接口的背后,可以为“所有可能的实现”提供弹性。例如这可使得成员变量被读或被写时轻松通知其他对象。可以验证class的约束条件及函数的前提和事后状态、可以在多线程环境中执行同步控制……。
除了上述作用外,另外一个优势就是:封装性。如果对客户隐藏成员变量(也就是)封装,保留了日后变更实现的权力。public意味着不封装,不封装意味着不可改变。
特别是被广泛使用的classes而言,因为他们最能够从“改采用一个较佳实现版本”中获益。
3.提供语法的一致性
你成员变量声明为private。如果你想访问这个数据成员,唯一的方法就是接口函数。客户就不用在思考其他的访问方式了。这样即保证了数据访问的唯一性,也防止使用者的纠结,从而节省时间。
按照上面的讨论,如果你使用public这是一种愚蠢的行为,但是如果将数据成员声明为protected呢?protected对数据的包含不彻底,在它的派生类中可访问protected成员变量。因此在一定的范围内它会导致public带来的问题,所以protected不能实现对数据成员的访问控制。同样也存在一致性的问题。因此不能将数据存储成员声明为public的原因也适用于protected。
小心陷阱:在类中将数据成员声明为protected和声明为public两者没有什么区别。两者都会降低类的封装性。
请谨记
- 将数据成员声明为private,可带来访问数据的一致性,访问控制,并可为以后提供足够的弹性。
- 将数据声明为protected并不比public更具有封装性。