运算符重载知识点总结
  zhRhucGD3dLm 2023年11月02日 57 0

# 运算符重载

在数学上,两个复数可以直接进行+、-等运算。但在C++中,直接将+或-用于复数对象是不允许的。

• 有时会希望,让对象也能通过运算符进行运算。这样代码更简洁,容易理解。

• 例如:complex_a和complex_b是两个复数对象;求两个复数的和, 希望能直接写:


complex_a + complex_b



在数学上,两个复数可以直接进行+、-等运算。但在C++中,直接将+或-用于复数对象是不允许的。

• 有时会希望,让对象也能通过运算符进行运算。这样代码更简洁,容易理解。

• 例如:complex_a和complex_b是两个复数对象;


求两个复数的和, 希望能直接写:


complex_a + complex_b


运算符重载(Operator Overloading)是一种特性,允许我们重新定义已有的运算符的行为,以适应自定义类型的操作。在编程语言中,运算符通常只能用于内置类型或标准库提供的类型,但通过运算符重载,我们可以为自定义类型赋予相应的运算符行为。

在大多数面向对象编程语言中,如C++、Python和Java,都支持运算符重载。不同的语言可能对运算符重载的实现方式有所区别。




以C++为例,通过在类中定义特殊的成员函数,可以实现对运算符的重载。例如,当我们创建一个名为"Vector"的自定义类时,可以重载"+"运算符来执行向量的加法操作:

```cpp

class Vector {

public:

    int x, y;

    Vector(int x, int y) : x(x), y(y) {}

    Vector operator+(const Vector& v) {

        return Vector(x + v.x, y + v.y);

    }

};

int main() {

    Vector v1(1, 2);

    Vector v2(3, 4);

    Vector result = v1 + v2;

    // result.x = 1+3 = 4, result.y = 2+4 = 6

    return 0;

}

```

在上述示例中,通过重载"+"运算符,我们可以直接使用"+"操作符来对两个Vector对象进行加法运算。

需要注意的是,在进行运算符重载时,要遵循一些约定和规则,以确保正确的行为。这包括运算符的参数类型、返回类型、操作数数量等。


当进行运算符重载时,我们可以重载多个不同的运算符,并给它们赋予适合自定义类型的行为。以下是一些常见的运算符,可以在许多编程语言中进行重载:


1. 算术运算符:例如 +、-、*、/ 等。通过重载这些运算符,我们可以定义自定义类型之间的加法、减法、乘法和除法操作。

2. 关系运算符:例如 ==、!=、<、>、<=、>= 等。通过重载这些运算符,我们可以定义自定义类型之间的相等性、大小比较等关系操作。

3. 赋值运算符:通常为 =。重载赋值运算符可以使自定义类型支持对象之间的赋值操作。

4. 下标运算符:通常为 []。通过重载下标运算符,我们可以使自定义类型像数组一样使用下标来访问元素。

5. 函数调用运算符:通常为 ()。重载函数调用运算符使得对象可以像函数一样被调用。

6. 类型转换运算符:通过重载类型转换运算符,我们可以使对象能够隐式或显式地转换为其他类型。

7.

要注意的是,在进行运算符重载时,应遵循一些约定和规则,以确保代码的清晰性和可读性。一些常见的指导原则包括:

- 不要改变运算符的原始含义,以免引起混淆。

- 遵循运算符的语义约定,例如加法运算符应进行加法操作。

- 保持重载的运算符的行为与内置类型的一致性,以避免意外的行为。



此外,不同编程语言可能对运算符重载有自己的规则和限制。在使用时,建议查阅相关文档或参考示例代码,以确保正确地进行运算符重载。


总之,运算符重载使得我们可以为自定义类型赋予特定的运算符行为,提供更灵活、直观的代码编写方式,并增强代码的可读性和可维护性。



**运算符重载的实质是函数重载**



**可以重载为普通函数,也可以重载为成员函数**


**把含运算符的表达式转换成对运算符函数的调用。**


**把运算符的操作数转换成运算符函数的参数。**


**运算符被多次重载时,根据实参的类型决定调用哪个运算符函数**




## 运算符重载示例

下面是几个在C++中进行运算符重载的示例:

