封装的理解

一、封装的概念

封装就是将抽象得到的数据和行为(或功能)相结合,形成一个有机整体——也就是将数据和操作数据的函数代码进行有机结合,构成”类”,其中数据和函数都是类的成员。

简单来说,封装就是把抽象阶段分离出来的”是什么”(数据)和”能做什么”(行为)打包到一个 {} 中,变成一个完整的类。

二、封装的实现方式

在 C++ 中,封装通过以下机制实现:

机制作用
class 关键字 + {}划定类的边界,将数据和函数组合在一起
访问限定符 public / private控制成员的访问权限,区分对外接口和内部实现
成员函数提供外部与类交互的唯一通道

三、示例:时钟类的封装

基于上一节的抽象结果,将时钟的数据和功能封装成一个完整的类:

class Clock
{
private:                        // 私有成员:外部无法直接访问
    int hour;
    int minute;
    int second;

public:                         // 公有成员:外部接口
    void setTime(int h, int m, int s)
    {
        if (h >= 0 && h < 24)
            hour = h;
        else
            hour = 0;

        if (m >= 0 && m < 60)
            minute = m;
        else
            minute = 0;

        if (s >= 0 && s < 60)
            second = s;
        else
            second = 0;
    }

    void showTime()
    {
        if (hour < 10) cout << "0";
        cout << hour << ":";

        if (minute < 10) cout << "0";
        cout << minute << ":";

        if (second < 10) cout << "0";
        cout << second << endl;
    }
};Code language: JavaScript (javascript)

分析:

  • {} 限定了类的边界,hour、minute、second 三个数据成员和 setTime、showTime 两个函数成员被封装在 Clock 类内部。
  • private 声明的三个整型数据是类的私有数据,外部代码无法直接读写。例如不能在类外写 c.hour = 10;,编译器会报错。
  • public 声明的两个函数是类对外的接口,外界只能通过调用 setTime() 和 showTime() 与 Clock 类发生联系。
  • setTime 内部包含了对数据的合法性校验(如小时不能超过23),这是封装带来的好处——数据如何被修改、修改时有什么约束,全部由类自己控制。

四、封装的意义

方面说明
数据保护私有数据不能被外部随意修改,只有通过公有接口才能访问,避免非法数据破坏对象状态
隐藏实现外部只需要知道”能做什么”(接口),不需要知道”怎么做”(内部实现),降低使用难度
降低耦合类内部实现改变不影响外部调用代码,只要接口不变,外部代码无需修改
简化使用使用者只需关注公有接口,不必关心类内部复杂的逻辑
增强安全性配合数据校验,确保对象始终处于合法状态

五、代码示例

以下代码演示封装在实际使用中的效果:

#include <iostream>
using namespace std;

class Clock
{
private:
    int hour;
    int minute;
    int second;

public:
    void setTime(int h, int m, int s)
    {
        if (h >= 0 && h < 24)
            hour = h;
        else
            hour = 0;

        if (m >= 0 && m < 60)
            minute = m;
        else
            minute = 0;

        if (s >= 0 && s < 60)
            second = s;
        else
            second = 0;
    }

    void showTime()
    {
        if (hour < 10) cout << "0";
        cout << hour << ":";

        if (minute < 10) cout << "0";
        cout << minute << ":";

        if (second < 10) cout << "0";
        cout << second << endl;
    }

    void tick()
    {
        second++;
        if (second >= 60)
        {
            second = 0;
            minute++;
            if (minute >= 60)
            {
                minute = 0;
                hour++;
                if (hour >= 24)
                    hour = 0;
            }
        }
    }
};

int main()
{
    Clock c;

    // 通过公有接口设置时间
    c.setTime(25, 61, 70);   // 传入非法值
    cout << "设置非法时间后:";
    c.showTime();            // 输出 00:00:00(被校验修正为默认值)

    c.setTime(9, 30, 45);
    cout << "设置合法时间后:";
    c.showTime();            // 输出 09:30:45

    // 模拟走时
    for (int i = 0; i < 5; i++)
    {
        c.tick();
    }
    cout << "5秒后:";
    c.showTime();

    // c.hour = 100;   // 错误:hour 是 private,外部无法直接访问

    return 0;
}Code language: PHP (php)

分析:

  • 调用 setTime(25, 61, 70) 传入非法值,但由于封装了数据校验逻辑,内部自动修正为 0:0:0,对象状态不会被破坏。
  • c.hour = 100; 如果取消注释会编译报错,证明私有数据确实被保护起来了。
  • 外部代码完全不需要知道时间是如何存储、如何校验的,只需调用接口即可。

六、访问限定符详解

限定符类内部类外部(通过对象)派生类中说明
public可访问可访问可访问对外接口
private可访问不可访问不可访问内部实现细节
protected可访问不可访问可访问继承相关(后续讲解)

注意:

  • class 默认访问级别是 private(不写限定符时成员为私有)。
  • struct 默认访问级别是 public(C 语言兼容,不推荐用于面向对象设计)。
  • 访问限定符可以出现多次,每次改变生效范围直到下一个限定符或类结束。
class Example
{
public:
    int a;          // a 是公有
private:
    int b;          // b 是私有
public:
    void func1();   // func1 是公有
private:
    int c;          // c 是私有
};Code language: PHP (php)

七、封装的设计原则

  • 数据成员尽量设为 private:除非有充分理由暴露,否则不要公开数据成员。
  • 对外接口尽量精简:只暴露必要的操作,隐藏一切可以隐藏的细节。
  • 接口设计要稳定:公有接口一旦确定并投入使用,应尽量保持不变,因为修改接口会影响所有调用者。
  • 内部实现可以自由修改:只要接口不变,私有成员可以随意增删改,不影响外部代码。
  • 在接口中加入必要的参数校验:防止非法数据进入对象内部。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注