在 C++ 中,封装 是面向对象编程的三大核心特性之一(封装、继承、多态),其核心思想“将数据(成员变量)和操作数据的方法(成员函数)捆绑在一起,并隐藏内部实现细节,仅通过公开的接口与外界交互”**。

一、封装的本质:数据隐藏与接口暴露

封装的目的是保护数据的安全性降低代码的耦合度

  • 隐藏内部细节:将类的核心数据(成员变量)和实现逻辑设为私有(private),防止外界直接修改或误操作。
  • 暴露安全接口:通过公开的成员函数(public)提供对数据的访问和操作,这些接口可以包含校验逻辑,确保数据的合法性。

二、封装的实现:访问修饰符

C++ 通过访问修饰符publicprivateprotected)实现封装,控制类成员的访问权限:

  • private(私有):仅类内部的成员函数可访问,外界(包括派生类)无法直接访问。
  • public(公有):类内部、外界、派生类均可访问,通常用于定义对外接口。
  • protected(保护):类内部和派生类可访问,外界不可访问(主要用于继承场景)。

三、封装的示例:学生类

以“学生”类为例,封装学生的“学号”(不可修改)和“成绩”(可修改但需校验):

#include <iostream>
#include <string>
using namespace std;

class Student {
private:
// 私有成员:数据隐藏(外界不可直接访问)
int id; // 学号(一旦确定不可修改)
double score; // 成绩(需在0-100之间)
string name; // 姓名

public:
// 公有接口:对外暴露的操作方法
// 1. 构造函数:初始化私有成员(唯一设置学号的地方)
Student(int studentId, string studentName) : id(studentId), name(studentName) {
score = 0; // 初始成绩为0
}

// 2. 读取学号(仅提供读接口,不提供写接口,保证学号不可改)
int getId() const {
return id;
}

// 3. 读取和设置成绩(设置时带校验逻辑)
double getScore() const {
return score;
}

void setScore(double s) {
// 校验成绩合法性:仅当0<=s<=100时才更新
if (s >= 0 && s <= 100) {
score = s;
} else {
cout << "成绩无效(必须在0-100之间)" << endl;
}
}

// 4. 读取和设置姓名
string getName() const {
return name;
}

void setName(string n) {
name = n;
}
};

int main() {
Student s(1001, "Alice"); // 创建对象,初始化学号和姓名

// 正确:通过公有接口访问和操作数据
s.setName("Alice Smith"); // 修改姓名
s.setScore(95); // 设置有效成绩
cout << "学号:" << s.getId() << endl; // 读取学号
cout << "姓名:" << s.getName() << endl; // 读取姓名
cout << "成绩:" << s.getScore() << endl; // 读取成绩

// 错误:直接访问私有成员(编译报错,体现封装的保护作用)
// s.id = 1002; // 无法修改学号
// s.score = -5; // 无法直接设置无效成绩

// 测试无效成绩
s.setScore(150); // 输出:成绩无效(必须在0-100之间)
return 0;
}

四、封装的核心优势

  1. 数据安全
    私有成员无法被外界直接修改,只能通过公开接口操作,而接口可以包含校验逻辑(如成绩必须在0-100之间),避免数据被非法篡改。

  2. 隐藏实现细节
    外界只需知道如何通过接口使用类(如调用 setScore 设置成绩),无需关心类内部如何存储和处理数据(如成绩的存储方式)。如果后续修改内部实现(如将 score 改为 int 类型),只要接口不变,外界代码无需修改。

  3. 降低耦合度
    类的内部变化不会影响依赖它的外部代码,减少了代码间的关联,提高了代码的可维护性。

  4. 代码复用
    封装好的类可以作为独立模块被多次复用(如上述 Student 类可在成绩管理系统的多个地方使用)。