【c++面向对象编程】第21篇:运算符重载基础:语法、规则与不可重载的运算符

目录

一、为什么需要运算符重载?

二、基本语法

两种实现方式

示例:加法运算符

[三、成员函数 vs 全局函数:如何选择?](#三、成员函数 vs 全局函数:如何选择?)

指导原则

四、可以重载的运算符

算术运算符

关系/逻辑运算符

位运算符

自增自减

下标/调用/成员访问

赋值/内存管理

逗号/指针解引用

五、不能重载的运算符

还有几个特殊情况

[危险示例:重载 && 失去短路求值](#危险示例:重载 && 失去短路求值)

六、运算符重载的规则和限制

[1. 不能发明新运算符](#1. 不能发明新运算符)

[2. 不能改变优先级和结合性](#2. 不能改变优先级和结合性)

[3. 不能改变操作数个数](#3. 不能改变操作数个数)

[4. 不能改变内置类型的行为](#4. 不能改变内置类型的行为)

[5. 至少一个操作数是用户自定义类型](#5. 至少一个操作数是用户自定义类型)

七、完整例子:有理数类的运算符重载

八、常见错误

[1. 忘记 const 正确性](#1. 忘记 const 正确性)

[2. 返回类型错误](#2. 返回类型错误)

[3. 没有处理自赋值(+= 等)](#3. 没有处理自赋值(+= 等))

[4. 重载 && 或 || 以为有短路求值](#4. 重载 && 或 || 以为有短路求值)

九、这一篇的收获


一、为什么需要运算符重载?

写一个复数类 Complex,没有运算符重载时:

cpp

复制代码
Complex a(1, 2), b(3, 4);
Complex c = add(a, b);        // 不能写 a + b
Complex d = multiply(a, b);   // 不能写 a * b

这很不直观。数学上复数就应该用 +-* 运算。运算符重载让代码更自然:

cpp

复制代码
Complex a(1, 2), b(3, 4);
Complex c = a + b;   // 清晰!
Complex d = a * b;   // 直观!

本质 :运算符重载就是定义特殊的函数------函数名是 operator 加上运算符符号。


二、基本语法

两种实现方式

方式 函数名 参数个数 示例
成员函数 operator@ 少一个(左侧对象是this) a + ba.operator+(b)
全局函数 operator@ 全部作为参数 a + boperator+(a, b)

示例:加法运算符

成员函数版本

cpp

复制代码
class Complex {
    double real, imag;
public:
    Complex(double r, double i) : real(r), imag(i) {}
    
    Complex operator+(const Complex& other) const {
        return Complex(real + other.real, imag + other.imag);
    }
};

Complex a(1, 2), b(3, 4);
Complex c = a + b;  // 等价于 a.operator+(b)

全局函数版本(通常需要友元):

cpp

复制代码
class Complex {
    double real, imag;
public:
    Complex(double r, double i) : real(r), imag(i) {}
    friend Complex operator+(const Complex& a, const Complex& b);
};

Complex operator+(const Complex& a, const Complex& b) {
    return Complex(a.real + b.real, a.imag + b.imag);
}

三、成员函数 vs 全局函数:如何选择?

运算符 推荐方式 原因
= () [] -> 必须是成员函数 C++语法规定
+ - * / 成员或全局均可 通常用成员函数
<< >> 必须是全局函数 左操作数不是本类对象
双目运算符 两种都行 看是否需要访问私有成员
单目运算符 成员函数更自然 ++a 对应 a.operator++()

指导原则

  1. 左操作数不是你控制的类型 → 必须用全局函数

    cpp

    复制代码
    // cout << obj,左操作数是ostream,你改不了ostream
    friend ostream& operator<<(ostream& os, const MyClass& obj);
  2. 需要对称性 → 用全局函数(比如 int + ComplexComplex + int 都支持)

  3. 自然属于对象的行为 → 用成员函数(比如 obj += other


四、可以重载的运算符

C++大部分运算符都可以重载(共约40个):

算术运算符

text

复制代码
+  -  *  /  %
+= -= *= /= %=

关系/逻辑运算符

text

复制代码
== != < > <= >=
&& || !

位运算符

text

复制代码
& | ^ ~ << >>
&= |= ^= <<= >>=

自增自减

text

复制代码
++ --  (前置和后置)

下标/调用/成员访问

text

复制代码
[]  ()  ->  ->

赋值/内存管理

text

复制代码
=  new  delete  new[]  delete[]

逗号/指针解引用

text

复制代码
,  *

五、不能重载的运算符

有5个运算符不能重载(C++语法禁止):

运算符 名称 不能重载的原因
:: 作用域解析 操作的是类型和命名空间,不是值
. 成员访问 保持内置语义,保证成员访问的安全性
.* 成员指针解引用 语义复杂,重载会混乱
?: 三元条件 第二个参数可以是表达式,重载无法模拟短路求值
sizeof 获取大小 编译时运算,不能变成运行时函数

还有几个特殊情况

运算符 情况 说明
= 可以重载 但必须是成员函数,且编译器默认生成
() 可以重载 必须是成员函数(仿函数)
[] 可以重载 必须是成员函数
-> 可以重载 必须是成员函数,且返回值必须是指针或可重载->的对象
&& ` `

危险示例:重载 && 失去短路求值

cpp

复制代码
bool operator&&(const MyClass& a, const MyClass& b) {
    // 编译器会求值两个操作数后才调用这个函数
    // 短路求值失效!
}

规则 :除非有特殊需求,否则不要重载 &&||,


六、运算符重载的规则和限制

1. 不能发明新运算符

cpp

复制代码
// ❌ 不能定义 @ 或 # 这种新符号
MyClass operator@(MyClass a, MyClass b);  // 错误

2. 不能改变优先级和结合性

cpp

复制代码
// + 的优先级永远高于 ==,无法改变

3. 不能改变操作数个数

cpp

复制代码
// ++ 永远是单目,不能变成三目
MyClass operator++(MyClass a, MyClass b, MyClass c); // 错误

4. 不能改变内置类型的行为

cpp

复制代码
// ❌ 不能重载 int 的 +
int operator+(int a, int b);  // 错误

5. 至少一个操作数是用户自定义类型

cpp

复制代码
// ✅ 正确:有一个是 Complex
Complex operator+(const Complex& a, const Complex& b);

// ❌ 错误:两个都是内置类型
int operator+(int a, int b);  // 错误

七、完整例子:有理数类的运算符重载

cpp

复制代码
#include <iostream>
#include <numeric>  // for gcd
using namespace std;

class Rational {
private:
    int num;   // 分子
    int den;   // 分母
    
    void reduce() {
        int g = gcd(num, den);
        num /= g;
        den /= g;
        if (den < 0) { num = -num; den = -den; }
    }
    
public:
    Rational(int n = 0, int d = 1) : num(n), den(d) {
        if (den == 0) throw invalid_argument("分母不能为0");
        reduce();
    }
    
    // 算术运算符(成员函数版本)
    Rational operator+(const Rational& other) const {
        return Rational(num * other.den + other.num * den,
                       den * other.den);
    }
    
    Rational operator-(const Rational& other) const {
        return Rational(num * other.den - other.num * den,
                       den * other.den);
    }
    
    Rational operator*(const Rational& other) const {
        return Rational(num * other.num, den * other.den);
    }
    
    Rational operator/(const Rational& other) const {
        return Rational(num * other.den, den * other.num);
    }
    
    // 复合赋值运算符(成员函数)
    Rational& operator+=(const Rational& other) {
        *this = *this + other;
        return *this;
    }
    
    // 比较运算符
    bool operator==(const Rational& other) const {
        return num == other.num && den == other.den;
    }
    
    bool operator<(const Rational& other) const {
        return num * other.den < other.num * den;
    }
    
    // 输入输出运算符(必须是全局函数,后面详解)
    friend ostream& operator<<(ostream& os, const Rational& r);
    friend istream& operator>>(istream& is, Rational& r);
};

// 全局函数:输出
ostream& operator<<(ostream& os, const Rational& r) {
    if (r.den == 1) os << r.num;
    else os << r.num << "/" << r.den;
    return os;
}

// 全局函数:输入
istream& operator>>(istream& is, Rational& r) {
    is >> r.num;
    char slash;
    is >> slash;
    if (slash == '/') is >> r.den;
    else r.den = 1;
    r.reduce();
    return is;
}

int main() {
    Rational a(1, 2);   // 1/2
    Rational b(2, 3);   // 2/3
    
    cout << a << " + " << b << " = " << a + b << endl;
    cout << a << " - " << b << " = " << a - b << endl;
    cout << a << " * " << b << " = " << a * b << endl;
    cout << a << " / " << b << " = " << a / b << endl;
    
    Rational c(3, 4);
    c += Rational(1, 4);
    cout << "c += 1/4 -> " << c << endl;
    
    cout << "1/2 == 2/4 ? " << (Rational(1,2) == Rational(2,4)) << endl;
    cout << "1/2 < 2/3 ? " << (Rational(1,2) < Rational(2,3)) << endl;
    
    return 0;
}

输出:

text

复制代码
1/2 + 2/3 = 7/6
1/2 - 2/3 = -1/6
1/2 * 2/3 = 1/3
1/2 / 2/3 = 3/4
c += 1/4 -> 1
1/2 == 2/4 ? 1
1/2 < 2/3 ? 1

八、常见错误

1. 忘记 const 正确性

cpp

复制代码
Complex operator+(Complex& other) { ... }   // 无法加const对象

应该用 const Complex& 参数,函数本身也要是 const(成员函数时)。

2. 返回类型错误

cpp

复制代码
Complex operator+(const Complex& a, const Complex& b) {
    Complex result;
    // ...
    return &result;   // ❌ 不要返回局部变量的指针/引用
}

3. 没有处理自赋值(+= 等)

cpp

复制代码
Complex& operator+=(const Complex& other) {
    real += other.real;
    imag += other.imag;
    return *this;   // ✅ 返回引用,支持链式调用
}

4. 重载 &&|| 以为有短路求值

重载后两个操作数都会被求值,行为与内置 && 不同,容易迷惑使用者。


九、这一篇的收获

你现在应该理解:

  • 语法operator@ 定义运算符重载函数

  • 实现方式:成员函数(左操作数是 this)vs 全局函数(两侧对称)

  • 不可重载的5个:: . .* ?: sizeof

  • 规则:不能发明新运算符、不能改优先级/结合性、至少一个操作数是自定义类型

  • 选择原则= () [] -> 必须是成员;<< >> 必须是全局;其余看设计

💡 小作业:实现一个 Vector3D 类(x, y, z),重载 +-*(点积,返回 double)、==<<。测试 v1 + v2v1 * v2 等操作。


下一篇预告 :第22篇《输入输出运算符重载:<< 与 >> 的友元实现》------为什么 cout << obj 必须用全局函数?为什么要用友元?如何正确实现 <<>> 支持链式输出/输入?下篇详解。

相关推荐
萧曵 丶1 小时前
JUC 实际业务高频面试题浅谈
java·juc·aqs·lock
开发者联盟league1 小时前
在cursor中配置c/c++开发环境
c语言·开发语言·c++
初圣魔门首席弟子1 小时前
bug 2026.05.15(以前能运行的java springboot项目突然间不能运行后台数据了)
java·开发语言·bug
澈2071 小时前
平衡二叉树:AVL与红黑树终极对比
数据结构·c++·红黑树
古怪今人1 小时前
项目和模块 一个目录下创建多个项目 IDEA Multi-Project Workspace插件
java·ide·intellij-idea
__log1 小时前
Vue 3 核心技术深度解析:从“会用API“到“懂原理、能表达“
前端·javascript·vue.js
小英雄大肚腩丶1 小时前
RabbitMQ消息队列
java·数据结构·spring boot·分布式·rabbitmq·java-rabbitmq
fengxin_rou2 小时前
用户模块架构实战:DTO 与 Domain 分层、Optional 空值处理、事务只读优化详解
java·后端·架构·用户实战