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

    • 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++ 专业术语

算术与关系运算符重载

定义与作用

算术运算符(+ - * / %)和关系运算符(== != < > <= >=)是自定义类型最常重载的运算符。通过合理重载,自定类型能像内置类型一样参与数学计算和条件判断。

核心原理

基于复合赋值的算术运算符实现模式

这个模式的优点:只需在一个地方(+=)维护核心逻辑,+ 自动跟随。

关系运算符的互推关系

只需实现 == 和 <,其余四个关系运算符都可以基于它们推演。

完整示例

示例一:三维向量的完整运算

场景说明:游戏开发或图形学中的 Vector3 类,支持完整的算术和比较运算。

#include <iostream>
#include <cmath>

class Vector3 {
public:
    Vector3(double x = 0, double y = 0, double z = 0)
        : x_(x), y_(y), z_(z) {}

    // ====== 复合赋值(成员)======

    Vector3& operator+=(const Vector3& rhs) {
        x_ += rhs.x_; y_ += rhs.y_; z_ += rhs.z_;
        return *this;
    }

    Vector3& operator-=(const Vector3& rhs) {
        x_ -= rhs.x_; y_ -= rhs.y_; z_ -= rhs.z_;
        return *this;
    }

    Vector3& operator*=(double scalar) {
        x_ *= scalar; y_ *= scalar; z_ *= scalar;
        return *this;
    }

    Vector3& operator/=(double scalar) {
        x_ /= scalar; y_ /= scalar; z_ /= scalar;
        return *this;
    }

    // ====== 一元运算符(成员)======

    Vector3 operator-() const {
        return Vector3(-x_, -y_, -z_);
    }

    Vector3 operator+() const {
        return *this;
    }

    // ====== 访问器 ======

    double x() const { return x_; }
    double y() const { return y_; }
    double z() const { return z_; }

    double length() const {
        return std::sqrt(x_ * x_ + y_ * y_ + z_ * z_);
    }

    double dot(const Vector3& v) const {
        return x_ * v.x_ + y_ * v.y_ + z_ * v.z_;
    }

    Vector3 cross(const Vector3& v) const {
        return Vector3(y_ * v.z_ - z_ * v.y_,
                       z_ * v.x_ - x_ * v.z_,
                       x_ * v.y_ - y_ * v.x_);
    }

private:
    double x_, y_, z_;
};

// ====== 二元算术(非成员 + 友元)======

inline Vector3 operator+(const Vector3& a, const Vector3& b) {
    return Vector3(a.x() + b.x(), a.y() + b.y(), a.z() + b.z());
}

inline Vector3 operator-(const Vector3& a, const Vector3& b) {
    return Vector3(a.x() - b.x(), a.y() - b.y(), a.z() - b.z());
}

inline Vector3 operator*(const Vector3& v, double scalar) {
    return Vector3(v.x() * scalar, v.y() * scalar, v.z() * scalar);
}

inline Vector3 operator*(double scalar, const Vector3& v) {
    return v * scalar;  // 利用对称性
}

inline Vector3 operator/(const Vector3& v, double scalar) {
    return Vector3(v.x() / scalar, v.y() / scalar, v.z() / scalar);
}

// ====== 关系运算符 ======

inline bool operator==(const Vector3& a, const Vector3& b) {
    return a.x() == b.x() && a.y() == b.y() && a.z() == b.z();
}

inline bool operator!=(const Vector3& a, const Vector3& b) {
    return !(a == b);
}

inline bool operator<(const Vector3& a, const Vector3& b) {
    // 按长度比较
    return a.length() < b.length();
}

inline bool operator>(const Vector3& a, const Vector3& b) {
    return b < a;
}

inline bool operator<=(const Vector3& a, const Vector3& b) {
    return !(b < a);
}

inline bool operator>=(const Vector3& a, const Vector3& b) {
    return !(a < b);
}

// ====== 流输出 ======

inline std::ostream& operator<<(std::ostream& os, const Vector3& v) {
    return os << "(" << v.x() << ", " << v.y() << ", " << v.z() << ")";
}

