C++类成员的访问范围(C++ private、public、protected)

在类的定义中,可以用 private、public 和 protected 三种关键字来指定成员可被访问的范围。

private:用来指定私有成员。一个类的私有成员,不论是成员变量还是成员函数,都只能在该类的成员函数内部才能被访问。

public:用来指定公有成员。一个类的公有成员在任何地方都可以被访问。

protected:用来指定保护成员。这需要等介绍“继承”之后再解释。

三种关键字出现的次数和先后次序都没有限制。成员变量的可访问范围由离它前面最近的那个访问范围说明符决定。

如果某个成员前面没有访问范围说明符,则对 class 来说,该成员默认地被认为是私有成员;对 struct 来说,该成员默认地被认为是公有成员。例如:
class A
{
    int m, n;
public:
    int a, b;
    int func1();
private:
    int c, d;
    void func2();
public:
    char e;
    int f;
    int func3();
};
在上面的类 A 中,成员 a、b 和 func1 是公有的,c、d 和 func2 是私有的,e、f 和 func3 又是公有的。m 和 n 没有指定可访问范围,则是私有的。如果把 class 换成 struct,那么 m 和 n 就是公有的。

下面的程序可以说明公有成员和私有成员的区别。假设一个企业员工管理程序的一小部分代码如下:
#include <iostream>
#include <cstring>
using namespace std;
class CEmployee {
private:
    char szName[30];  //名字
public:
    int salary;  //工资
    void setName(char* name);
    void getName(char* name);
    void averageSalary(CEmployee el, CEmployee e2);
};
void CEmployee::setName(char* name) {
    strcpy(szName, name);  //ok
}
void CEmployee::getName(char* name) {
    strcpy(name, szName);  //ok
}
void CEmployee::averageSalary(CEmployee el, CEmployee e2)
{
    salary = (el.salary + e2.salary) / 2;
}
int main()
{
    CEmployee e;
    strcpy(e.szName, "Tom1234567889");  //编译出错,不能访问私有成员
    e.setName("Tom");  //ok
    e.salary = 5000;  //ok
    return 0;
}
在上面的程序中,szName 是私有成员,其他成员都是公有的。

私有成员只能在成员函数内部访问,因此第 14 行和第 17 行没有问题,这两条语句都是在访问函数所作用的那个对象的 szName 私有成员。另外,类的成员函数内部可以访问任何同类对象的私有成员。

所谓成员函数内部,指的就是成员函数的函数体内部。main 函数中的语句,如第 26 行,当然就不在 CEmployee 的成员函数内部,因此该行试图访问 e 这个对象的 szName 私有成员变量就会导致编译错误。

而第 27 行虽然也不属于 CEmployee 类的成员函数内部,但其访问的是对象 e 的公有成员 setName,因此没有问题。同理,第 28 行也没有问题。

在 CEmployee 类的成员函数外面,若要访问 CEmployee 对象的 szName 私有成员变量,不能直接访问,只能通过两个成员函数 setName 和 getName 间接进行访问。

“隐藏”的作用

设置私有成员的机制叫作“隐藏”。“隐藏”的一个目的就是强制对成员变量的访问一定要通过成员函数进行。这样做的好处是,如果以后修改了成员变量的类型等属性,只需要更改成员函数即可;否则,所有直接访问成员变量的语句都需要修改。

以上面的企业员工管理程序为例,如果 szName 不是私有的,那么整个程序中可能会有很多类似于第 26 行

strcpy(man1, szName, "Tom1234567889");

这样的语句。假设需要将该程序移植到内存空间紧张的手持设备上,希望将 CEmployee 类的成员变量 szName 改为 char szName[5],以便节约空间,那么所有这样的语句都要找出来检查一番并修改,以防止数组越界。这显然很麻烦。

如果将 szName 变为私有的,则除了 CEmployee 类的成员函数内部,其他地方不可能出现第 26 行那样对 szName 直接访问的语句,所有对 szName 的访问都是通过成员函数进行的。例如:

e.setName("Tom12345678909887");

就算 szName 的长度变短了,上面的语句也不需要修改,只要修改 setName 成员函数,在其中去掉超长的部分,确保数组不越界就可以了。

可见,“隐藏”有利于程序的修改。

“隐藏”机制还可以避免对对象的不正确操作。有的成员函数只是设计用来让同类的成员函数调用的,并不希望对外开放,因此就可以将它们声明为私有的,隐藏起来。

现代软件开发绝大多数是合作完成的,一个程序员设计了一个类,可能被许多程序员使用。在设计类的时候,应当尽可能隐藏使用者不需要知道的实现细节,只留下必要的接口(即一些成员函数)来对对象进行操作,这样能够避免类的使用者随意使用成员函数和成员变量而导致错误。

就像数字照相机的设计者会用外壳将内部的电路全部封装隐藏起来,用户不需要知道数字照相机的具体工作原理以及其中有哪些器件,只要能通过设计者留下的接口,即外壳上的各种按钮来使用照相机即可。如果把内部的电路、器件、开关都暴露给用户,那么外行用户很可能会把照相机弄坏。