C++ / ACM 自学笔记 · v5.0

(二) 封装与访问控制

学生记录能保存成绩后,还要阻止外部写入 -1 或 101。先用公开方法检查范围,再说明哪些成员应允许外部直接访问。

封装是把数据及其操作组织起来,通过接口维护对象的约束。例如成绩只能在 0 到 100 之间,就让修改成绩的方法统一检查范围。

权限 类内部 派生类成员 类外普通代码
public 可访问 可访问 可访问
protected 可访问 可以按保护成员规则访问 不可直接访问
private 可访问 不可直接访问 不可直接访问

如果希望某个外部函数或类也能访问非公开成员,可以把它声明为友元。普通成员访问仍按上表中的权限进行。

这里维护的对象不变量是 0 <= score_ <= 100。 成员先用已学的默认初值设为 0;每次修改只允许通过 setScore 检查范围。因此,只经公开接口操作的对象始终保持合法成绩。

例如当前成绩为 85,调用 setScore(90) 会变成 90;调用 setScore(-1) 则先抛出异常,没有执行后面的赋值,成绩保持修改前的值。调用者需要处理异常,否则异常继续向外传播。private 限制的是访问途径,不会自动判断成绩是否合法,setScore 中的判断负责检查成绩数值。

本例用到的异常接口: <stdexcept> 的 invalid_argument 表示参数不满足约定;throw invalid_argument("...") 抛出带文字说明的异常,可用 catch(const invalid_argument& error) 捕获,再用 error.what() 读取说明。判断在赋值之前,因此抛出异常不会写入非法成绩。

// 数据成员设为 private,修改成绩统一经过 setScore 的范围检查。
#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;

class Student {
  private:
    string name_;
    int score_ = 0; // 对象创建时先给合法初值。

  public:
    void setName(const string &name) {
        name_ = name; // 先用已经学过的赋值,不要求先掌握构造函数。
    }

    void setScore(int score) {
        if (score < 0 || score > 100) {
            throw invalid_argument("成绩必须在 0 到 100 之间");
        }
        score_ = score;
    }

    int score() const {
        return score_;
    }

    const string &name() const {
        return name_;
    }
};

int main() {
    Student student;
    student.setName("Alice");
    student.setScore(85);
    cout << student.name() << ' ' << student.score() << '\n';
}

外部不能直接写 student.score_ = -1,只能调用公开接口。name() 返回的引用仍依赖 student 的生命周期,不能在对象销毁后继续使用。

查阅笔记正文与全部题解 · Ctrl / ⌘ K