跳转至

05-类与对象:从“数据+函数”到抽象

目录页:见 C++ 小白教程目录

目标

  • 理解类/对象:把数据与操作打包成一个整体
  • 会写最基本的类:成员变量、成员函数、构造函数
  • 理解 public/private 的意义(封装)
  • 会用对象组织一段小程序的逻辑

先决知识

  • 会写函数并理解作用域(见 02 章)
  • 理解引用参数能修改外部变量(见 04 章 5)

1. 为什么需要类:让“数据”跟“规则”绑在一起

如果你只用函数和变量,程序一大就会出现这种问题:

  • 数据到处都是,谁都能改
  • 改坏了你也不知道是谁改的
  • 相关操作分散在很多函数里,看不出它们属于同一个东西

类就是为了解决这个问题:

  • 专业名称:类(class)与对象(object)
  • 类比:类像“图纸/模具”,对象像“按图纸做出来的实体”
  • 作用:把“数据(状态)+ 对数据的操作(行为)”打包,形成清晰边界
  • 规则/坑点:封装不是为了“难用”,而是为了让错误更难发生

2. 一个最小的类:Counter(计数器)

可运行例子:

#include <iostream>

class Counter {
public:
    Counter() : value_(0) {}

    void add(int delta) {
        value_ += delta;
    }

    void reset() {
        value_ = 0;
    }

    int value() const {
        return value_;
    }

private:
    int value_;
};

int main() {
    Counter c;
    c.add(3);
    c.add(5);
    std::cout << "value=" << c.value() << '\n';
    c.reset();
    std::cout << "value=" << c.value() << '\n';
    return 0;
}

把这段拆开(只讲第一次出现的术语):

  • 专业名称:成员变量(data member / member variable)
  • 类比:对象身上的“私人物品”
  • 作用:保存对象状态
  • 专业名称:成员函数(member function / method)
  • 类比:对象能做的“动作”
  • 作用:在类内部实现对状态的操作
  • 专业名称:访问控制(access control):public / private
  • 类比:public 是对外公开窗口,private 是内部仓库
  • 作用:把“允许外界怎么用”固定下来,减少误用
  • 专业名称:构造函数(constructor)
  • 类比:对象出生时的“初始化流程”
  • 作用:保证对象一创建就处于可用状态
  • 规则/坑点:构造函数没有返回类型,名字与类名相同
  • value() const
  • 这里的 const 表示:这个成员函数承诺“不修改对象状态”

3. 构造函数与初始化列表:对象出生时把东西装好

你看到的 Counter() : value_(0) {} 叫初始化列表:

  • 专业名称:成员初始化列表(member initializer list)
  • 类比:开箱时把物品按清单放到固定位置,而不是开箱后再到处塞
  • 作用:更高效、更明确地初始化成员
  • 规则/坑点:推荐优先用初始化列表来初始化成员

再看一个带参数的构造函数:

#include <iostream>

class Counter {
public:
    Counter(int start) : value_(start) {}

    void add(int delta) { value_ += delta; }
    int value() const { return value_; }

private:
    int value_;
};

int main() {
    Counter c(10);
    c.add(7);
    std::cout << "value=" << c.value() << '\n';
    return 0;
}

4. this 指针:成员函数怎么知道“我是谁”

当你写 c.add(3) 时,add 里需要知道要改的是哪个对象的 value_

  • 专业名称:this 指针(this pointer)
  • 类比:每个对象在执行自己的方法时,都有一个“指向自己的地址纸条”
  • 作用:在成员函数内部区分“当前对象”与其他对象
  • 规则/坑点:大多数时候你不需要显式写 this->,但知道它存在很重要

可运行例子(用链式调用直观看到 this 的作用):

#include <iostream>

class Counter {
public:
    Counter() : value_(0) {}

    Counter& add(int delta) {
        value_ += delta;
        return *this;
    }

    int value() const { return value_; }

private:
    int value_;
};

int main() {
    Counter c;
    c.add(1).add(2).add(3);
    std::cout << "value=" << c.value() << '\n';
    return 0;
}

return *this; 的意思是:返回当前对象本身(引用),所以可以连着调用。


5. 一个更像“真实业务”的类:BankAccount

目标:外面不能随便把余额改成负数,必须走类提供的规则。

#include <iostream>
#include <string>

class BankAccount {
public:
    BankAccount(std::string owner, int balance)
        : owner_(owner), balance_(balance) {}

    const std::string& owner() const { return owner_; }
    int balance() const { return balance_; }

    void deposit(int amount) {
        if (amount <= 0) {
            return;
        }
        balance_ += amount;
    }

    bool withdraw(int amount) {
        if (amount <= 0) {
            return false;
        }
        if (amount > balance_) {
            return false;
        }
        balance_ -= amount;
        return true;
    }

private:
    std::string owner_;
    int balance_;
};

int main() {
    BankAccount acc("alice", 100);
    acc.deposit(50);

    bool ok1 = acc.withdraw(120);
    bool ok2 = acc.withdraw(20);

    std::cout << "owner=" << acc.owner() << '\n';
    std::cout << "balance=" << acc.balance() << '\n';
    std::cout << "withdraw120=" << ok1 << '\n';
    std::cout << "withdraw20=" << ok2 << '\n';
    return 0;
}

这就是封装的价值:余额只能通过 deposit/withdraw 改,而且规则集中在一个地方。


常见坑

  • 错误:把成员变量都设成 public
    结果:外部随便改,类的规则形同虚设
    正确:成员变量一般放 private,对外提供受控接口

  • 错误:构造函数不初始化成员就开始用
    结果:对象状态不可靠
    正确:用构造函数 + 初始化列表保证“出生就可用”

  • 错误:在 const 成员函数里修改成员
    结果:编译失败(这是好事)
    正确:区分“查询函数(const)”和“修改函数(非 const)”


小练习

练习 1:写一个 Student 类

  • 题目:保存 namescore,提供 setScoreisPassprint
  • 输入/输出:在 main 里创建对象并输出信息
  • 约束:score 只能是 0~100,超出范围不修改
  • 提示:isPass 返回 bool(60 及格)
  • 目标:练成员变量封装与成员函数设计

练习 2:写一个 Rectangle 类

  • 题目:保存宽高,提供 areaperimeterscale(factor)
  • 输入/输出:创建对象,输出面积周长,缩放后再输出
  • 约束:宽高不能为负
  • 提示:scale 修改对象自身(非 const)
  • 目标:练 const 成员函数与非 const 成员函数区分

练习 3:Counter 增强版

  • 题目:支持 addsubresetvalue,并支持链式调用
  • 输入/输出:在 main 里写一行链式调用并输出
  • 约束:不允许 value 变成负数(小于 0 时保持 0)
  • 提示:链式调用用 return *this;
  • 目标:练 this 与返回引用

练习 4:银行账户加转账

  • 题目:给 BankAccount 增加 transferTo(BankAccount& other, int amount)
  • 输入/输出:创建两个账户,转账后输出两个余额
  • 约束:转账失败不能改变任何一方余额
  • 提示:先检查,再修改
  • 目标:练“一个对象操作另一个对象”(引用参数)

小结

你现在应该掌握:

  • 类是图纸,对象是实体;成员变量是状态,成员函数是行为
  • public/private 用来做封装,让错误更难发生
  • 构造函数与初始化列表保证对象创建后就可用
  • const 成员函数用于“只读操作”,不允许修改对象状态

下一篇会讲资源管理:你会看到“对象生命周期”如何帮我们自动管理资源,避免内存泄漏与悬空指针。