封装指将数据与操作数据的函数绑定,并隐藏实现细节,仅通过公开接口与外部交互。C++ 用 public、protected、private 访问说明符控制成员可见性。本节说明三类说明符的含义、通过成员函数维护不变量,以及友元与多文件组织的入门。
访问说明符
| 说明符 | 类外访问 | 派生类访问 |
|---|---|---|
public | 允许 | 允许(对 public 基类成员) |
protected | 不允许 | 允许 |
private | 不允许 | 不允许(除非友元) |
class 内未写说明符的成员默认为 private;struct 默认为 public。
通过接口维护不变量
#include <iostream>
class Account {
public:
void deposit(double amount) {
if (amount > 0) {
balance_ += amount;
}
}
bool withdraw(double amount) {
if (amount > 0 && amount <= balance_) {
balance_ -= amount;
return true;
}
return false;
}
double balance() const { return balance_; }
private:
double balance_ = 0.0;
};
int main() {
Account a;
a.deposit(100);
std::cout << a.balance() << "\n";
return 0;
}典型输出:100。外部不能直接改 balance_,业务规则集中在成员函数内校验。
getter 与 setter
只读属性用 const 成员函数取出私有数据(getter);需要改写时,用另一组成员函数校验后再赋给私有成员(setter)。不是每个字段都要配 setter,很多类型只提供只读接口。
#include <iostream>
#include <string>
class Person {
std::string name_;
int age_ = 0;
public:
const std::string& name() const { // getter:不修改对象
return name_;
}
void set_name(const std::string& n) {
if (!n.empty()) {
name_ = n;
}
}
int age() const {
return age_;
}
void set_age(int a) { // setter:拒绝不合理的年龄
if (a >= 0 && a < 150) {
age_ = a;
}
}
};
int main() {
Person p;
p.set_name("Ada");
p.set_age(20);
p.set_age(-1); // 不合法,age_ 保持 20
std::cout << p.name() << " " << p.age() << std::endl;
return 0;
}输出:
Ada 20name()、age() 只读;set_name 拒绝空串,set_age(-1) 被丢掉,所以年龄仍是 20。类外不能写 p.age_ = -1。
protected 与继承
protected 成员对派生类可见、对外不可见,用于向子类开放扩展点而不破坏封装。在继承层次中设计基类接口时,宜区分 public API 与 protected 钩子函数。
友元(入门)
friend 函数或类可访问私有成员,有意破坏封装边界。适用于运算符重载、紧密耦合组件或测试夹具;应控制范围,避免大面积友元导致维护困难。
class Engine {
friend void tune(Engine& e);
private:
int rpm_ = 0;
};接口与实现分离
头文件常只声明 public 接口;私有成员函数与复杂实现可放在 .cpp 源文件,缩短编译依赖,与头文件与多文件组织一致。PIMPL(指针指向实现)是进一步隐藏细节的惯用法,属进阶主题。
封装与多态
公开接口可设计为抽象基类(纯虚函数),实现细节藏在派生类 private 区,调用方仅依赖虚函数接口。这与数据成员的 private 封装互补。
本节说明了访问控制与封装意图。下一节介绍继承:基类与派生类及 public 继承语义。