C++ 运算符重载:自定义类型的运算扩展

C++ 运算符重载:自定义类型的运算扩展

💡 学习目标 :掌握运算符重载的核心语法与规则,能够为自定义类型重载常用运算符,实现类对象的灵活运算。

💡 学习重点:运算符重载的基本形式、成员函数与全局函数重载的区别、常见运算符的重载实现、禁止重载的运算符。

一、运算符重载的概念与核心价值

✅ 结论:运算符重载是 C++ 静态多态的重要体现,允许为自定义类型(如类、结构体)重新定义运算符的行为,让自定义对象可以像内置类型一样使用运算符。

运算符重载的核心价值:

  1. 简化代码书写:用直观的运算符替代繁琐的成员函数调用,提升代码可读性
  2. 统一操作风格:让自定义类型的运算逻辑与内置类型保持一致,降低学习和使用成本
  3. 扩展类型功能:根据业务需求定制运算符的行为,满足自定义类型的运算需求

⚠️ 注意事项:运算符重载不会改变运算符的优先级和结合性,也不会改变运算符的操作数个数。

二、运算符重载的基本语法

运算符重载的本质是函数重载 ,分为成员函数重载 和全局函数重载两种形式。

2.1 成员函数重载语法

将运算符重载函数定义为类的成员函数,语法格式如下:

cpp 复制代码
class 类名 {
public:
    返回值类型 operator运算符(参数列表) {
        // 自定义运算逻辑
    }
};
  • 一元运算符(如 ++、--)作为成员函数时,没有参数
  • 二元运算符(如 +、-)作为成员函数时,只有一个参数,表示运算符右侧的操作数

2.2 全局函数重载语法

将运算符重载函数定义为全局函数,语法格式如下:

cpp 复制代码
返回值类型 operator运算符(参数列表) {
    // 自定义运算逻辑
}
  • 一元运算符作为全局函数时,有一个参数
  • 二元运算符作为全局函数时,有两个参数,分别表示左右两侧的操作数
  • 如果需要访问类的私有成员,可将全局重载函数声明为类的友元函数

三、常见运算符的重载实现

3.1 二元运算符重载:以 + 为例

以自定义 Point 类为例,重载 + 运算符实现两个点的坐标相加。

3.1.1 成员函数重载 + 运算符
cpp 复制代码
#include <iostream>
using namespace std;

class Point {
public:
    int x, y;

    // 构造函数
    Point(int x = 0, int y = 0) : x(x), y(y) {}

    // 成员函数重载 + 运算符
    Point operator+(const Point& p) {
        return Point(this->x + p.x, this->y + p.y);
    }

    // 打印点坐标
    void print() {
        cout << "(" << x << ", " << y << ")" << endl;
    }
};

int main() {
    Point p1(1, 2), p2(3, 4);
    Point p3 = p1 + p2; // 等价于 p1.operator+(p2)
    p3.print(); // 输出 (4, 6)
    return 0;
}
3.1.2 全局函数重载 + 运算符
cpp 复制代码
#include <iostream>
using namespace std;

class Point {
public:
    int x, y;

    // 构造函数
    Point(int x = 0, int y = 0) : x(x), y(y) {}

    // 打印点坐标
    void print() {
        cout << "(" << x << ", " << y << ")" << endl;
    }

    // 声明友元函数
    friend Point operator+(const Point& p1, const Point& p2);
};

// 全局函数重载 + 运算符
Point operator+(const Point& p1, const Point& p2) {
    return Point(p1.x + p2.x, p1.y + p2.y);
}

int main() {
    Point p1(1, 2), p2(3, 4);
    Point p3 = p1 + p2; // 等价于 operator+(p1, p2)
    p3.print(); // 输出 (4, 6)
    return 0;
}

3.2 一元运算符重载:以 ++ 为例

一元运算符分为前置++和后置++,二者的重载方式有区别。

3.2.1 前置 ++ 运算符重载

前置 ++ 表示先自增,再使用,重载时没有参数。

cpp 复制代码
#include <iostream>
using namespace std;

class Counter {
private:
    int count;
public:
    Counter(int c = 0) : count(c) {}

    // 成员函数重载前置 ++
    Counter& operator++() {
        this->count++;
        return *this; // 返回自增后的对象,支持链式操作
    }

    void show() {
        cout << "计数:" << count << endl;
    }
};

int main() {
    Counter c(5);
    ++c; // 等价于 c.operator++()
    c.show(); // 输出 计数:6

    Counter c2 = ++c;
    c2.show(); // 输出 计数:7
    return 0;
}
3.2.2 后置 ++ 运算符重载

后置 ++ 表示先使用,再自增,重载时需要添加一个占位参数 int 区分前置版本。

cpp 复制代码
#include <iostream>
using namespace std;

class Counter {
private:
    int count;
public:
    Counter(int c = 0) : count(c) {}