1. 加法运算符重载:

```cpp

#include <iostream>

class Vector {

public:

    int x, y;

    Vector(int x, int y) : x(x), y(y) {}

    Vector operator+(const Vector& v) {

        return Vector(x + v.x, y + v.y);

    }

};

int main() {

    Vector v1(1, 2);

    Vector v2(3, 4);

    Vector result = v1 + v2;

    std::cout << result.x << " " << result.y << std::endl;

    return 0;

}

```

输出结果:4 6

2. 关系运算符重载:

```cpp

#include <iostream>

class Date {

public:

    int year, month, day;

    Date(int year, int month, int day) : year(year), month(month), day(day) {}

    bool operator==(const Date& other) {

        return (year == other.year && month == other.month && day == other.day);

    }

    bool operator!=(const Date& other) {

        return !(*this == other);

    }

};

int main() {

    Date d1(2022, 10, 1);

    Date d2(2022, 10, 1);


    if (d1 == d2) {

        std::cout << "Dates are equal" << std::endl;

    } else {

        std::cout << "Dates are not equal" << std::endl;

    }


    return 0;

}

```

输出结果:Dates are equal

3. 赋值运算符重载:

```cpp

#include <iostream>

class Point {

public:

    int x, y;

    Point(int x, int y) : x(x), y(y) {}

    Point& operator=(const Point& other) {

        x = other.x;

        y = other.y;

        return *this;

    }

};

int main() {

    Point p1(1, 2);

    Point p2(3, 4);


    p1 = p2;


    std::cout << p1.x << " " << p1.y << std::endl;


    return 0;

}

```

输出结果:3 4


这些示例演示了如何重载不同类型的运算符,使自定义类型具有相应的行为。在实际使用中,可以根据需要进行更多类型的运算符重载,并根据具体情况来定义运算符的行为。请注意,上述代码只是示例,实际应用中可能需要添加错误处理、边界检查等额外的逻辑。



## 赋值运算符的重载

有时候希望赋值运算符两边的类型可以不匹配,比如,把一个int类型变量赋值给一个Complex对象,

或把一个 char * 类型的字符串赋值给一个字符串对象,此时就需要重载赋值运算符“=”。赋值运算符“=”只能重载为成员函数。





赋值运算符"="只能作为类的成员函数进行重载。在C++中,赋值运算符的重载函数必须是一个成员函数,并且没有返回类型。


如果想要实现不同类型之间的赋值操作,可以使用类型转换构造函数(或转换运算符)来实现类型的隐式转换。然后再通过重载赋值运算符来执行相应的赋值操作。


以下是一个示例,展示了将int类型的变量赋值给Complex对象的情况:

```cpp

#include <iostream>

class Complex {

public:

    double real, imag;

    Complex(double real = 0.0, double imag = 0.0) : real(real), imag(imag) {}

    // 转换构造函数

    Complex(int value) : real(value), imag(0.0) {}

    // 赋值运算符重载

    Complex& operator=(const Complex& other) {

        if (this == &other) {

            return *this;

        }

        real = other.real;

        imag = other.imag;

        return *this;

    }

};

int main() {

    int intValue = 5;

    Complex complexObj;

    complexObj = intValue;  // 将int类型的变量赋值给Complex对象

    std::cout << complexObj.real << " + " << complexObj.imag << "i" << std::endl;

    return 0;

}

```

在上述示例中,通过定义Complex类的转换构造函数,我们可以将int类型的值隐式转换为Complex对象。然后,在赋值运算符重载函数中,我们可以将右侧的Complex对象的成员变量值赋给当前对象。


需要注意的是,在进行类型转换时,应该确保转换是合理和安全的,并且不会导致数据丢失或意外行为。同时,赋值运算符的行为应符合预期,确保正确处理各种边界情况。


总结起来,通过利用类型转换构造函数和赋值运算符重载,我们可以实现不同类型之间的赋值操作,提供更灵活的代码编写方式,以适应特定需求。





### 浅拷贝和深拷贝

浅拷贝和深拷贝是在对象复制过程中涉及的两个概念,用于描述如何复制对象及其数据。

