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

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

特殊运算符重载

定义与作用

除了算术和关系运算符,C++ 还支持重载一些具有特殊语义的运算符:函数调用运算符 () 创建仿函数、下标运算符 [] 实现类数组访问、箭头运算符 -> 实现智能指针、new/delete 定制内存分配,以及 C++11 引入的用户定义字面量。

核心原理

特殊运算符的独特规则

完整示例

示例一:仿函数与下标访问——简易矩阵类

场景说明:设计支持二维下标访问 matrix(i, j) 和 matrix[i][j] 的矩阵类。

#include <iostream>
#include <vector>
#include <stdexcept>
#include <initializer_list>

class Matrix {
public:
    Matrix(size_t rows, size_t cols)
        : rows_(rows), cols_(cols), data_(rows * cols, 0.0) {}

    Matrix(std::initializer_list<std::initializer_list<double>> init) {
        rows_ = init.size();
        cols_ = rows_ > 0 ? init.begin()->size() : 0;
        data_.reserve(rows_ * cols_);
        for (const auto& row : init) {
            data_.insert(data_.end(), row.begin(), row.end());
        }
    }

    // ====== operator()——函数调用:二维下标访问 ======
    double& operator()(size_t row, size_t col) {
        check_bounds(row, col);
        return data_[row * cols_ + col];
    }

    const double& operator()(size_t row, size_t col) const {
        check_bounds(row, col);
        return data_[row * cols_ + col];
    }

    // ====== operator()——仿函数:矩阵变换 ======
    Matrix operator()(double (*func)(double)) const {
        Matrix result(rows_, cols_);
        for (size_t i = 0; i < data_.size(); ++i) {
            result.data_[i] = func(data_[i]);
        }
        return result;
    }

    // ====== operator[]——返回代理对象以支持 [i][j] ======
    class RowProxy {
    public:
        RowProxy(double* row, size_t cols) : row_(row), cols_(cols) {}

        double& operator[](size_t col) {
            if (col >= cols_) throw std::out_of_range("列索引越界");
            return row_[col];
        }

        const double& operator[](size_t col) const {
            if (col >= cols_) throw std::out_of_range("列索引越界");
            return row_[col];
        }

    private:
        double* row_;
        size_t cols_;
    };

    RowProxy operator[](size_t row) {
        if (row >= rows_) throw std::out_of_range("行索引越界");
        return RowProxy(&data_[row * cols_], cols_);
    }

    size_t rows() const { return rows_; }
    size_t cols() const { return cols_; }

    void print() const {
        for (size_t i = 0; i < rows_; ++i) {
            std::cout << "  [";
            for (size_t j = 0; j < cols_; ++j) {
                if (j > 0) std::cout << ", ";
                std::cout << (*this)(i, j);
            }
            std::cout << "]" << std::endl;
        }
    }

private:
    void check_bounds(size_t row, size_t col) const {
        if (row >= rows_ || col >= cols_)
            throw std::out_of_range("矩阵索引越界");
    }

    size_t rows_, cols_;
    std::vector<double> data_;
};

// ====== 用户定义字面量(C++11)======

// 像素单位字面量
constexpr unsigned long long operator"" _px(unsigned long long n) {
    return n;
}

// 弧度转角度
constexpr long double operator"" _deg(long double deg) {
    return deg * 3.14159265358979323846L / 180.0L;
}

// 字节单位
constexpr unsigned long long operator"" _KB(unsigned long long n) {
    return n * 1024;
}

constexpr unsigned long long operator"" _MB(unsigned long long n) {
    return n * 1024 * 1024;
}

int main() {
    std::cout << "===== 矩阵运算 =====" << std::endl;

    // 用 operator() 访问
    Matrix m(3, 3);
    m(0, 0) = 1.0;  m(0, 1) = 2.0;  m(0, 2) = 3.0;
    m(1, 0) = 4.0;  m(1, 1) = 5.0;  m(1, 2) = 6.0;
    m(2, 0) = 7.0;  m(2, 1) = 8.0;  m(2, 2) = 9.0;

    std::cout << "原始矩阵:" << std::endl;
    m.print();

    // 用 operator[] 访问
    std::cout << "m[1][2] = " << m[1][2] << std::endl;

    // 用 operator() 作为仿函数——对每个元素应用变换
    auto squared = m([](double x) { return x * x; });
    std::cout << "\n平方矩阵:" << std::endl;
    squared.print();

    // 列表初始化
    Matrix identity = {
        {1.0, 0.0, 0.0},
        {0.0, 1.0, 0.0},
        {0.0, 0.0, 1.0}
    };
    std::cout << "单位矩阵:" << std::endl;
    identity.print();

    std::cout << "\n===== 用户定义字面量 =====" << std::endl;
    std::cout << "100_px = " << 100_px << " px" << std::endl;
    std::cout << "180_deg = " << 180.0_deg << " 弧度" << std::endl;
    std::cout << "1_KB = " << 1_KB << " 字节" << std::endl;
    std::cout << "1_MB = " << 1_MB << " 字节" << std::endl;

    return 0;
}

