乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • C++ 学习路径
  • 第1章 C++概述与开发环境

    • C++ 语言概述与编译模型
    • 第一个 C++ 程序与规范
    • 开发环境配置
    • 预处理指令详解
  • 第2章 基本语法与类型系统

    • 变量与基本类型
    • 类型转换
    • 枚举类型
    • 引用与指针
    • 数组与 stdarray
    • 字符串与原始字符串字面量
    • const 与 constexpr
    • nullptr 与空指针
    • 类型推导 auto 与 decltype
    • 基于范围的 for 循环
    • 值类别全面解析
    • char16_t 与 char32_t
    • static_assert 编译期断言
  • 第3章 函数与重载

    • 函数声明与定义
    • 函数重载
    • 默认参数与内联函数
    • Lambda 表达式
    • 函数对象与 stdfunction
    • 后置返回类型与 noexcept
  • 第4章 类与对象

    • 类的基本定义
    • 构造函数与析构函数
    • 拷贝控制
    • 移动构造函数与移动赋值
    • 列表初始化与类内初始化器
    • 静态成员与嵌套类
    • 友元
    • =default 与 =delete
  • 第5章 继承与多态

    • 继承基础
    • 虚函数与多态
    • 虚函数表与动态绑定原理
    • 虚析构函数
    • 抽象类与纯虚函数
    • 多重继承与虚继承
    • 继承构造函数
  • 第6章 运算符重载

    • 运算符重载基础
    • 算术与关系运算符重载
    • 赋值与移动运算符重载
    • 特殊运算符重载
  • 第7章 模板与泛型编程

    • 函数模板
    • 类模板
    • 模板特化与偏特化
    • 可变参数模板
    • 别名模板与模板模板参数
    • SFINAE 与类型萃取
    • 依赖名与 typename/template 关键字
  • 第8章 异常处理

    • 异常处理机制
    • noexcept 与异常安全
    • RAII 与异常安全实践
  • 第9章 内存管理与智能指针

    • 动态内存与内存分区
    • RAII 惯用法
    • unique_ptr
    • shared_ptr 与 weak_ptr
    • 内存管理最佳实践
  • 第10章 右值引用与移动语义

    • 右值引用与值类别深度解析
    • std::move 原理与使用
    • 完美转发与 std::forward
    • 移动语义性能对比与最佳实践
  • 第11章 STL容器

    • vector 深度剖析
    • deque 内部机制
    • list 与 forward_list
    • map 与 set 深度解析
    • unordered 容器与哈希原理
    • array 与 tuple
    • 容器适配器
    • 容器选择全景指南
  • 第12章 STL算法与迭代器

    • 迭代器体系全解
    • 非变异算法
    • 变异算法
    • 排序与二分算法
    • Lambda 与算法组合
    • std::random 随机数库
    • 自定义迭代器开发
  • 第13章 IO流与文件

    • 标准 IO 流
    • 格式化输出控制
    • 文件流操作
    • 字符串流
    • std::regex 正则表达式
  • 第14章 并发与多线程

    • thread 基础与线程管理
    • mutex 与 lock_guard
    • unique_lock 与 condition_variable
    • thread_local 线程局部存储
    • atomic 与内存序
    • future 与 async 异步编程
    • std::chrono 时间库
  • 第15章 现代C++新特性

    • 从 C++11 到 C++20 演进路线
    • C++14 关键新特性
    • C++17 关键新特性
    • C++20 核心特性速览
  • 第16章 面试考点与最佳实践

    • C++ 综合最佳实践清单
    • 高频面试题精讲
    • 多线程面试题与实战
    • 内存管理常见陷阱与排查
  • 附录

    • C++ 核心知识点
    • C++ 专业术语

运算符重载基础

定义与作用

运算符重载允许为自定义类型定义运算符行为,使自定义类型像内置类型一样自然使用运算符:

Complex a(1, 2), b(3, 4);
Complex c = a + b;          // 而非 a.add(b)
std::cout << a;             // 而非 a.print()
if (a == b) { /* ... */ }   // 而非 a.equals(b)

它解决的核心痛点是可读性——数学类、容器类、智能指针类等,使用运算符比命名函数更直观自然。

核心原理

可重载运算符全景

成员 vs 非成员的选择

运算符推荐形式原因
= [] () ->成员语言强制
+= -= *= 等复合赋值成员修改左操作数自身
+ - * / 等二元算术非成员支持左操作数隐式转换
== != < 等关系非成员对称性
<< >> 流操作非成员左操作数是 ostream 非本类
++ -- 自增自减成员修改对象自身

完整示例

示例一:复数类的运算符重载

场景说明:设计复数类,支持完整的算术运算、比较和流输出。

#include <iostream>
#include <cmath>

class Complex {
public:
    Complex(double real = 0, double imag = 0) : real_(real), imag_(imag) {}