1. 浅拷贝(Shallow Copy):

浅拷贝是指将一个对象的值复制到另一个对象,但仅复制对象本身的成员变量,而不复制动态分配的资源。这意味着,对于指向内存资源(如堆内存)的指针成员变量,仅复制指针的值,而不创建新的资源副本。因此,原始对象和副本对象将共享同一块内存。

示例:

```cpp

#include <iostream>

class MyString {

public:

    char* data;

    MyString(const char* str = nullptr) {

        if (str != nullptr) {

            int length = std::strlen(str);

            data = new char[length + 1];

            std::strcpy(data, str);

        } else {

            data = nullptr;

        }

    }

    // 拷贝构造函数(浅拷贝)

    MyString(const MyString& other) {

        data = other.data;  // 仅复制指针的值

    }

};

int main() {

    MyString original("Hello");

    MyString copy(original);  // 调用拷贝构造函数(浅拷贝)

    original.data[0] = 'X';  // 修改原始对象的数据

    std::cout << "Original: " << original.data << std::endl;  // 输出:Xello

    std::cout << "Copy: " << copy.data << std::endl;  // 输出:Xello(共享同一块内存)

    return 0;

}

```

在上述示例中,浅拷贝的拷贝构造函数仅复制指针的值,这意味着原始对象和副本对象将共享相同的data指针,即它们指向同一块内存。如果修改了其中一个对象的data数据,会影响另一个对象。

2. 深拷贝(Deep Copy):

深拷贝是指将一个对象及其相关资源复制到另一个对象,包括动态分配的内存资源。深拷贝创建了一个新的独立对象,其中包含与原始对象完全相同的数据,但是它们使用不同的内存空间。因此,对其中一个对象进行修改不会影响另一个对象。

示例:

```cpp

#include <iostream>

class MyString {

public:

    char* data;

    MyString(const char* str = nullptr) {

        if (str != nullptr) {

            int length = std::strlen(str);

            data = new char[length + 1];

            std::strcpy(data, str);

        } else {

            data = nullptr;

        }

    }

    // 深拷贝的拷贝构造函数

    MyString(const MyString& other) {

        if (other.data != nullptr) {

            int length = std::strlen(other.data);

            data = new char[length + 1];

            std::strcpy(data, other.data);

        } else {

            data = nullptr;

        }

    }

    // 深拷贝的赋值运算符重载

    MyString& operator=(const MyString& other) {

        if (this == &other) {  // 检查自我赋值

            return *this;

        }

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

        if (other.data != nullptr) {

            int length = std::strlen(other.data);

            data = new char[length + 1];

            std::strcpy(data, other.data);

        } else {

            data = nullptr;

        }

        return *this;

    }

};

int main() {

    MyString original("Hello");

    MyString copy(original);  // 调用深拷贝的拷贝构造函数

    original.data[0] = 'X';  // 修改原始对象的数据

    std::cout << "Original: " << original.data << std::endl;  // 输出:Xello

    std::cout << "Copy: " << copy.data << std::endl;  // 输出:Hello(独立的内存空间)

    return 0;

}

```

在上述示例中,深拷贝的拷贝构造函数和赋值运算符重载会创建一个新的data数组,并将原始对象的数据复制到其中。这样,原始对象和副本对象将使用不同的内存空间,修改其中一个对象的data数据不会影响另一个对象。


**总结:**


浅拷贝只是简单地复制成员变量的值,包括指针的值,而不复制相关资源。深拷贝则复制了对象及其相关资源,创建了一个独立的新对象,避免了对象之间共享资源的问题。在设计类时,需要根据实际需求决定是使用浅拷贝还是深拷贝,并在拷贝构造函数和赋值运算符重载中进行相应的处理。



## 运算符重载为友元函数

一般情况下,将运算符重载为类的成员函数,是较好的选择。


但有时,重载为成员函数不能满足使用要求,重载为普通函数,又不能访问类的私有成员,所以需要将运算符重载为友元。


在C++中,运算符重载既可以作为成员函数进行重载,也可以作为友元函数进行重载。通过将运算符重载声明为友元函数,我们可以访问类的私有成员变量,并且不需要通过类的对象来调用运算符。