预期输出:

===== 矩阵运算 =====
原始矩阵:
  [1, 2, 3]
  [4, 5, 6]
  [7, 8, 9]
m[1][2] = 6

平方矩阵:
  [1, 4, 9]
  [16, 25, 36]
  [49, 64, 81]
单位矩阵:
  [1, 0, 0]
  [0, 1, 0]
  [0, 0, 1]

===== 用户定义字面量 =====
100_px = 100 px
180_deg = 3.14159 弧度
1_KB = 1024 字节
1_MB = 1048576 字节

逐段分析:

  • operator()(size_t, size_t) 实现二维下标访问——比 operator[] 更灵活(参数个数任意)
  • operator()(double(*)(double)) 让矩阵对象可像函数一样调用——这是仿函数模式
  • operator[] 返回 RowProxy 代理对象——实现 m[i][j] 的级联下标
  • 用户定义字面量(operator"" _px)让代码更具可读性——100_px 比 100 /* px */ 更自然
  • 字面量运算符的参数只能是特定类型(unsigned long long、long double、const char* 等)

示例二:智能指针的箭头运算符

#include <iostream>
#include <memory>

// 包装类——演示 operator->
template <typename T>
class AccessLogger {
public:
    explicit AccessLogger(T* ptr) : ptr_(ptr) {}

    ~AccessLogger() { delete ptr_; }

    // operator-> 返回原始指针
    T* operator->() {
        std::cout << "  [访问] operator-> 调用" << std::endl;
        return ptr_;
    }

    const T* operator->() const {
        std::cout << "  [只读访问] operator-> 调用" << std::endl;
        return ptr_;
    }

    T& operator*() {
        std::cout << "  [解引用] operator* 调用" << std::endl;
        return *ptr_;
    }

private:
    T* ptr_;
};

struct Student {
    std::string name;
    int score;
    void study() { std::cout << "  " << name << " 在学习..." << std::endl; }
};

int main() {
    std::cout << "===== operator-> 演示 =====" << std::endl;

    AccessLogger<Student> logger(new Student{"张三", 95});

    // logger->name 等价于 logger.operator->()->name
    logger->name = "张三丰";
    std::cout << "姓名: " << logger->name << std::endl;

    logger->study();

    // operator* 解引用
    (*logger).score = 98;
    std::cout << "分数: " << logger->score << std::endl;

    return 0;
}

预期输出:

===== operator-> 演示 =====
  [访问] operator-> 调用
  [访问] operator-> 调用
姓名: 张三丰
  [访问] operator-> 调用
  张三丰 在学习...
  [解引用] operator* 调用
  [访问] operator-> 调用
分数: 98

易错场景与面试考点

易错场景

场景错误表现正确做法
operator[] 只提供非 const 版本const 对象无法访问同时提供 const 和非 const 版本
operator() 滥用语义模糊保留给有明确"调用"语义的场景
operator-> 返回非指针类型编译器不断调用 operator->最终必须返回原生指针
字面量运算符重载全局作用域命名冲突放在自定义命名空间中
operator new 只重载不重载 delete不匹配的分配/释放new 和 delete 成对重载

常见面试问题

  1. operator() 和普通成员函数的区别?什么时候用?——operator() 让对象像函数一样可调用(仿函数),常用于 STL 算法(比函数指针更灵活,可保持状态)、回调、延迟计算等。相比普通成员函数,仿函数提供了统一的调用语义。

  2. operator-> 的实现原理?为什么最终必须返回原生指针?——编译器对 obj->member 的处理是:若 obj 的 operator-> 返回一个指针,直接用该指针访问成员;若返回的是对象,递归调用该对象的 operator->,直到返回原生指针。这是 C++ 标准规定的。

  3. 什么是代理类(Proxy)模式?在 operator[] 中有什么用?——通过 operator[] 返回一个代理对象来支持多维下标(如 matrix[i][j])。代理对象本身重载了 operator[],形成级联。

  4. 用户定义字面量的约束?——参数只能是 unsigned long long、long double、char、wchar_t、char16_t、char32_t 或 const char* + size_t。不能重载 C++ 内置的字面量后缀(如 f、L)。推荐以下划线 _ 开头。

  5. 为什么需要同时重载 const 和非 const 版本的 operator[] 和 operator()?——const 对象只能调用 const 成员函数。如果只有非 const 版本,const 对象或 const 引用将无法访问元素。

小结

  • operator() 创建仿函数——参数个数不限,是 STL 算法的重要搭档
  • operator[] 返回代理对象实现多维下标——需同时提供 const 和非 const 版本
  • operator-> 必须最终返回原生指针——编译器会递归调用直到得到指针
  • 用户定义字面量(C++11)让代码更具可读性——后缀必须以下划线开头
  • operator new/delete 定制内存分配/释放——成对重载,静态成员
上一页
赋值与移动运算符重载