CC++ & Algorithm

类的定义与运算符重载

困难13
语言版本:C++
概述:学习如何用C++创建自己的“物品模板”(类),并告诉计算机怎么用加减乘除等符号来处理这些物品(运算符重载)。

类的定义与运算符重载:让C++听懂你的“自定义玩具”

你有没有想过,C++里的 intdouble 这些基本类型能直接做加减乘除,但如果你发明了一种新东西——比如「魔法闹钟」——计算机就不知道 +> 该怎么用了。类(class) 就是让你自己画出“物品设计图”的工具,而运算符重载则是教会计算机用 +> 这些符号去处理你设计的物品。这篇文章用最贴近你生活的例子(零食、游戏、闹钟),一步步带你掌握这两个重要知识。


一、类:你的私人“物品设计图”

想象你要设计一款新游戏,里面有各种角色。每个角色都有名字、血量和攻击力。如果每出现一个角色都单独写一堆变量,代码会变得又臭又乱。就像一张工厂模具,定义好角色应该有哪些属性(数据)行为(函数),然后用这张模具可以批量“生产”出不同的角色实例。

1.1 先看一个简单的类:魔法闹钟

#include <iostream>
#include <string>
using namespace std;

class MagicClock {
public:  // 公开的,外部可以访问
    string color;   // 颜色
    int ringTime;   // 闹铃时间(0~23)
    int volume;     // 音量(0~100)

    // 构造函数:创建闹钟时自动设置属性
    MagicClock(string c, int t, int v) {
        color = c;
        ringTime = t;
        volume = v;
    }

    // 成员函数:显示闹钟信息
    void show() {
        cout << "颜色: " << color << ", 闹铃: " << ringTime << "点, 音量: " << volume << endl;
    }
};

int main() {
    MagicClock a("红色", 7, 50);  // 用设计图造出第一个闹钟
    MagicClock b("蓝色", 8, 60);  // 第二个闹钟
    a.show();  // 输出:颜色: 红色, 闹铃: 7点, 音量: 50
    b.show();  // 输出:颜色: 蓝色, 闹铃: 8点, 音量: 60
    return 0;
}

生活类比:类好比乐高积木的“说明书”,告诉你每个积木有几个凸点、什么颜色。而 ab 就是按说明书拼出来的具体积木块,它们有相同的结构(颜色、闹铃时间、音量),但具体数值不同。

1.2 类的关键点

  • 属性(成员变量):对象拥有的数据,比如闹钟的 colorvolume
  • 方法(成员函数):对象能做的事情,比如 show() 显示信息。
  • 构造函数:名字和类名相同、没有返回值,用于初始化对象。在 new 对象时自动调用。
  • 访问权限public 表示外部可以随便用;private 只允许类内部使用(后面会讲)。

二、运算符重载:让计算机听懂你定义的“加减乘除”

现在你有了两个闹钟,想比较哪个音量更大,或者想加总两个闹钟的音量(但不超过100)。直接写 if (a > b)a + b 是不行的,因为计算机不知道 >+MagicClock 是什么意思。运算符重载就是给这些符号“扩句”,告诉计算机:“当这个符号用在我的类上时,按我写的规则执行。”

2.1 重载 > 运算符:比较音量

// 在类内部添加这个函数
bool operator > (const MagicClock& other) {
    return volume > other.volume;
}
  • > 是二元运算符,需要两个操作数。重载时,左侧的操作数是对象自身 this,右侧的操作数通过参数 other 传入。
  • const MagicClock& 是常量引用,既防止误修改 other,又提高效率(避免拷贝整个对象)。
  • 返回 bool,正好用来做判断。

现在你就可以写这样的代码:

if (alarm1 > alarm2) {
    cout << "alarm1 更响" << endl;
} else {
    cout << "alarm2 更响或一样响" << endl;
}

生活例子:就像你和小明比身高,> 运算符就是“身高比较器”,你告诉计算机“比较两个闹钟的 volume 属性”。如果不用重载,你只能写 if (a.volume > b.volume),虽然也行,但不如 a > b 直观。