下面是一个示例,展示了如何将加法运算符"+"重载为友元函数:

```cpp

#include <iostream>

class Complex {

public:

    double real, imag;

    Complex(double real = 0.0, double imag = 0.0) : real(real), imag(imag) {}

    // 声明友元函数

    friend Complex operator+(const Complex& c1, const Complex& c2);

};

// 定义友元函数

Complex operator+(const Complex& c1, const Complex& c2) {

    return Complex(c1.real + c2.real, c1.imag + c2.imag);

}

int main() {

    Complex c1(1.0, 2.0);

    Complex c2(3.0, 4.0);


    Complex result = c1 + c2;  // 调用友元函数

    std::cout << "Real: " << result.real << ", Imaginary: " << result.imag << std::endl;


    return 0;

}

```

在上述示例中,我们将加法运算符"+"声明为Complex类的友元函数。这样,我们可以在友元函数中直接访问Complex类的私有成员变量real和imag,并执行相应的加法操作。在主函数中,我们通过调用友元函数来执行两个Complex对象的加法运算,并将结果存储在result对象中。


需要注意的是,友元函数声明应放在类的定义中,并且在类的外部定义实际的友元函数。这样可以确保友元函数能够访问类的私有成员变量。


总结起来,通过将运算符重载声明为友元函数,我们可以直接访问类的私有成员变量,并实现对自定义类型的运算符重载。友元函数提供了一种更灵活的方式来定义运算符重载,尤其在需要访问类的私有成员时非常有用。


## 运算符重载实例:可变长整型数组

下面是一个示例,展示了如何通过运算符重载创建一个可变长整型数组类,并实现对数组进行加法运算的功能:

```cpp

#include <iostream>

#include <vector>

class VarIntArray {

private:

    std::vector<int> data;

public:

    VarIntArray() {}

    VarIntArray(std::initializer_list<int> list) : data(list) {}

    // 运算符重载:加法运算符

    VarIntArray operator+(const VarIntArray& other) const {

        VarIntArray result;

        size_t maxSize = std::max(data.size(), other.data.size());

        for (size_t i = 0; i < maxSize; i++) {

            int value1 = (i < data.size()) ? data[i] : 0;

            int value2 = (i < other.data.size()) ? other.data[i] : 0;

            result.data.push_back(value1 + value2);

        }

        return result;

    }

    void print() const {

        for (int num : data) {

            std::cout << num << " ";

        }

        std::cout << std::endl;

    }

};

int main() {

    VarIntArray arr1 = {1, 2, 3};

    VarIntArray arr2 = {4, 5, 6, 7};

    VarIntArray result = arr1 + arr2;

    arr1.print();       // 输出:1 2 3

    arr2.print();       // 输出:4 5 6 7

    result.print();     // 输出:5 7 9 7

    return 0;

}

```

在上述示例中,我们定义了一个VarIntArray类来表示可变长整型数组。通过使用std::vector<int>来存储实际的数组数据。在构造函数中,我们使用了std::initializer_list<int>来接受初始化列表,方便创建对象时传递初始值。


然后,我们重载了加法运算符"+",使得两个VarIntArray对象可以通过加法运算进行相加。在重载函数中,我们根据两个数组长度的较大值,遍历对应位置上的元素,将其相加并添加到结果数组中。


最后,在主函数中,我们创建了两个VarIntArray对象arr1和arr2,并使用加法运算符将它们相加,将结果存储在result对象中,然后分别输出三个对象的内容。


通过运算符重载,我们可以使自定义类型具有与内置类型类似的行为,提供更直观和灵活的操作方式。对于可变长数组这样的类,通过重载加法运算符,可以很方便地实现数组的合并操作。



## 流插入运算符流提取运算符的重载

流插入运算符和流提取运算符是C++中常用的运算符重载之一,它们分别用于将自定义类型的对象插入到输出流中和从输入流中提取对象。

1. 流插入运算符"<<"

流插入运算符重载函数通常返回一个std::ostream&类型,并接受两个参数:一个是要输出的流对象(如std::ostream),另一个是要插入到流中的自定义类型对象。在重载函数中,我们可以根据需要将自定义类型的成员变量以特定的格式插入到输出流中,并返回流对象本身。

