C++基础回顾--运算符重载

C++ 中的运算符重载允许你为自定义类型(如类或结构体)赋予与内置类型相似的操作行为,这让代码更直观和易读。下面我用一个表格汇总主要的运算符重载类型、关键特点和简单示例,然后为你提供详细的代码说明。

运算符类型

主要特点

简单示例

​算术运算符​

不修改操作数,通常返回新对象

Vector v3 = v1 + v2;

​复合赋值运算符​

修改左操作数,返回左操作数的引用

v1 += v2;// v1 被修改

​关系运算符​

通常返回 bool值,用于比较

if (s1 == s2) { ... }

​输入/输出运算符​

通常为非成员友元函数,用于自定义类型的输入输出

std::cout << myObj;std::cin >> myObj;

​下标运算符 []​

通常提供 const 和非 const 版本,非 const 版本返回引用可修改元素

myArray[5] = 10;int val = myArray[5];

​递增/递减运算符​

区分前置(返回引用)和后置(返回旧值副本,参数列表含 int)

++myObj;myObj++;

​函数调用运算符 ()​

使对象能像函数一样使用,称为“仿函数”

MyFunctor obj; obj(10);

​赋值运算符 =​

​必须为成员函数​​,注意处理自赋值和深拷贝问题

MyClass obj2; obj2 = obj1;

下面是这些运算符重载的详细说明和代码案例。

📝 一、算术运算符重载

算术运算符(如 +, -, *, /)通常不修改操作数,而是返回一个新的对象。

#include <iostream>

class Vector {
public:
    double x, y;

    Vector(double x = 0, double y = 0) : x(x), y(y) {}

    // 成员函数形式重载 + 运算符
    Vector operator+(const Vector& other) const {
        return Vector(x + other.x, y + other.y);
    }

    // 友元函数形式重载 - 运算符
    friend Vector operator-(const Vector& a, const Vector& b);
};

// 实现友元函数 operator-
Vector operator-(const Vector& a, const Vector& b) {
    return Vector(a.x - b.x, a.y - b.y);
}

int main() {
    Vector v1(3.0, 4.0);
    Vector v2(1.0, 2.0);

    Vector sum = v1 + v2; // 调用 operator+
    Vector diff = v1 - v2; // 调用 operator-

    std::cout << "Sum: (" << sum.x << ", " << sum.y << ")\n";
    std::cout << "Diff: (" << diff.x << ", " << diff.y << ")\n";

    return 0;
}

说明

  • operator+被定义为类的成员函数,它使用 const引用参数,且本身也是 const成员函数,表示不修改当前对象。
  • operator-被定义为非成员函数(友元函数),以便支持左操作数不是 Vector对象的情况(虽然此例中都是)。
  • ​​成员函数局限​​:无法处理 5 + c1 这种情况(5不是 Complex对象,编译器不会尝试将 5置于 this的位置)
  • 有缘函数优势:​​​支持对称性操作​​。你可以轻松地重载以实现 c1 - 5和 5 - c1

友元函数:

  • 友元函数的作用​​实现运算符的对称性(Symmetry)​​:这是友元函数最重要的作用。对于像 +, -, *, /, ==, !=这样的双目运算符,通常期望其左右操作数可以互换(a + b和 b + a应该都能工作)。友元函数不受“左操作数必须是该类对象”的限制,可以自由定义各种类型的参数组合,从而完美支持对称操作。
  • 访问私有成员​​:友元函数被授予了访问类的私有(private)和保护(protected)成员的权限。当运算符的实现需要直接操作这些私有数据时(例如复数的加减法需要直接访问实部和虚部),友元函数就非常有用,无需依赖公开的接口(getter/setter),有时这能带来更好的封装性和效率。
  • 重载流操作符​​:重载输入 (>>) 和输出 (<<) 运算符时,​​必须​​使用友元函数(或非成员函数)。因为它们的左操作数是流对象 (std::istream或 std::ostream),而不是自定义类的对象。你无法去修改标准库中的流类来添加成员函数,所以只能以全局友元函数的形式重载。

📝 二、复合赋值运算符重载

复合赋值运算符(如 +=, -=, *=)会修改左操作数,并通常返回左操作数的引用,以支持链式操作。

class Vector {
    // ... 其他成员同上

public:
    // 复合赋值运算符 +=,成员函数形式
    Vector& operator+=(const Vector& other) {
        x += other.x;
        y += other.y;
        return *this; // 返回当前对象的引用
    }
};

int main() {
    Vector v1(3.0, 4.0);
    Vector v2(1.0, 2.0);

    v1 += v2; // v1 被修改为 (4.0, 6.0)

    std::cout << "v1 after +=: (" << v1.x << ", " << v1.y << ")\n";

    return 0;
}