    // 成员函数重载后置 ++
    Counter operator++(int) {
        Counter temp = *this; // 保存当前对象状态
        this->count++;
        return temp; // 返回自增前的对象
    }

    void show() {
        cout << "计数:" << count << endl;
    }
};

int main() {
    Counter c(5);
    Counter c2 = c++; // 等价于 c.operator++(0)
    c.show();  // 输出 计数:6
    c2.show(); // 输出 计数:5
    return 0;
}

3.3 关系运算符重载:以 == 为例

重载关系运算符(==、!=、<、> 等),实现自定义对象的比较逻辑。

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

class Student {
private:
    string name;
    int id;
public:
    Student(string name, int id) : name(name), id(id) {}

    // 成员函数重载 == 运算符
    bool operator==(const Student& s) {
        return this->id == s.id; // 按学号判断是否为同一学生
    }

    string getName() {
        return name;
    }
};

int main() {
    Student s1("张三", 2024001);
    Student s2("李四", 2024002);
    Student s3("张三", 2024001);

    if (s1 == s3) {
        cout << s1.getName() << " 和 " << s3.getName() << " 是同一学生" << endl;
    } else {
        cout << "不是同一学生" << endl;
    }

    if (s1 == s2) {
        cout << "是同一学生" << endl;
    } else {
        cout << s1.getName() << " 和 " << s2.getName() << " 不是同一学生" << endl;
    }
    return 0;
}

3.4 输入输出运算符重载:<< 和 >>

<< 和 >> 运算符通常需要全局函数 + 友元 的方式重载,因为左侧操作数是 ostream 或 istream 对象。

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

class Person {
private:
    string name;
    int age;
public:
    Person(string name = "", int age = 0) : name(name), age(age) {}

    // 声明友元函数,重载 << 运算符
    friend ostream& operator<<(ostream& os, const Person& p);
    // 声明友元函数,重载 >> 运算符
    friend istream& operator>>(istream& is, Person& p);
};

// 重载 << 运算符,用于输出对象
ostream& operator<<(ostream& os, const Person& p) {
    os << "姓名:" << p.name << ",年龄:" << p.age;
    return os; // 返回 os,支持链式输出
}

// 重载 >> 运算符,用于输入对象
istream& operator>>(istream& is, Person& p) {
    is >> p.name >> p.age;
    return is; // 返回 is,支持链式输入
}

int main() {
    Person p;
    cout << "请输入姓名和年龄:" << endl;
    cin >> p; // 等价于 operator>>(cin, p)
    cout << "你输入的信息:" << p << endl; // 等价于 operator<<(cout, p)
    return 0;
}

四、运算符重载的限制条件

4.1 禁止重载的运算符

以下运算符不允许重载,使用时需注意:

  • 成员访问运算符:.
  • 成员指针访问运算符:.*
  • 作用域解析运算符:::
  • 条件运算符:?:
  • 预处理运算符:#

4.2 必须以成员函数重载的运算符

以下运算符只能通过类的成员函数重载,不能使用全局函数:

  • 赋值运算符:=
  • 函数调用运算符:()
  • 下标运算符:[]
  • 箭头运算符:->

4.3 重载赋值运算符 = 的注意事项

赋值运算符 = 是类的默认成员函数,编译器会自动生成一个浅拷贝版本。当类中包含指针成员时,必须手动重载 = 实现深拷贝,避免浅拷贝导致的内存泄漏。

cpp 复制代码
#include <iostream>
#include <cstring>
using namespace std;

class String {
private:
    char *str;
public:
    // 构造函数
    String(const char *s = "") {
        str = new char[strlen(s) + 1];
        strcpy(str, s);
    }

    // 析构函数
    ~String() {
        delete[] str;
    }

    // 重载赋值运算符 =,实现深拷贝
    String& operator=(const String& s) {
        if (this == &s) { // 防止自赋值
            return *this;
        }

        // 释放当前对象的内存
        delete[] str;

        // 分配新内存并拷贝数据
        str = new char[strlen(s.str) + 1];
        strcpy(str, s.str);

        return *this; // 支持链式赋值
    }

    void show() {
        cout << str << endl;
    }
};

int main() {
    String s1("Hello C++");
    String s2;
    s2 = s1; // 调用重载的 = 运算符
    s2.show(); // 输出 Hello C++
    return 0;
}

⚠️ 核心注意点 :重载赋值运算符时,必须返回 *this 的引用,否则无法支持链式赋值(如 a = b = c)。

五、实战案例:重载运算符实现复数运算

💡 需求:设计一个 Complex 类表示复数,重载 +、-、*、<< 运算符,实现复数的加减乘运算和输出功能。