int main() {
    std::cout << "===== 三维向量运算 =====" << std::endl;

    Vector3 position(1.0, 2.0, 3.0);
    Vector3 velocity(0.5, 0.5, 0.0);
    Vector3 gravity(0.0, -9.8, 0.0);

    std::cout << "初始位置: " << position << std::endl;
    std::cout << "速度: " << velocity << std::endl;
    std::cout << "重力: " << gravity << std::endl;

    // 运动更新
    Vector3 new_pos = position + velocity + gravity * 0.016;  // dt=16ms
    std::cout << "\n新位置(1 帧后): " << new_pos << std::endl;

    // 缩放
    Vector3 normalized = velocity * (1.0 / velocity.length());
    std::cout << "归一化速度: " << normalized << std::endl;
    std::cout << "归一化速度长度: " << normalized.length() << std::endl;

    // 点积和叉积
    Vector3 a(1, 0, 0);
    Vector3 b(0, 1, 0);
    std::cout << "\na · b = " << a.dot(b) << std::endl;
    std::cout << "a × b = " << a.cross(b) << std::endl;

    // 比较
    std::cout << std::boolalpha;
    std::cout << "\nposition == new_pos? " << (position == new_pos) << std::endl;
    std::cout << "velocity < gravity? " << (velocity < gravity) << std::endl;

    return 0;
}

预期输出:

===== 三维向量运算 =====
初始位置: (1, 2, 3)
速度: (0.5, 0.5, 0)
重力: (0, -9.8, 0)

新位置(1 帧后): (1.5, 2.3432, 3)
归一化速度: (0.707107, 0.707107, 0)
归一化速度长度: 1

a · b = 0
a × b = (0, 0, 1)

position == new_pos? false
velocity < gravity? true

逐段分析:

  • 复合赋值(+= -= *= /=)是成员函数——修改自身
  • 二元运算(+ - * /)是非成员——支持隐式转换,且保持对称性
  • operator*(double, Vector3) 调用 operator*(Vector3, double)——只维护一份实现
  • 关系运算符推导链:只实现 == 和 <,其余四个全基于这两个推演——极简且一致
  • 所有二元运算通过 public getter 访问私有成员,不需要友元——更好的封装

示例二:分数的算术与比较

#include <iostream>
#include <numeric>  // std::gcd
#include <stdexcept>

class Fraction {
public:
    Fraction(int num = 0, int den = 1) : num_(num), den_(den) {
        if (den_ == 0) throw std::invalid_argument("分母不能为 0");
        if (den_ < 0) { num_ = -num_; den_ = -den_; }  // 负数分母标准化
        reduce();
    }

    // 复合赋值
    Fraction& operator+=(const Fraction& rhs) {
        num_ = num_ * rhs.den_ + rhs.num_ * den_;
        den_ = den_ * rhs.den_;
        reduce();
        return *this;
    }

    Fraction& operator*=(const Fraction& rhs) {
        num_ *= rhs.num_;
        den_ *= rhs.den_;
        reduce();
        return *this;
    }

    // 取倒数
    Fraction reciprocal() const {
        if (num_ == 0) throw std::domain_error("不能取 0 的倒数");
        return Fraction(den_, num_);
    }

    double to_double() const {
        return static_cast<double>(num_) / den_;
    }

private:
    void reduce() {
        int g = std::gcd(num_, den_);
        num_ /= g;
        den_ /= g;
    }

    int num_, den_;

    friend Fraction operator+(const Fraction& a, const Fraction& b);
    friend Fraction operator*(const Fraction& a, const Fraction& b);
    friend bool operator==(const Fraction& a, const Fraction& b);
    friend bool operator<(const Fraction& a, const Fraction& b);
    friend std::ostream& operator<<(std::ostream& os, const Fraction& f);
};

Fraction operator+(const Fraction& a, const Fraction& b) {
    Fraction tmp = a;
    tmp += b;
    return tmp;
}

Fraction operator*(const Fraction& a, const Fraction& b) {
    Fraction tmp = a;
    tmp *= b;
    return tmp;
}

Fraction operator-(const Fraction& a, const Fraction& b) {
    return a + Fraction(-b.num_, b.den_);  // a - b = a + (-b)
}

Fraction operator/(const Fraction& a, const Fraction& b) {
    return a * b.reciprocal();
}

bool operator==(const Fraction& a, const Fraction& b) {
    return a.num_ == b.num_ && a.den_ == b.den_;
}

