封装
需要预先学习:
1. 封装
封装(encapsulation)有两层含义:
- 把数据(成员变量)和操作数据的行为(成员函数)打包到类中
- 通过访问权限限定符控制谁能访问这些成员,把实现细节藏起来
在 类与对象 里,成员统一用了 public,任何代码都能直接读写成员变量。这带来一个问题:如果有人给 m_age 赋了 -1,程序照单全收,对象就处于“年龄是负数”的非法状态。封装要做的,就是收起成员的直接访问权,只通过类提供的成员函数来操作数据,由类自己守住数据的合法性。
1.1 访问权限限定符
C++ 提供三种访问权限限定符,用它们把类体分成几个区域:
| 限定符 | 类内部 | 类外部 |
|---|---|---|
public |
可访问 | 可访问 |
private |
可访问 | 不可访问 |
protected |
可访问 | 不可访问 |
private 与 protected 在"类外部不可访问"上是一致的,区别在于继承时派生类能否访问(详见 继承)。本节统一用 private 表示私有成员。
#include <iostream>
#include <string>
class Person
{
public: // 公开接口
int get_age() const
{
return m_age; // 成员函数可以访问私有成员
}
private: // 私有数据
std::string m_name;
int m_age = 0;
};
int main()
{
Person zhangsan;
std::cout << zhangsan.m_age << std::endl; // 错误!类外不能访问私有成员
return 0;
}
编译时报错:
error: 'int Person::m_age' is private within this context
规则:
- 成员函数无论写在类内的哪个区域,都可以访问类的所有成员
- 类外部(如
main中)只能访问public成员 class的成员默认是private,struct的成员默认是public(见下文"struct 与 class")
1.2 通过公开接口操作私有数据
私有成员变量不可直接访问,于是类对外提供一对公开的成员函数来读写它们:
- getter:读取成员变量的值,通常形如
get_age(),声明为const - setter:修改成员变量的值,通常形如
set_age(),可以在其中校验新值的合法性
#include <iostream>
#include <string>
class Person
{
public:
void set_age(int age) // setter:校验后才写入
{
if (age >= 0)
m_age = age;
else
std::cout << "年龄不合法:" << age << std::endl;
}
int get_age() const // getter:只读
{
return m_age;
}
private:
std::string m_name;
int m_age = 0;
};
int main()
{
Person zhangsan;
zhangsan.set_age(20);
std::cout << zhangsan.get_age() << std::endl;
zhangsan.set_age(-1); // 非法值被拦截
std::cout << zhangsan.get_age() << std::endl;
return 0;
}
编译并运行代码后,看到这样的输出:
20
年龄不合法:-1
20
可以看到,-1 被 setter 拦了下来,m_age 始终保持合法。如果不封装,m_age 被直接写成 -1,没人能拦住。
1.3 封装带来的好处
1. 保证数据处于合法状态
数据只能通过 setter 写入,非法值在入口处被拦截,对象不会出现"年龄为负"这类状态。
2. 实现细节可以自由替换
使用者只依赖公开接口,不关心内部如何实现。只要接口不变,类的内部可以随意改造而不影响任何使用者:
class Thermometer
{
public:
double get_celsius() const { return m_t; }
private:
double m_t = 0.0; // 内部存的是摄氏温度
};
如果某天想把内部存储换成开尔文温度,只需改动类内部的几行代码,get_celsius() 的调用方一行都不用动:
class Thermometer
{
public:
double get_celsius() const { return m_t - 273.15; } // 接口不变,内部换算
private:
double m_t = 273.15; // 内部改存开尔文温度
};
3. 降低耦合
使用方的代码只和公开接口打交道,不会碰内部细节,类的改动不会波及使用方,程序的各个部分更容易独立修改和维护。
1.4 struct 与 class
在 C++ 里,struct 和 class 几乎是同一个东西——struct 同样可以有成员函数、构造函数和继承。唯一的区别是默认访问权限:
| 类型 | 默认访问权限 |
|---|---|
struct |
public |
class |
private |
struct Point
{
int m_x; // 默认 public,外部可直接访问
int m_y;
};
class Point2
{
int m_x; // 默认 private,外部不可访问
int m_y;
};
约定俗成的用法:struct 用于简单的数据打包(成员大多是公开的),class 用于需要封装的完整类型。


