在面向对象编程中,封装是三大特性(封装、继承、多态)的基础,它像一个 "黑盒子",将数据和操作数据的方法捆绑在一起,隐藏内部实现细节,只通过预设接口与外部交互。今天我们通过两个经典案例 ------立方体类 和点与圆的位置关系,来聊聊 C++ 中封装的具体应用。
一、案例一:立方体类 ------ 数据与行为的有机结合
需求背景
我们需要设计一个立方体类,实现以下功能:
- 管理立方体的长、宽、高
- 计算立方体的表面积和体积
- 判断两个立方体是否完全相同
类的封装设计
立方体类的核心是 "数据隐藏" 和 "接口开放",我们来看具体实现:
cpp
class cube
{
private:
// 私有成员:隐藏内部数据,不允许直接修改
int L; // 长
int W; // 宽
int H; // 高
public:
// 公共接口:通过方法操作数据,控制访问权限
// 设置与获取长、宽、高
void setL(int a) { L = a; }
int getL() { return L; }
void setW(int a) { W = a; }
int getW() { return W; }
void setH(int a) { H = a; }
int getH() { return H; }
// 计算表面积(行为封装)
int cubeS() { return 2 * ((L * W) + (L * H) + (W * H)); }
// 计算体积(行为封装)
int cubeV() { return L * W * H; }
// 成员函数判断两个立方体是否相同
bool Issample(cube c) {
return (L == c.getL()) && (W == c.getW()) && (H == c.getH());
}
};
封装的体现
-
数据隐藏 :长、宽、高(L、W、H)被声明为
private,外部无法直接修改(比如不能直接写c1.L = 10),只能通过setL()等方法间接设置,这为后续添加数据校验(比如防止负数)预留了空间。 -
行为封装 :计算表面积(
cubeS())和体积(cubeV())的逻辑被封装在类内部,外部只需调用方法即可,无需关心具体公式(即使公式修改,外部调用方式也无需改变)。 -
接口设计 :
setXxx()和getXxx()构成了标准的 "设置 - 获取" 接口,清晰定义了外部与类的交互方式。
扩展:立方体比较的两种方式
案例中还提供了两种判断立方体是否相同的方法:
- 成员函数
Isyiyang(cube c):通过当前对象访问自身私有成员,通过参数对象的getXxx()访问其私有成员。 - 全局函数
Issmple(cube c1, cube c2):完全通过getXxx()接口访问两个对象的私有成员。
两种方式各有场景:成员函数更适合与当前对象强相关的操作,全局函数更适合处理多个对象间的关系。
二、案例二:点和圆的位置关系 ------ 类的组合与封装协作
需求背景
设计点和圆两个类,实现:
- 管理点的坐标(x, y)和圆的半径、圆心(圆心是一个点)
- 判断点与圆的位置关系(点在圆上、圆内、圆外)
类的封装设计
这个案例的核心是类的组合(圆包含点对象),通过封装实现职责分离:
cpp
// 点类:专注管理点的坐标
class Point
{
private:
int m_X; // x坐标
int m_Y; // y坐标
public:
void setX(int x) { m_X = x; }
int getX() { return m_X; }
void setY(int y) { m_Y = y; }
int getY() { return m_Y; }
};
// 圆类:包含点对象作为圆心,管理半径
class circle
{
private:
int m_R; // 半径
Point m_Center; // 圆心(点对象,体现类的组合)
public:
void setR(int r) { m_R = r; }
int getR() { return m_R; }
void setCenter(Point center) { m_Center = center; }
Point getCenter() { return m_Center; }
};
位置关系判断的封装
判断点和圆的关系被封装为全局函数,通过类的公共接口获取数据:
cpp
void isInCircle(circle &c, Point &p)
{
// 计算点到圆心的距离平方(避免开方运算,提高效率)
int distance = pow(c.getCenter().getX() - p.getX(), 2) +
pow(c.getCenter().getY() - p.getY(), 2);
// 计算半径的平方
int rDistance = pow(c.getR(), 2);
// 判断位置关系
if (distance == rDistance) cout << "点在圆上" << endl;
else if (distance > rDistance) cout << "点在圆外" << endl;
else cout << "点在圆内" << endl;
}
封装的体现
-
职责单一:点类只负责管理坐标,圆类只负责管理半径和圆心,两者通过接口交互,互不干扰。
-
组合的灵活性 :圆类通过包含
Point对象作为成员,复用了点的功能,体现了 "组合优于继承" 的设计思想(当需要 "有一个" 关系时,组合更合适)。 -
接口依赖 :判断函数
isInCircle完全依赖类的公共接口(getX()、getR()等),即使点或圆的内部实现修改(比如坐标改用double类型),只要接口不变,判断函数就无需修改。
三、两个案例的共性与封装的核心价值
共性总结
- 都将数据(成员变量)设为私有,通过公共方法(成员函数)控制访问。
- 都隐藏了内部实现细节(比如表面积公式、距离计算逻辑),只暴露必要接口。
- 都通过封装提高了代码的可维护性(修改内部逻辑不影响外部调用)。
封装的核心价值
- 数据安全 :防止外部随意修改内部数据(比如立方体的长不能为负数,可在
setL()中添加if(a>0) L=a实现校验)。 - 接口稳定:外部只需关注接口如何使用,无需关心内部实现,降低耦合度。
- 代码复用:像点类这样的基础组件,可在多个场景中复用(比如后续扩展椭圆、矩形等图形)。
- 职责清晰:每个类专注于自身功能,符合 "单一职责原则"。
结语
从立方体的属性管理到点与圆的关系判断,封装让代码从 "面向过程的逻辑堆砌" 转变为 "面向对象的组件协作"。在实际开发中,合理的封装能让代码更健壮、更易扩展 ------ 记住,好的封装就像一个设计精良的工具,用户只需知道如何使用,无需知道内部构造。