在 C++ 中定义类时,如果我们把所有成员变量都放在 public: 下面完全公开,类的外部就能随意修改对象里的数据。比如考试成绩本来应该在 0~100 之间,要是外部直接写出 stu.score = -50; 或 stu.score = 999;,类本身根本没法阻止。
为了合理控制类中各个成员的访问范围,C++ 提供了访问修饰符(Access Specifier)。常用的访问修饰符共有 3 种:① public(公有);② private(私有);③ protected(受保护)。
C++ 三种访问修饰符
在刚接触类与对象时,为了方便演示,我们把成员都放在了 public: 下面。实际上,我们可以在 class 的花括号内部按需使用不同的访问修饰符,每个访问修饰符后面都紧跟一个英文冒号(:)。
语法:
class 类名 {
public:
// 公有成员
private:
// 私有成员
protected:
// 受保护成员
};说明:
当某个访问修饰符出现后,它下方的所有成员都会采用该访问权限,直到遇到下一个访问修饰符或者类定义结束为止。成员变量和成员函数都可以放在任意访问区域中,同一个访问修饰符在一个类里也可以出现多次。
这 3 种访问修饰符的具体作用如下:
- public(公有):在当前类的内部和类的外部(如
main()函数中)都可以直接访问,对外提供的成员函数通常放在这里。 - private(私有):只能在当前类自己的成员函数内部直接访问,在类的外部不能通过对象直接访问,需要保护的成员变量通常放在这里。
- protected(受保护):在类的外部同样不能直接访问,但允许子类的成员函数直接访问。
在还没有学习类的继承之前,protected 和 private 的表现完全一样,在类的外部都不能直接访问。所以现阶段编写单个类时,小伙伴们只需要重点掌握 public 和 private 这两个就可以了。
另外,大家不要把 private 误解成 “这个变量不能用了”。它只是不允许类外的代码直接读写,在类的内部,我们依然可以通过 public 成员函数去正常读写 private 成员变量。
这 3 种访问修饰符的访问权限如下表所示。
| 访问修饰符 | 当前类内部 | 子类内部 | 类的外部 |
|---|---|---|---|
| public(公有) | 可以访问 | 可以访问 | 可以访问 |
| protected(受保护) | 可以访问 | 可以访问 | 不能访问 |
| private(私有) | 可以访问 | 不能访问 | 不能访问 |
注意 如果在定义成员时没有写任何访问修饰符,class 中的成员默认是 private,而 struct 中的成员默认是 public。
下面我们通过两个简单的例子,来看看 public 和 private 成员在类内与类外的访问差别。
示例 1:访问公有与私有成员
#include <iostream>
#include <string>
class Student {
public:
std::string name; // 公有成员变量
void showInfo() {
std::cout << "姓名:" << name << '\n';
std::cout << "成绩:" << score << '\n';
}
private:
int score = 90; // 私有成员变量
};
int main() {
Student stu;
stu.name = "Tom"; // 类外访问 public 成员
stu.showInfo(); // 调用 public 成员函数
return 0;
}运行结果如下。
姓名:Tom
成绩:90分析:
在这个例子中,name 位于 public: 区域,我们在 main() 函数中可以直接写出 stu.name = "Tom";。而 score 位于 private: 区域,虽然不能在类外直接访问,但类自己的成员函数 showInfo() 依然可以直接读取并打印它。
反过来,如果我们尝试在类的外部直接通过对象去读写 private 私有成员,编译时就会报错。请看下面例子。
示例 2:类外访问私有成员报错
#include <iostream>
#include <string>
class Student {
public:
std::string name; // 公有成员变量
private:
int score = 90; // 私有成员变量
};
int main() {
Student stu;
stu.name = "Tom"; // 正常访问公有成员
stu.score = 95; // 错误:不能访问私有成员
return 0;
}运行结果如下。
(报错)error: 'int Student::score' is private within this context分析:
stu.score = 95; 这句代码尝试在 main() 函数中直接修改私有成员 score。由于 private 成员只允许在 Student 类自己的成员函数内部访问,类的外部无权直接读写,所以编译时直接报错,提示 “is private(属于私有成员)”。
使用 set 和 get 函数封装数据
既然 private 成员变量不能在类的外部直接读写,那在实际开发中,我们应该怎么修改和获取它的值呢?
常用的做法是把需要保护的成员变量放进 private 区域,再对外提供公开的 public 成员函数负责读写。习惯上,我们把修改私有成员的函数命名为 setXXX()(如 setScore()),把读取私有成员的函数命名为 getXXX()(如 getScore())。
换成通过成员函数来读写之后,我们就可以在 setScore() 内部加上条件判断,把不合理的数值拦截在外面。下面我们来看一个例子。
示例 3:使用 set 和 get 函数
#include <iostream>
class Student {
private:
int score = 0; // 私有成员变量
public:
// 修改成绩
void setScore(int newScore) {
if (newScore >= 0 && newScore <= 100) {
score = newScore;
} else {
std::cout << "成绩无效,需在 0~100 之间\n";
}
}
// 获取成绩
int getScore() {
return score;
}
};
int main() {
Student stu;
stu.setScore(95); // 传入合法成绩
std::cout << "当前成绩:" << stu.getScore() << '\n';
stu.setScore(120); // 传入非法成绩
std::cout << "当前成绩:" << stu.getScore() << '\n';
return 0;
}运行结果如下。
当前成绩:95
成绩无效,需在 0~100 之间
当前成绩:95分析:
这里 setScore() 和 getScore() 是对外公开的两个 public 成员函数,专门作为外部读写私有成员 score 的通道。
当我们第 1 次调用 stu.setScore(95) 时,95 处于 0~100 之间,score 被正常改为 95。第 2 次调用 stu.setScore(120) 时,120 超出了合理范围,函数给出提示并拒绝修改,因此 score 保持原来的 95 不变。
此外,这种写法还能灵活控制读写权限。比如学生的学号一旦确定就不希望再被外部更改,我们只需要提供读取数据的 get 函数,不提供修改数据的 set 函数,就能实现外部 “只读” 的效果。
像这样把内部数据放进 private 保护起来,只对外提供受控的 public 成员函数来操作数据,就是面向对象编程中常说的 “封装(Encapsulation)”。
常见问题
1. 定义类的时候,所有成员变量都必须设为 private 并编写 set 和 get 函数吗?
不需要。如果我们只是把几项简单数据打包在一起(如二维坐标的 x 和 y),没有任何数值范围限制,直接设为 public 或使用结构体会更简洁。只有当数据不希望被外部随意修改,或者需要在修改时检查合法性时,我们才需要将其设为 private。
2. 在一个类中,public 和 private 区域的先后顺序有规定吗?
C++ 语法本身对先后顺序没有硬性规定,先写 private 还是先写 public 都可以正常编译。有人习惯把 public 写在前面,方便先看清对外接口。也有人习惯先把 private 成员变量列在开头,后面再写 public 成员函数,同一个项目内部保持统一即可。