说明​​:复合赋值运算符通常定义为成员函数,因为它们需要直接修改左操作数。

📝 三、关系运算符重载

关系运算符(如 ==, !=, <, >)用于比较两个对象,通常返回 bool值。

#include <iostream>
#include <string>

class Student {
public:
    std::string name;
    int id;

    Student(std::string n, int i) : name(n), id(i) {}

    // 重载 == 运算符
    bool operator==(const Student& other) const {
        return (id == other.id); // 假设学号相同即为同一学生
    }

    // 重载 != 运算符,通常复用 ==
    bool operator!=(const Student& other) const {
        return !(*this == other);
    }

    // 重载 < 运算符,按学号比较
    bool operator<(const Student& other) const {
        return id < other.id;
    }
};

int main() {
    Student s1("Alice", 1001);
    Student s2("Bob", 1002);
    Student s3("Charlie", 1001); // 学号与 s1 相同

    std::cout << "s1 == s2: " << (s1 == s2) << "\n"; // 输出 0 (false)
    std::cout << "s1 == s3: " << (s1 == s3) << "\n"; // 输出 1 (true)
    std::cout << "s1 != s2: " << (s1 != s2) << "\n"; // 输出 1 (true)
    std::cout << "s1 < s2: " << (s1 < s2) << "\n";   // 输出 1 (true)

    return 0;
}

说明​​:关系运算符通常成对实现(例如实现了 ==,最好也实现 !=),并且通常定义为 const成员函数。

📝 四、输入/输出运算符重载

输入运算符 >>和输出运算符 <<通常重载为​​非成员函数​​,并声明为类的友元,以便访问私有成员。

#include <iostream>

class Point {
private:
    int x, y;

public:
    Point(int x = 0, int y = 0) : x(x), y(y) {}

    // 声明友元函数
    friend std::ostream& operator<<(std::ostream& os, const Point& p);
    friend std::istream& operator>>(std::istream& is, Point& p);
};

// 重载输出运算符 <<
std::ostream& operator<<(std::ostream& os, const Point& p) {
    os << "(" << p.x << ", " << p.y << ")";
    return os;
}

// 重载输入运算符 >>
std::istream& operator>>(std::istream& is, Point& p) {
    is >> p.x >> p.y;
    return is;
}

int main() {
    Point p(3, 4);
    std::cout << "Point: " << p << "\n"; // 输出: Point: (3, 4)

    Point q;
    std::cout << "Enter a point (x y): ";
    std::cin >> q;
    std::cout << "You entered: " << q << "\n";

    return 0;
}

说明​​:

  • 输出运算符 <<的第一个参数是 std::ostream&(如 cout),第二个参数是要输出的对象的常量引用,返回 ostream引用以支持链式输出。
  • 输入运算符 >>的第一个参数是 std::istream&(如 cin),第二个参数是要输入的对象引用(非常量),返回 istream引用以支持链式输入。

📝 五、下标运算符 [] 重载

下标运算符 []通常用于容器类,需要重载 ​​const​​ 和​​非 const​​ 两个版本。

#include <iostream>
#include <stdexcept> // 用于 std::out_of_range

class SimpleVector {
private:
    int* data;
    size_t size;

public:
    SimpleVector(size_t s) : size(s) {
        data = new int[size];
        for (size_t i = 0; i < size; ++i) {
            data[i] = 0;
        }
    }

    ~SimpleVector() {
        delete[] data;
    }

    // 非 const 版本,返回引用,允许修改元素
    int& operator[](size_t index) {
        if (index >= size) {
            throw std::out_of_range("Index out of bounds");
        }
        return data[index];
    }

    // const 版本,返回常量引用,只读访问
    const int& operator[](size_t index) const {
        if (index >= size) {
            throw std::out_of_range("Index out of bounds");
        }
        return data[index];
    }
};

int main() {
    SimpleVector vec(5);
    vec[0] = 10; // 调用非 const 版本 []
    vec[1] = 20;

    const SimpleVector constVec(5);
    // constVec[0] = 5; // 错误:const 对象调用 const 版本 [],返回常量引用,不可修改

    std::cout << "vec[0]: " << vec[0] << "\n"; // 输出 10

    return 0;
}

说明​​:非 const 版本返回元素的引用,允许对元素进行赋值修改;const 版本返回常量引用,确保不会修改对象状态。

📝 六、递增/递减运算符重载

递增 ++和递减 --运算符有前置和后置两种形式,需要区分重载。

#include <iostream>