以下是一个示例,展示了如何重载流插入运算符:

```cpp

#include <iostream>

class Point {

public:

    int x, y;

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

    // 流插入运算符重载

    friend std::ostream& operator<<(std::ostream& out, const Point& p);

};

// 定义流插入运算符重载函数

std::ostream& operator<<(std::ostream& out, const Point& p) {

    out << "(" << p.x << ", " << p.y << ")";

    return out;

}

int main() {

    Point p(3, 4);

    std::cout << "Point: " << p << std::endl;  // 使用流插入运算符输出自定义类型对象

    return 0;

}

```

在上述示例中,我们定义了一个Point类来表示二维坐标点。通过将流插入运算符重载声明为友元函数,我们可以在重载函数中直接访问Point类的私有成员变量,并将其以特定的格式插入到输出流中。

在主函数中,我们创建了一个Point对象p,并使用流插入运算符将其插入到std::cout输出流中,将结果打印到控制台上。

2. 流提取运算符">>"

流提取运算符重载函数通常返回一个std::istream&类型,并接受两个参数:一个是要输入的流对象(如std::istream),另一个是要从流中提取的自定义类型对象的引用。在重载函数中,我们可以根据需要从输入流中读取数据,并将其赋值给自定义类型对象的成员变量。

以下是一个示例,展示了如何重载流提取运算符:

```cpp

#include <iostream>

class Point {

public:

    int x, y;

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

    // 流提取运算符重载

    friend std::istream& operator>>(std::istream& in, Point& p);

};

// 定义流提取运算符重载函数

std::istream& operator>>(std::istream& in, Point& p) {

    in >> p.x >> p.y;

    return in;

}

int main() {

    Point p;

    std::cout << "Enter coordinates (x y): ";

    std::cin >> p;  // 使用流提取运算符从输入流中提取自定义类型对象

    std::cout << "Point: (" << p.x << ", " << p.y << ")" << std::endl;

    return 0;

}

```

在上述示例中,我们同样定义了一个Point类来表示二维坐标点。通过将流提取运算符重载声明为友元函数,我们可以在重载函数中直接访问Point类的私有成员变量,并从输入流中读取数据并赋值给它们。

在主函数中,我们创建了一个Point对象p,并使用流提取运算符从std::cin输入流中提取数据,并将其赋值给p的成员变量。然后,我们将p的坐标打印到控制台上。


通过重载流插入运算符和流提取运算符,我们可以方便地将自定义类型对象插入到输出流中,并从输入流中提取数据赋值给对象的成员变量。这样,我们可以使用标准的输入输出方式与自定义类型对象进行交互。



## 类型转换运算符和自增、自减运算符的重载

类型转换运算符和自增、自减运算符是C++中常用的运算符重载之一,它们可以让我们更方便地进行类型转换和实现对象的自增、自减操作。

1. 类型转换运算符

类型转换运算符用于将一个类的对象转换为另一个类型。在C++中,类型转换运算符可以被重载为成员函数,并且没有返回类型。

以下是一个示例,展示了如何重载类型转换运算符:

```cpp

#include <iostream>

class MyString {

private:

    std::string data;

public:

    MyString(const std::string& str = "") : data(str) {}

    // 类型转换运算符重载:将MyString转换为std::string

    operator std::string() const {

        return data;

    }

};

int main() {

    MyString myStr("Hello");

    std::string str = static_cast<std::string>(myStr);  // 使用类型转换运算符将MyString转换为std::string

    std::cout << str << std::endl;  // 输出:Hello

    return 0;

}

```

在上述示例中,我们定义了一个MyString类来表示字符串对象。通过重载类型转换运算符,我们可以将MyString对象转换为std::string类型。在重载函数中,我们只需返回MyString对象的data成员变量即可。

在主函数中,我们创建了一个MyString对象myStr,并使用类型转换运算符将其转换为std::string类型,将结果存储在str对象中。然后,我们将str对象输出到控制台上。

2. 自增、自减运算符

自增和自减运算符用于对对象进行递增或递减操作。在C++中,自增和自减运算符可以被重载为成员函数,分别对应前缀形式和后缀形式。