2.2 重载 + 运算符:合并两个闹钟

假设你想把两个闹钟“融合”成一个新闹钟:颜色用第一个的,闹铃时间取平均值,音量相加但不超过100。

MagicClock operator + (const MagicClock& other) {
    int newVolume = volume + other.volume;
    if (newVolume > 100) newVolume = 100;  // 限制最大100
    int newTime = (ringTime + other.ringTime) / 2;  // 取平均时间(整数除法)
    return MagicClock(color, newTime, newVolume);  // 返回一个新对象
}

注意:

  • 返回类型是 MagicClock(新对象),而不是 void
  • 参数仍然是 const MagicClock&,只有一个参数(另一个是 this)。
  • 返回时可以用构造函数直接生成一个临时对象,C++会自动把它复制给接收变量。

2.3 重载 << 运算符:像打印普通变量一样输出闹钟

前面我们用了成员函数 show() 来输出。如果你想让 cout << a; 像打印整数一样直接输出闹钟,需要重载 <<。这个重载有些特殊,必须在类外面写,因为 << 的左侧是 ostream 对象(如 cout),而不是 MagicClock

// 全局函数(类外)
ostream& operator << (ostream& out, const MagicClock& clock) {
    out << "颜色:" << clock.color << " 闹铃:" << clock.ringTime << "点 音量:" << clock.volume;
    return out;  // 返回 out 可以支持链式输出: cout << a << b;
}

然后在 main 中就可以写 cout << a << endl; 了,非常简洁。


三、完整可运行示例:魔法闹钟大比拼

把上面所有知识整合成一个完整程序。你可以直接复制到编译器里运行。

#include <iostream>
#include <string>
using namespace std;

class MagicClock {
public:
    string color;   // 颜色
    int ringTime;   // 闹铃时间(0~23)
    int volume;     // 音量(0~100)

    // 构造函数
    MagicClock(string c, int t, int v) {
        color = c;
        ringTime = t;
        volume = v;
    }

    // 显示信息(可保留,但后面用 << 重载替代)
    void show() {
        cout << "颜色:" << color << " 闹铃:" << ringTime << "点 音量:" << volume << endl;
    }

    // 重载 > :比较音量
    bool operator > (const MagicClock& other) {
        return volume > other.volume;
    }

    // 重载 + :合并两个闹钟
    MagicClock operator + (const MagicClock& other) {
        int newVolume = volume + other.volume;
        if (newVolume > 100) newVolume = 100;
        int newTime = (ringTime + other.ringTime) / 2;
        return MagicClock(color, newTime, newVolume);
    }
};

// 全局函数:重载 << 方便输出
ostream& operator << (ostream& out, const MagicClock& clock) {
    out << "颜色:" << clock.color << " 闹铃:" << clock.ringTime << "点 音量:" << clock.volume;
    return out;
}

int main() {
    MagicClock a("红色", 7, 50);
    MagicClock b("蓝色", 8, 60);

    cout << "两个闹钟信息:" << endl;
    cout << "a: " << a << endl;   // 使用重载的 <<
    cout << "b: " << b << endl;

    // 比较音量
    if (a > b) {
        cout << "a 更响" << endl;
    } else {
        cout << "b 更响或一样响" << endl;
    }

    // 合并
    MagicClock c = a + b;
    cout << "合并后闹钟 c: " << c << endl;

    // 还能连续操作吗?比如 a + b + a? 需要重载 + 返回 MagicClock,可以链式,但要注意顺序。
    MagicClock d = a + b + b;  // 先 a+b 得到一个临时对象,再 +b
    cout << "a+b+b: " << d << endl;

    return 0;
}

输出示例

两个闹钟信息:
a: 颜色:红色 闹铃:7点 音量:50
b: 颜色:蓝色 闹铃:8点 音量:60
b 更响或一样响
合并后闹钟 c: 颜色:红色 闹铃:7点 音量:100
a+b+b: 颜色:红色 闹铃:7点 音量:100