5.1 需求分析

  1. 复数的形式为 a+bia + bia+bi,包含实部 real 和虚部 imag 两个成员变量
  2. 重载 +:(a+bi)+(c+di)=(a+c)+(b+d)i(a+bi)+(c+di) = (a+c)+(b+d)i(a+bi)+(c+di)=(a+c)+(b+d)i
  3. 重载 -:(a+bi)−(c+di)=(a−c)+(b−d)i(a+bi)-(c+di) = (a-c)+(b-d)i(a+bi)−(c+di)=(a−c)+(b−d)i
  4. 重载 *:(a+bi)∗(c+di)=(ac−bd)+(ad+bc)i(a+bi)*(c+di) = (ac-bd)+(ad+bc)i(a+bi)∗(c+di)=(ac−bd)+(ad+bc)i
  5. 重载 <<:输出复数的标准格式

5.2 完整代码实现

cpp 复制代码
#include <iostream>
using namespace std;

class Complex {
private:
    double real; // 实部
    double imag; // 虚部
public:
    // 构造函数
    Complex(double real = 0, double imag = 0) : real(real), imag(imag) {}

    // 成员函数重载 + 运算符
    Complex operator+(const Complex& c) {
        return Complex(this->real + c.real, this->imag + c.imag);
    }

    // 成员函数重载 - 运算符
    Complex operator-(const Complex& c) {
        return Complex(this->real - c.real, this->imag - c.imag);
    }

    // 成员函数重载 * 运算符
    Complex operator*(const Complex& c) {
        double r = this->real * c.real - this->imag * c.imag;
        double i = this->real * c.imag + this->imag * c.real;
        return Complex(r, i);
    }

    // 友元函数重载 << 运算符
    friend ostream& operator<<(ostream& os, const Complex& c);
};

// 实现 << 运算符重载
ostream& operator<<(ostream& os, const Complex& c) {
    if (c.imag >= 0) {
        os << c.real << " + " << c.imag << "i";
    } else {
        os << c.real << " - " << -c.imag << "i";
    }
    return os;
}

int main() {
    Complex c1(3, 4), c2(1, -2);

    Complex c3 = c1 + c2;
    Complex c4 = c1 - c2;
    Complex c5 = c1 * c2;

    cout << "c1 = " << c1 << endl;
    cout << "c2 = " << c2 << endl;
    cout << "c1 + c2 = " << c3 << endl;
    cout << "c1 - c2 = " << c4 << endl;
    cout << "c1 * c2 = " << c5 << endl;

    return 0;
}

5.3 运行结果

复制代码
c1 = 3 + 4i
c2 = 1 - 2i
c1 + c2 = 4 + 2i
c1 - c2 = 2 + 6i
c1 * c2 = 11 - 2i

六、运算符重载的开发规范

  1. 保持语义一致:重载后的运算符行为应与内置类型的语义相近,避免让使用者产生困惑
  2. 优先使用成员函数 :对于一元运算符和复合赋值运算符(如 +=、-=),优先使用成员函数重载
  3. 输入输出用全局友元 :<< 和 >> 运算符必须使用全局函数 + 友元的方式重载
  4. 深拷贝处理 :当类包含指针成员时,必须手动重载赋值运算符 = 实现深拷贝
  5. 避免过度重载:只重载实际需要的运算符,不要为了重载而重载,以免增加代码复杂度

七、本章总结

✅ 运算符重载的本质是函数重载,分为成员函数和全局函数两种实现方式。

✅ 运算符重载不会改变运算符的优先级、结合性和操作数个数。

✅ 部分运算符有固定的重载方式,如 = 必须用成员函数,<< 必须用全局友元函数。

✅ 合理使用运算符重载可以简化代码、统一风格,是 C++ 面向对象编程的重要技能。

相关推荐
驭渊的小故事9 分钟前
Spring AI Alibaba 实战:从maven依赖治理到声明式工具调用
java·人工智能·spring
Elastic 中国社区官方博客32 分钟前
我们如何通过 70 多个拉取请求,将 OpenTelemetry Java agent 迁移到 invokedynamic
java·大数据·开发语言·elasticsearch·搜索引擎·全文检索·es
tyn1881 小时前
记录本地安装nacos服务的方法及异常
java·开发语言
造火箭1 小时前
安卓不同类型自动化测试介绍
android
步行cgn1 小时前
Spring MVC 视图机制的核心接口与类
java·开发语言·后端·spring·mvc
y1su2 小时前
Leetcode 字符串模板
java·数据结构·算法·leetcode·职场和发展·动态规划
syagain_zsx3 小时前
BRpc 二次封装:Channel 管理、服务管理与回调工厂
c++·学习·程序人生·rpc·谷歌
我命由我123453 小时前
Python 开发 - Python 的 pathlib
java·开发语言·后端·python·学习·java-ee·学习方法
苜蓿muxu3 小时前
Java 字符串拼接:+、StringBuilder、StringBuffer的区别
java
摇滚侠3 小时前
《Java并发编程之美》第 4 章 Java 并发包中原子操作类原理剖析 阅读笔记 7
java·笔记·python