    // ---- 成员形式:复合赋值 ----
    Complex& operator+=(const Complex& rhs) {
        real_ += rhs.real_;
        imag_ += rhs.imag_;
        return *this;
    }

    Complex& operator-=(const Complex& rhs) {
        real_ -= rhs.real_;
        imag_ -= rhs.imag_;
        return *this;
    }

    Complex& operator*=(const Complex& rhs) {
        double r = real_ * rhs.real_ - imag_ * rhs.imag_;
        double i = real_ * rhs.imag_ + imag_ * rhs.real_;
        real_ = r;
        imag_ = i;
        return *this;
    }

    // ---- 成员形式:自增 / 自减 ----
    Complex& operator++() {    // 前置 ++
        ++real_;
        return *this;
    }

    Complex operator++(int) {  // 后置 ++(int 哑元)
        Complex tmp = *this;
        ++real_;
        return tmp;
    }

    // ---- 非成员形式:二元算术(声明为友元以访问私有成员)----
    friend Complex operator+(const Complex& a, const Complex& b);
    friend Complex operator-(const Complex& a, const Complex& b);
    friend Complex operator*(const Complex& a, const Complex& b);

    // ---- 非成员形式:等于 / 不等于 ----
    friend bool operator==(const Complex& a, const Complex& b);
    friend bool operator!=(const Complex& a, const Complex& b);

    // ---- 非成员形式:流输出 ----
    friend std::ostream& operator<<(std::ostream& os, const Complex& c);

    double modulus() const {
        return std::sqrt(real_ * real_ + imag_ * imag_);
    }

private:
    double real_, imag_;
};

// 二元算术:非成员 + 友元
Complex operator+(const Complex& a, const Complex& b) {
    return Complex(a.real_ + b.real_, a.imag_ + b.imag_);
}

Complex operator-(const Complex& a, const Complex& b) {
    return Complex(a.real_ - b.real_, a.imag_ - b.imag_);
}

Complex operator*(const Complex& a, const Complex& b) {
    return Complex(a.real_ * b.real_ - a.imag_ * b.imag_,
                   a.real_ * b.imag_ + a.imag_ * b.real_);
}

// 关系运算符
bool operator==(const Complex& a, const Complex& b) {
    return a.real_ == b.real_ && a.imag_ == b.imag_;
}

bool operator!=(const Complex& a, const Complex& b) {
    return !(a == b);  // 用 == 实现 !=,减少重复
}

// 流输出
std::ostream& operator<<(std::ostream& os, const Complex& c) {
    os << "(" << c.real_;
    if (c.imag_ >= 0)
        os << " + " << c.imag_ << "i)";
    else
        os << " - " << -c.imag_ << "i)";
    return os;
}

int main() {
    std::cout << "===== 复数运算 =====" << std::endl;

    Complex a(3, 4);   // 3 + 4i
    Complex b(1, -2);  // 1 - 2i

    std::cout << "a = " << a << std::endl;
    std::cout << "b = " << b << std::endl;

    Complex c = a + b;
    std::cout << "a + b = " << c << std::endl;

    Complex d = a * b;
    std::cout << "a * b = " << d << std::endl;

    // 复合赋值
    a += Complex(1, 1);
    std::cout << "a += (1+1i) → " << a << std::endl;

    // 自增
    std::cout << "++a = " << ++a << std::endl;
    std::cout << "a++ = " << a++ << std::endl;
    std::cout << "after a++ → " << a << std::endl;

    // 比较
    std::cout << std::boolalpha;
    std::cout << "a == b? " << (a == b) << std::endl;
    std::cout << "a != b? " << (a != b) << std::endl;

    // 模
    std::cout << "|a| = " << a.modulus() << std::endl;

    return 0;
}

预期输出:

===== 复数运算 =====
a = (3 + 4i)
b = (1 - 2i)
a + b = (4 + 2i)
a * b = (11 - 2i)
a += (1+1i) → (4 + 5i)
++a = (5 + 5i)
a++ = (5 + 5i)
after a++ → (6 + 5i)
a == b? false
a != b? true
|a| = 7.81025

逐段分析:

  • 成员重载:+= -= *= 修改左操作数,++ 修改自身——都是成员
  • 非成员 + 友元:+ - * 二元运算返回新对象,用非成员实现支持对称隐式转换
  • operator++()(无参)是前置,operator++(int)(哑元 int)是后置——后置返回值而非引用
  • operator== 用非成员实现保持对称性;operator!= 基于 == 实现,避免重复
  • operator<< 第一个参数是 std::ostream——只能是友元非成员,不能是成员

示例二:轻量级字符串类

#include <iostream>
#include <cstring>
#include <algorithm>
#include <utility>

class String {
public:
    String() : data_(new char[1]{'\0'}), size_(0) {}

    String(const char* str) : size_(std::strlen(str)) {
        data_ = new char[size_ + 1];
        std::copy(str, str + size_ + 1, data_);
    }

