跳转至

类与对象的声明

class 语法开始,把对象思维落到可编译的 C++ 代码中。

类是用户自定义类型

整形、浮点型、字符型、布尔型是高级程序设计语言中常见的基本数据类型。 这些基本数据类型不足以表达复杂的数据结构和业务规则。 设计类就是设计一种新的类型。每个类就是面向对象程序的基本组成模块。

  • 基本数据类型有明确规则,例如 int 能参与算术运算。
  • 自定义的类也应该有明确规则,例如一个 Clock 对象应该如何被创建、修改和显示

设计类的核心目标

一个良好设计的类能让调用者少记规则,少犯错误。

类的基本语法

class 内部可以声明数据成员和成员函数。

类的基本语法结构

1
2
3
4
5
6
7
8
class 类名 {
public:
    // 公有成员,用于对外接口
private:
    // 私有成员,用于内部实现
protected:
    // 保护成员
};  // 花括号后的分号不能少

语法要点

  • 分号不能少:类定义结束后需要一个分号。
  • 访问控制publicprivate 决定类内成员对外是否可见。
  • 成员:成员可以是数据,也可以是函数。

Clock 类第一版

先把状态和行为放进同一个类,再逐步增强规则。

Clock 类的声明与实现

class Clock {
public:
    void setTime(int h, int m, int s);
    void show() const;
private:
    int hour;
    int minute;
    int second;
};

void Clock::setTime(int h, int m, int s) {
    hour = h;
    minute = m;
    second = s;
}

void Clock::show() const {
    cout << hour << ':' << minute << ':' << second;
}
class Clock {
public:
    void setTime(int h, int m, int s) {
        hour = h;
        minute = m;
        second = s;
    }

    void show() const {
        cout << hour << ':' << minute << ':' << second;                
    }

private:
    int hour;
    int minute;
    int second;
};

关键设计点

  • public:外部可以访问或调用的接口。
  • private:对象内部保存的状态和功能,外部不可直接访问。
  • const 成员函数show() 不应该修改对象状态,所以用 const 修饰,也称为常成员函数

说明

publicprivate不仅可以用于修饰函数成员,也可以用于修饰数据成员。 但从封装的设计思想出发,应该尽量减少public所修饰的部分,仅对外公开需要公开的部分。

对象的声明

类是类型,对象是这个类型的具体实例。

创建和使用对象

1
2
3
4
5
6
7
8
9
int main() {
    Clock c1;
    Clock c2;

    c1.setTime(8, 30, 0);
    c2.setTime(14, 5, 30);

    return 0;
}

类与对象的关系

  • 同一数据类型c1c2 都是 Clock 类型的对象,即Clock类的实例。
  • 独立内部状态:每个对象保存自己的 hourminutesecond
  • 共享功能逻辑:它们使用同一套成员函数定义。

成员访问运算符 “.

通过对象名和点运算符调用公开成员函数。

通过对象调用公开接口

1
2
3
4
5
6
7
8
9
int main() {
    Clock c;
    c.setTime(8, 30, 0);
    c.show();

    // c.hour = 8;  // hour 是 private 成员,类外不能直接访问其内容

    return 0;
}

访问规则

  • 公开接口:可以通过“对象名.成员名”调用 public 成员函数。
  • 私有成员private 数据不能被类外直接访问。
  • 设计意图:让外部通过对象允许的方式办事。

类作用域与成员可见性

理解类的作用域(Scope)和成员的可见性(Visibility),是正确设计和使用类的基础。

  • 每一个类的定义都引入了一个独立的类作用域。在类作用域内声明的成员(包括数据成员和成员函数),其标识符在该作用域内是可见的。
  • 成员函数体是类作用域的一部分,因此它可以直接访问该类中声明的任何成员,无论其访问权限是 publicprivate 还是 protected

类内访问

在类定义的花括号 {} 内部,包括所声明的成员变量、成员函数等;成员函数可以直接访问本类的全部数据成员,不受成员访问权限的限制。

类作用域内的直接访问

class Clock {
public:
    void setTime(int h, int m, int s) {
        // 在成员函数体内,hour/minute/second 都是可见的,可以直接使用
        hour = h;
        minute = m;
        second = s;
    }
private:
    int hour, minute, second;  // 在类作用域内声明
};

类外访问:作用域与访问控制的共同作用