class Counter {
private:
    int count;

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

    // 前置递增 ++i,返回引用
    Counter& operator++() {
        ++count;
        return *this;
    }

    // 后置递增 i++,int 参数是占位符用于区分,返回旧值
    Counter operator++(int) {
        Counter temp = *this; // 保存旧值
        ++(*this);            // 调用前置递增
        return temp;          // 返回旧值
    }

    // 类似地,可以重载前置递减和后置递减

    void display() const {
        std::cout << "Count: " << count << "\n";
    }
};

int main() {
    Counter c(5);

    Counter pre = ++c; // 前置递增
    c.display();       // 输出 Count: 6
    pre.display();     // 输出 Count: 6

    Counter post = c++; // 后置递增
    c.display();        // 输出 Count: 7
    post.display();     // 输出 Count: 6 (返回的是递增前的值)

    return 0;
}

说明​​:

  • ​​前置递增​​:不接受参数(或理解为隐含的 this),返回递增后对象的引用。
  • 后置递增​​:接受一个 int类型的占位参数以区分前置版本,返回递增前对象的副本(值)。

📝 七、函数调用运算符 () 重载

重载函数调用运算符 ()的类称为​​函数对象​​或​​仿函数​​。

#include <iostream>
#include <string>

class Greeter {
public:
    // 重载 () 运算符
    void operator()(const std::string& name) const {
        std::cout << "Hello, " << name << "!\n";
    }

    // 可以重载多个版本的 ()
    int operator()(int a, int b) const {
        return a + b;
    }
};

int main() {
    Greeter greet;
    greet("Alice"); // 像函数一样使用对象,输出: Hello, Alice!

    int sum = greet(3, 4); // 调用另一个重载版本
    std::cout << "Sum: " << sum << "\n"; // 输出: Sum: 7

    // 也可以匿名对象调用
    Greeter()("World"); // 输出: Hello, World!

    return 0;
}

说明​​:函数对象可以拥有状态,比普通函数更灵活,常用于 STL 算法中。

📝 八、赋值运算符 = 重载

赋值运算符 =​​必须​​重载为成员函数。如果类管理动态资源,需实现深拷贝。

#include <iostream>
#include <cstring> // 用于 strcpy

class MyString {
private:
    char* data;
    size_t length;

public:
    // 构造函数
    MyString(const char* str = "") {
        length = std::strlen(str);
        data = new char[length + 1];
        std::strcpy(data, str);
    }

    // 析构函数
    ~MyString() {
        delete[] data;
    }

    // 拷贝构造函数(深拷贝)
    MyString(const MyString& other) {
        length = other.length;
        data = new char[length + 1];
        std::strcpy(data, other.data);
    }

    // 赋值运算符重载 (深拷贝)
    MyString& operator=(const MyString& other) {
        // 1. 检查自赋值
        if (this == &other) {
            return *this;
        }

        // 2. 释放原有资源
        delete[] data;

        // 3. 分配新资源并复制数据
        length = other.length;
        data = new char[length + 1];
        std::strcpy(data, other.data);

        // 4. 返回当前对象的引用
        return *this;
    }

    const char* getCStr() const { return data; }
};

int main() {
    MyString str1("Hello");
    MyString str2("World");

    str2 = str1; // 调用赋值运算符

    std::cout << "str2: " << str2.getCStr() << "\n"; // 输出: str2: Hello

    return 0;
}

说明​​:

  • 赋值运算符通常返回当前对象的引用(*this),以支持链式赋值(如 a = b = c)。
  • ​​关键点​​:必须处理​​自赋值​​(如 a = a)情况,否则释放资源后再访问会导致未定义行为。

💡 运算符重载的注意事项

  1. ​​保持直觉性​​:重载的运算符行为应符合用户对该运算符的直觉预期。例如,+应该实现加法,而不是减法。
  2. ​​选择成员函数还是非成员函数​​:
    • 赋值 =、下标 []、函数调用 ()、成员访问 ->​​必须​​是成员函数。
    • 复合赋值运算符(如 +=)通常也是成员函数。
    • 具有对称性的运算符(如算术运算符 +、关系运算符 ==)和输入输出运算符(<<、>>)通常是非成员函数(常声明为友元)。
  3. 成对重载​​:某些运算符通常需要成对实现,例如重载了 ==,最好也重载 !=;重载了 <,最好也重载 >、<=、>=。
  4. ​​谨慎使用​​:不要过度滥用运算符重载,导致代码可读性降低。确保重载的行为清晰明了。

合理地重载运算符可以极大提高代码的可读性和易用性,让你的自定义类型用起来就像内置类型一样自然。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值