bool operator!=(const Fraction& a, const Fraction& b) {
    return !(a == b);
}

bool operator<(const Fraction& a, const Fraction& b) {
    return a.num_ * b.den_ < b.num_ * a.den_;
}

bool operator>(const Fraction& a, const Fraction& b)  { return b < a; }
bool operator<=(const Fraction& a, const Fraction& b) { return !(b < a); }
bool operator>=(const Fraction& a, const Fraction& b) { return !(a < b); }

std::ostream& operator<<(std::ostream& os, const Fraction& f) {
    if (f.den_ == 1)
        return os << f.num_;
    return os << f.num_ << "/" << f.den_;
}

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

    Fraction f1(1, 2);   // 1/2
    Fraction f2(1, 3);   // 1/3
    Fraction f3(2, 4);   // 自动约分为 1/2

    std::cout << "f1 = " << f1 << std::endl;
    std::cout << "f2 = " << f2 << std::endl;
    std::cout << "f3 = " << f3 << " (2/4 自动约分)" << std::endl;

    std::cout << "f1 + f2 = " << (f1 + f2) << std::endl;
    std::cout << "f1 - f2 = " << (f1 - f2) << std::endl;
    std::cout << "f1 * f2 = " << (f1 * f2) << std::endl;
    std::cout << "f1 / f2 = " << (f1 / f2) << std::endl;

    std::cout << "\n小数表示:" << std::endl;
    std::cout << "f1 = " << f1.to_double() << std::endl;
    std::cout << "f2 = " << f2.to_double() << std::endl;

    std::cout << "\n比较:" << std::endl;
    std::cout << std::boolalpha;
    std::cout << "f1 == f3? " << (f1 == f3) << std::endl;
    std::cout << "f1 > f2?  " << (f1 > f2) << std::endl;

    return 0;
}

预期输出:

===== 分数运算 =====
f1 = 1/2
f2 = 1/3
f3 = 1/2 (2/4 自动约分)

f1 + f2 = 5/6
f1 - f2 = 1/6
f1 * f2 = 1/6
f1 / f2 = 3/2

小数表示:
f1 = 0.5
f2 = 0.333333

比较:
f1 == f3? true
f1 > f2?  true

逐段分析:

  • 构造函数自动调用 reduce() 做约分——任何方式创建分数都是最简形式
  • + 基于 += 实现(拷贝后复合赋值),- 基于 + 和取负号实现,/ 基于 * 和取倒数——层层复用,极简维护
  • 只有 == 和 < 是手写的,!= > <= >= 都是基于这两个推导
  • 通分比较 a.num * b.den < b.num * a.den 避免了浮点精度问题

易错场景与面试考点

易错场景

场景错误表现正确做法
算术运算符作为成员不支持左操作数隐式转换用非成员函数实现
关系运算符各自独立实现不一致(如 > 和 <= 不互补)基于 == 和 < 推导其余
忘记约分 / 标准化同一值有多种表示构造和运算后统一标准化
除法未检查除零运行时崩溃抛异常或返回特殊值
operator== 使用浮点直接比较精度问题用 epsilon 容差

常见面试问题

  1. 为什么 + 通常用非成员实现?——支持左操作数的隐式类型转换。成员函数形式 a.operator+(b) 要求 a 必须是该类对象。

  2. 关系运算符的推导式实现是如何减少代码的?——六个关系运算符(== != < > <= >=)只需自行实现 == 和 <,其余四个都可通过逻辑推导实现,减少重复代码和维护成本。

  3. 如何重载 operator* 以同时支持 Vector * scalar 和 scalar * Vector?——只实现 Vector operator*(Vector, double),再写 Vector operator*(double, Vector) 调用前者即可。

  4. 重载算术运算符时的返回值类型选择?——+ - * / 返回新对象(按值),+= -= *= /= 返回 *this(按引用)。不返回临时对象的引用。

小结

  • 算术运算符用复合赋值驱动:先实现 +=,+ 基于 += 实现
  • 关系运算符用 == 和 < 驱动:其余四个基于这两个推导
  • 二元算术是非成员函数——支持对称隐式转换
  • 所有重载运算符保持自然语义——不创造"惊喜"
上一页
运算符重载基础
下一页
赋值与移动运算符重载