一、访问控制的基本思想
类的成员包含数据成员和函数成员,分别描述问题的属性和行为。以钟表为例,不论是何种钟表,都记录着时间值,都有显示面板、旋钮或按钮。正常用户只需要通过面板查看时间,通过旋钮调节时间。面板、按钮或旋钮就是接触和使用钟表的仅有途径,它们对应类中的公有接口。
而钟表内部记录的时间值(时、分、秒),使用者无法直接触碰,只能通过外部接口间接访问,它们对应类中的私有成员。
这种”外部只能走接口,内部数据被隐藏”的机制,就是访问控制。
二、三种访问属性
| 访问属性 | 关键字 | 类内部 | 类外部(通过对象) | 派生类中 | 作用 |
|---|---|---|---|---|---|
| 公有类型 | public | 可访问 | 可访问 | 可访问 | 定义类的外部接口 |
| 私有类型 | private | 可访问 | 不可访问 | 不可访问 | 隐藏内部数据和实现细节 |
| 保护类型 | protected | 可访问 | 不可访问 | 可访问 | 继承体系中向派生类暴露成员 |
三、公有成员——类的外部接口
公有类型成员定义了类的外部接口。外界只能通过 public 成员与类的对象进行交互。
以时钟类为例,ShowTime() 和 SetTime() 是公有成员函数,使用者通过它们查看和设置时间:
Clock c;
c.SetTime(9, 30, 0); // 合法:SetTime 是 public
c.ShowTime(); // 合法:ShowTime 是 publicCode language: JavaScript (javascript)
公有成员是类与外界通信的唯一通道。设计类时,应将所有需要对外提供的功能声明为 public。
四、私有成员——隐藏内部实现
私有成员只能被本类的成员函数访问,来自类外部的任何访问都是非法的。私有成员将内部数据结构完全隐藏在类中,保护数据的安全性。
时钟类中的 hour、minute、second 都是私有成员:
class Clock
{
private:
int hour, minute, second; // 私有成员
public:
void SetTime(int h, int m, int s)
{
hour = h; // 合法:类内成员函数可访问 private 成员
minute = m;
second = s;
}
void ShowTime()
{
cout << hour << ":" << minute << ":" << second << endl; // 合法
}
};
int main()
{
Clock c;
// c.hour = 9; // 错误:hour 是 private,类外不可访问
c.SetTime(9, 0, 0); // 合法:通过 public 接口间接操作私有数据
return 0;
}Code language: JavaScript (javascript)
一般情况下,一个类的数据成员都应该声明为私有成员。这样,内部数据结构就不会对该类以外的其余部分造成影响,程序模块之间的相互作用被降低到最小。
五、保护成员——继承相关
保护类型成员 protected 的性质与私有成员相似,类外部同样不可直接访问。其与私有成员的差别在于继承过程中对产生的新类影响不同:protected 成员在派生类中可以访问,private 成员在派生类中不可访问。
关于 protected 在继承中的具体表现,将在后续继承章节中详细讲解。
六、访问限定符的使用规则
在类的定义中,具有以下特点:
- 不同访问属性的成员可以按任意顺序出现。
- 修饰访问属性的关键字(
public、private、protected)可以出现多次。 - 但一个成员只能具有一种访问属性,以后出现的限定符为准直到下一个限定符出现。
以下写法都是正确的:
class Example
{
public:
int a; // a 是公有
private:
int b; // b 是私有
public:
void func1(); // func1 是公有
private:
int c; // c 是私有
void helper(); // helper 是私有
protected:
int d; // d 是保护
};Code language: PHP (php)
等价写法(推荐,按访问属性分组,每种限定符出现一次):
class Example
{
private:
int b;
int c;
void helper();
protected:
int d;
public:
int a;
void func1();
};Code language: PHP (php)
两种写法在语法上完全等价,但推荐按 private → protected → public 的顺序分组书写,使类的接口一目了然。
七、代码示例
以下代码演示三种访问属性的实际效果:
#include <iostream>
using namespace std;
class Clock
{
private:
int hour; // 私有:内部数据
int minute;
int second;
void validate() // 私有:内部辅助函数
{
if (hour < 0 || hour > 23) hour = 0;
if (minute < 0 || minute > 59) minute = 0;
if (second < 0 || second > 59) second = 0;
}
public:
void SetTime(int h, int m, int s)
{
hour = h;
minute = m;
second = s;
validate(); // 类内调用私有成员函数
}
void ShowTime()
{
if (hour < 10) cout << "0";
cout << hour << ":";
if (minute < 10) cout << "0";
cout << minute << ":";
if (second < 10) cout << "0";
cout << second << endl;
}
};
int main()
{
Clock c;
c.SetTime(25, 70, 80); // 通过公有接口传入非法值
c.ShowTime(); // 输出 00:00:00(内部 validate 修正)
// c.hour = 10; // 错误:hour 是 private
// c.validate(); // 错误:validate 是 private
c.SetTime(14, 35, 50); // 通过公有接口设置合法值
c.ShowTime(); // 输出 14:35:50
return 0;
}Code language: PHP (php)
分析:
hour、minute、second和validate()都是private,外部无法直接访问。SetTime和ShowTime是public,外部通过它们与对象交互。validate()是私有辅助函数,在SetTime内部被调用,负责数据校验。外部不需要知道它的存在,体现了实现隐藏。