四、新手最容易犯的 4 个错误

  1. 忘记写 const 和引用 &
    比如 bool operator > (MagicClock other) 会拷贝整个对象,效率低且可能误修改。应该用 const MagicClock&

  2. 运算符重载的返回值类型弄错
    > 返回 bool+ 返回对象,+= 返回引用。常见错误:把 + 重载写成 void 或返回局部变量的引用(会导致悬挂引用)。

  3. 重载 << 时写在类内部
    << 的左侧是 ostream,不是你的类。如果写在类内部,调用时 a << cout 非常奇怪。必须写成全局函数。

  4. 忽视运算符的原始语义
    比如你重载 + 却让两个闹钟相减,别人看代码会一头雾水。尽量保持重载运算符的意义与内置类型一致(加就是合并、增大等)。


五、更多有趣的重载:模拟零花钱累计

除了数学运算,你还可以重载 +=++[] 等运算符,让类用起来像内置类型。下面用一个 零花钱账户 的例子展示 +=++ 的重载。

class PocketMoney {
public:
    int money;   // 零花钱金额(单位:元)

    PocketMoney(int m = 0) : money(m) {}

    // 重载 += :增加金额
    PocketMoney& operator += (int add) {
        money += add;
        return *this;  // 返回自身引用,支持链式 a += 10 += 5;
    }

    // 重载 ++ :每天过完零花钱加1
    PocketMoney& operator ++ () {  // 前置++
        money += 1;
        return *this;
    }

    // 显示
    void show() {
        cout << "当前零花钱: " << money << "元" << endl;
    }
};

int main() {
    PocketMoney my(50);
    my.show();                 // 当前零花钱: 50元
    my += 20;                  // 得到20元
    my.show();                 // 当前零花钱: 70元
    ++my;                      // 过了一天
    my.show();                 // 当前零花钱: 71元
    return 0;
}

小提示:如果你重载了 ++=,最好让 += 返回引用,而 + 返回新对象。这样 a += b 不会产生临时对象,效率更高。


六、总结与相关指引

  • 是你自定义的数据类型,包含属性和方法。用它可以把相关的数据和行为打包在一起,让代码更清晰。
  • 运算符重载 是让自定义类型支持 C++ 已有的运算符,使代码更自然。关键是遵循运算符的语义,并且注意参数、返回类型和 const 的使用。
  • 接下来你可以继续学习:友元函数(让类外的函数访问私有成员)、拷贝构造函数(对象的深拷贝)、继承与多态(让类之间形成父子关系)。

尝试把生活中常见的“分数”、“二维坐标”、“学生成绩单”等写成一个类,并重载一些运算符(比如分数可以加减、比较大小)。动手练习是掌握这些概念最好的方法!

例题精讲

1单选题

在C++中,下列哪个运算符不允许被重载?

A+
B+=
C::
D[]
2单选题

对于类Complex,如果需要使用cout << comp; 来输出一个Complex对象,那么应该选择哪种方式重载<<运算符?

A将operator<<定义为Complex的成员函数
B将operator<<定义为Complex的友元函数
C将operator<<定义为Complex的静态成员函数
D将operator<<定义为普通全局函数(不声明为友元)
3判断题

在C++中,重载后置自增运算符(i++)时,为了区分前置自增,需要添加一个int类型的占位参数。

4填空题
请补全以下Point类的operator+重载函数,使其能实现两个Point对象的相加,返回一个新的Point对象。

class Point {
private:
    int x, y;
public:
    Point(int x=0, int y=0): x(x), y(y) {}
    ___ operator+(const Point& other) const {
        return Point(x + other.x, y + other.y);
    }
};
5填空题
请补全以下复数类Complex的输出运算符重载声明,使其能通过cout输出复数。

class Complex {
private:
    double real, imag;
public:
    Complex(double r=0, double i=0): real(r), imag(i) {}
    friend ___ operator<<(___, const Complex& c);
};

ostream& operator<<(ostream& os, const Complex& c) {
    os << c.real << '+' << c.imag << 'i';
    return os;
}