C++ 私有数据和私有类
本书之前提到了“封装”的概念,即指将一系列指令放在一个函数体内部的处理过程。而这样的做法则是为了将函数的接口与它的实现分离(函数接口指如何使用这个函数,函数实现则指如何去实现这个函数及实现具体做了些什么)。
上面提到这种封装可以命名为“功能封装”,用以区分本章将要介绍的“数据封装”。数据封装是基于这样的理念提出的:每一个结构的定义应当包括应用于本结构的函数集以及阻止对内部的无限制访问。
数据封装的应用之一在于隐藏用户或程序员不必了解的那些实现层次的细节。
比如对于一张“扑克”的花色和点数可以有很多种表达方式,可以用两个整数,两个字符串或者两个枚举类型。而实现这个“扑克”类的作者需要知道如何实现它,使用这个“扑克”的其他人就不应该知道它的内部结构了。 另外一个例子,我们之前使用apstring和apvector对象却未曾讨论过他们的实现方式。实现方式可以有很多种,但作为使用这些库的“客户”则不必知晓。
在C++中确保数据封装的通常办法是通过禁止客户程序访问对象的变量来实现的。在结构定义时使用关键字private进行保护。比如,我们有如下的“扑克”定义。
struct Card
{
private:
int suit, rank; //suit为花色,rank牌大小
public:
Card ();
Card (int s, int r);
int getRank () const { return rank; }
int getSuit () const { return suit; }
void setRank (int r) { rank = r; }
void setSuit (int s) { suit = s; }
};
该定义中分为两个部分:私有部分和公共部分。函数是公共的,这就意味着他们可以被用户程序调用。变量是私有的,于是他们就只能被“扑克”的成员函数进行读写。
但通过访问函数(以get和set开头的函数)可以实现用户程序对私有变量的读写。从另一方面来看,通过访问函数就可以很容易的控制哪个操作用户可以实施于哪个变量上。比如,让所有的牌在创建之后是只读是一个好主意。为了实现这个目的,我们需要做的只需移除所有的set函数。
使用访问函数的另外一个优点则是我们可以改变扑克的内部表达形式而不必更改用户的程序。