    // 拷贝
    String(const String& other) : size_(other.size_) {
        data_ = new char[size_ + 1];
        std::copy(other.data_, other.data_ + size_ + 1, data_);
    }

    // 移动
    String(String&& other) noexcept
        : data_(other.data_), size_(other.size_) {
        other.data_ = nullptr;
        other.size_ = 0;
    }

    // 拷贝赋值
    String& operator=(const String& other) {
        if (this != &other) {
            delete[] data_;
            size_ = other.size_;
            data_ = new char[size_ + 1];
            std::copy(other.data_, other.data_ + size_ + 1, data_);
        }
        return *this;
    }

    // 移动赋值
    String& operator=(String&& other) noexcept {
        if (this != &other) {
            delete[] data_;
            data_ = other.data_;
            size_ = other.size_;
            other.data_ = nullptr;
            other.size_ = 0;
        }
        return *this;
    }

    ~String() { delete[] data_; }

    // 下标访问(可读可写)
    char& operator[](size_t index) { return data_[index]; }

    // 下标访问(只读)
    const char& operator[](size_t index) const { return data_[index]; }

    // 拼接
    friend String operator+(const String& a, const String& b);

    // 比较
    friend bool operator==(const String& a, const String& b);
    friend bool operator<(const String& a, const String& b);

    // 流输出
    friend std::ostream& operator<<(std::ostream& os, const String& s);

    size_t size() const { return size_; }
    const char* c_str() const { return data_; }

private:
    char* data_;
    size_t size_;
};

String operator+(const String& a, const String& b) {
    String result;
    delete[] result.data_;
    result.size_ = a.size_ + b.size_;
    result.data_ = new char[result.size_ + 1];
    std::copy(a.data_, a.data_ + a.size_, result.data_);
    std::copy(b.data_, b.data_ + b.size_ + 1, result.data_ + a.size_);
    return result;
}

bool operator==(const String& a, const String& b) {
    return std::strcmp(a.data_, b.data_) == 0;
}

bool operator<(const String& a, const String& b) {
    return std::strcmp(a.data_, b.data_) < 0;
}

std::ostream& operator<<(std::ostream& os, const String& s) {
    return os << s.data_;
}

int main() {
    std::cout << "===== 自定义字符串类 =====" << std::endl;

    String s1("Hello");
    String s2(" World");

    std::cout << "s1 = " << s1 << std::endl;
    std::cout << "s2 = " << s2 << std::endl;

    String s3 = s1 + s2;
    std::cout << "s1 + s2 = " << s3 << std::endl;

    // 下标修改
    s3[0] = 'h';
    std::cout << "s3[0] = 'h' → " << s3 << std::endl;

    // 比较
    std::cout << std::boolalpha;
    std::cout << "s1 == \"Hello\"? " << (s1 == "Hello") << std::endl;
    std::cout << "s1 < s2? " << (s1 < s2) << std::endl;

    return 0;
}

预期输出:

===== 自定义字符串类 =====
s1 = Hello
s2 =  World
s1 + s2 = Hello World
s3[0] = 'h' → hello World
s1 == "Hello"? true
s1 < s2? true

易错场景与面试考点

易错场景

场景错误表现正确做法
= 重载但忘记自赋值检查自我赋值时资源被释放if (this != &rhs)
后置 ++ 返回引用返回临时对象的悬垂引用后置返回值(非引用)
+ 作为成员重载不支持左操作数隐式转换非成员函数
滥用运算符违反直觉语义(如 + 做减法)运算符保留自然语义
&& 或 `` 重载

常见面试问题

  1. 哪些运算符必须重载为成员函数?哪些不能重载?——必须成员:= [] () ->。不可重载::: . .* ?:。

  2. 运算符重载选择成员还是非成员?——修改左操作数的(= += ++)用成员。对称操作(+ ==)用非成员,支持两侧隐式转换。流操作(<< >>)必须非成员。

  3. 前置和后置 ++ 如何区分?——前置无参 operator++(),后置有 int 哑元 operator++(int)。前置返回引用,后置返回值。

  4. 为什么 << 和 >> 通常声明为友元?——它们的左操作数是 std::ostream / std::istream,不可能是本类对象,所以只能是自由函数。需要访问私有成员时声明为友元。

  5. 重载 && 和 || 有什么问题?——会失去短路求值特性,所有操作数都会被求值。标准不建议重载这两个运算符。

小结

  • 运算符重载提升可读性——让自定义类型像内置类型一样自然
  • 选择原则:修改自身用成员,对称操作用非成员,流操作用友元非成员
  • 四个必须成员重载:= [] () ->;四个不可重载::: . .* ?:
  • 后置 ++/-- 用 int 哑元区分,返回的是值而非引用
  • 语义一致性:重载的运算符应保持直觉行为
下一页
算术与关系运算符重载