在类的作用域之外(即类定义的花括号 {} 外部,例如 main 函数或其他普通函数中),类的成员并非不可见,但其可见性和可访问性受到双重限制:

  1. 作用域运算符 :::在类外,需要通过“对象名.成员名”或“类名::静态成员名”的方式,在类的作用域中查找该成员。这是可见性的体现。
  2. 访问控制符:即使成员在类作用域内是可见的,类外代码也必须遵守 publicprivateprotected 的访问控制规则。这是可访问性的规则。

类外访问的规则

int main() {
    Clock c;
    c.setTime(8, 30, 0);  // OK: setTime 是 public,在类外可见且可访问

    c.show();             // OK: show 是 public

    // c.hour = 8;        // Error: hour 是 private,在类外不可访问
    // int h = c.hour;    // Error: 同上

    return 0;
}

可见性与访问控制的区别

为了更清晰地理解,可以将两者分开来看:

概念 核心含义 决定因素 成员函数内 类外(通过对象)
可见性 (Visibility) 能否通过名称引用该成员 作用域规则 全部可见 需要使用 .:: 运算符查找
可访问性 (Accessibility) 引用后,能否执行读写或调用操作 访问控制符 (public/private/protected) 全部可访问 只能访问 public 成员

要点概括

  • 访问控制不是为难调用者,而是保护对象不被错误使用。
  • 对象内部知道如何管理自己,外部只需要知道如何请求服务。
思考题

在 C++ 中,成员函数通常在类定义的花括号 {} 之外实现,这些成员函数的函数体属于“类内”还是“类外”?

答案

成员函数的函数体虽然在词法上写在类外,但它仍然属于类的成员,因此在类的作用域内,具有类内访问权限,可以访问所有私有成员。

C/C++ 对比:公开字段 vs 私有字段

C 写法(字段直接可见) C++ 写法(通过接口修改)
Clock c;
c.hour = 8;
c.minute = 99;
c.second = 0;
Clock c;
// c.hour = 8; // 不能在类外直接访问
c.setTime(8, 30, 0);
风险:任何调用点都可能写入非法值。 收益:状态修改经过统一入口,可进行检查。

C 风格的风险

在 C 语言中,结构体字段完全公开,任何调用者都可以随意修改,导致对象状态可能被破坏。

C++ 风格的收益

在 C++ 中,将数据成员设为 private,强制外部通过 public 成员函数访问,从而在统一入口处进行合法性检查,保护对象状态的完整性。

设计原则:面向接口编程

正是由于类外代码对 private 成员“不可见且不可访问”,类的设计者才能自由地修改内部实现(例如改变数据存储方式),而不会影响使用该类的“外部客户代码”。这就是封装带来的核心价值——通过访问控制实现“接口与实现分离”。

  • 外部视角:只需关注 public 接口,了解“这个对象能做什么”。
  • 内部视角:开发者专注于 private 部分,确保“这个对象如何做好自己的工作”。

内联成员函数

非常简短的函数可以直接在类内定义。

内联成员函数

1
2
3
4
5
6
7
8
class Clock {
public:
    inline void show() const {
        cout << hour << ':' << minute << ':' << second;
    }
private:
    int hour, minute, second;
};

使用建议

  • 适用场景:函数短小、逻辑简单、频繁调用。
  • 保持简洁:不要在内联函数中包含复杂结构,如循环语句和 switch 语句等。
  • 重点理解:将函数体放在类声明中使用 inline 关键字,理解写法即可,优化细节以后再谈。

案例改进:给 Clock 增加合法性检查

把检查逻辑放在修改状态的成员函数中。

带合法性检查的 setTime

bool Clock::setTime(int h, int m, int s) {
    if (h < 0 || h >= 24) return false;
    if (m < 0 || m >= 60) return false;
    if (s < 0 || s >= 60) return false;

    hour = h;
    minute = m;
    second = s;
    return true;
}

改进要点

  • 统一入口:所有时间修改都经过 setTime()
  • 返回结果:调用者可以根据 true/false 处理错误。
  • 类内访问:成员函数可以直接写私有数据。

小结

本节的核心是理解 如何作为自定义类型,将数据和行为封装在一起,并通过访问控制保护对象的内部状态。

  • class 定义了对象的类型对象 是类型的实例
  • public 成员是对象的对外接口private 成员是对象的内部实现
  • 通过成员函数操作私有数据,实现规则集中管理
  • 类内初始值内联函数 让代码更简洁、更安全。

下一步将进入 构造函数与对象初始化,解决对象“出生”时如何获得有效状态的问题。