以下是一个示例,展示了如何重载自增、自减运算符:

```cpp

#include <iostream>

class Counter {

private:

    int count;

public:

    Counter(int value = 0) : count(value) {}

    // 前缀形式自增运算符重载

    Counter& operator++() {

        ++count;

        return *this;

    }

    // 后缀形式自增运算符重载

    Counter operator++(int) {

        Counter temp(*this);

        ++count;

        return temp;

    }

};

int main() {

    Counter counter(5);

    ++counter;  // 使用前缀形式自增运算符递增对象

    std::cout << "Count: " << counter.count << std::endl;  // 输出:Count: 6

    counter++;  // 使用后缀形式自增运算符递增对象

    std::cout << "Count: " << counter.count << std::endl;  // 输出:Count: 7

    return 0;

}

```

在上述示例中,我们定义了一个Counter类来表示计数器对象。通过重载自增运算符,我们可以实现对象的自增操作。在重载函数中,前缀形式自增运算符返回递增后的对象本身,而后缀形式自增运算符则返回递增前的对象的副本。


在主函数中,我们创建了一个Counter对象counter,并连续使用前缀和后缀形式的自增运算符对其进行递增操作。然后,我们将计数器的值输出到控制台上。


通过重载自增、自减运算符,我们可以方便地实现自定义类型对象的自增、自减操作,提供更直观的操作方式。需要注意的是,在重载自增、自减运算符时,应根据语义约定和常规用法来定义运算符的行为。




## 运算符重载的注意事项

1. C++不允许定义新的运算符 ;



2. 重载后运算符的含义应该符合日常习惯;


~~~cpp

complex_a + complex_b

word_a > word_b

date_b = date_a + n

~~~


3. 运算符重载不改变运算符的优先级;



4. 以下运算符不能被重载:“.”、“.*”、“::”、“?:”、sizeof;



6. 重载运算符()、[]、->或者赋值运算符=时,运算符重载函数必须声明为类的成员函数。



在进行运算符重载时,有一些重要的注意事项需要考虑:


1. 符合语义:运算符重载应与其原始含义相符,遵循常规用法和语义约定。这样可以提高代码的可读性,并减少使用者的困惑。



2. 类型匹配:运算符重载应适用于自定义类型,并与内置类型的行为保持一致。确保操作数的类型与预期一致,并考虑各种可能的组合和转换情况。



3. 返回类型:运算符重载函数应返回适当的类型,以确保正确的结果。通常将返回值作为引用或常量引用,以避免不必要的副本构造。

4. 顺序和优先级:运算符重载的行为和优先级应与其原始含义一致。如果需要改变优先级,请使用括号来明确表达意图。

5. 自我赋值检查:对于赋值运算符等涉及到自我赋值的情况,应首先检查对象是否与自身相同。如果是,则直接返回当前对象的引用,以避免意外行为。

6. 异常安全性:保证在运算符重载中处理异常,以确保程序的安全性和稳定性。在分配内存、访问资源等可能引发异常的操作中,应采取适当的异常处理措施。

7. 常量成员函数:在某些情况下,运算符重载函数可能需要声明为常量成员函数。这样可以确保运算符的重载不会修改对象的状态。

8. 一致性和预期行为:运算符重载的行为应与用户的预期一致,并遵循编程语言的规范和约定。提供适当的文档、注释和示例代码,以帮助其他开发者正确使用运算符重载。



在进行运算符重载时,建议查阅相关的文档和规范,并参考标准库中对于运算符的重载实现。此外,进行单元测试和边界条件的测试也是验证运算符重载行为正确性的重要手段。



总结起来,运算符重载是一项强大的特性,可以提高代码的可读性和灵活性。但要注意遵循语义约定、类型匹配、返回类型、自我赋值检查等原则,以确保正确且一致的行为。


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
  X5zJxoD00Cah   2023年11月24日   33   0   0 SQL运算符
  zLxnEsMLk4BL   2023年11月19日   27   0   0 赋值运算符字符串
  zLxnEsMLk4BL   2023年11月19日   26   0   0 赋值字符串bc
zhRhucGD3dLm