【C++重载操作符与转换】赋值操作符

目录

一、赋值操作符重载基础

1.1 什么是赋值操作符重载

1.2 默认赋值操作符

1.3 浅拷贝的问题

1.4 赋值操作符的调用场景

1.5 赋值操作符的声明规范

二、重载赋值操作符

2.1 基本语法

2.2 深拷贝实现

2.3 自我赋值检查

三、赋值操作符重载的其他形式

3.1 不同类型之间的赋值 

3.2 复合赋值操作符重载

四、赋值操作符重载的注意事项

4.1 只能重载为成员函数

4.2 避免内存泄漏

4.3 异常安全性

五、“拷贝并交换” 技术

5.1 原理

5.2 代码示例

六、总结

七、相关代码总结

7.1 深拷贝赋值操作符重载代码 

7.2 不同类型赋值操作符重载代码 

7.3 复合赋值操作符重载代码 

 7.4 “拷贝并交换” 技术实现赋值操作符重载代码

八、附录:操作符重载对比表


在C++中,赋值操作符(operator=)是类设计中最为关键的重载操作符之一。它不仅影响对象的赋值行为,还与资源管理、深拷贝/浅拷贝、异常安全等核心特性紧密相关。

一、赋值操作符重载基础

1.1 什么是赋值操作符重载

在 C++ 里,赋值操作符 = 用于将一个对象的值赋给另一个对象。对于内置数据类型(如 intdouble 等),赋值操作是由编译器自动处理的。但对于自定义类,编译器会提供一个默认的赋值操作符,但这个默认的赋值操作符只是简单地进行浅拷贝,在某些情况下可能无法满足我们的需求,这时就需要我们自己重载赋值操作符。

1.2 默认赋值操作符

当我们定义一个类而没有显式重载赋值操作符时,编译器会为我们生成一个默认的赋值操作符。默认赋值操作符会逐个成员地进行赋值,也就是浅拷贝。下面是一个简单的示例:

#include <iostream>
class MyClass {
private:
    int data;
public:
    MyClass(int d = 0) : data(d) {}
    // 编译器会自动生成默认赋值操作符
    void display() const {
        std::cout << "Data: " << data << std::endl;
    }
};

int main() {
    MyClass obj1(10);
    MyClass obj2(20);
    obj2 = obj1;  // 使用默认赋值操作符
    obj2.display();
    return 0;
}

 

obj2 = obj1 调用了编译器生成的默认赋值操作符,将 obj1 的 data 成员的值赋给了 obj2 的 data 成员。

1.3 浅拷贝的问题

浅拷贝在处理包含指针成员的类时会出现问题。考虑下面的代码: 

#include <iostream>
class MyClass {
private:
    int* data;
public:
    MyClass(int d = 0) {
        data = new int(d);
    }
    ~MyClass() {
        delete data;
    }
    void display() const {
        std::cout << "Data: " << *data << std::endl;
    }
};

int main() {
    MyClass obj1(10);
    MyClass obj2(20);
    obj2 = obj1;  // 默认赋值操作符(浅拷贝)
    obj1.display();
    obj2.display();
    return 0;
}

 

默认赋值操作符只是将 obj1 的 data 指针的值赋给了 obj2 的 data 指针,意味着两个对象的 data 指针指向了同一块内存。当其中一个对象被销毁时,会释放这块内存,而另一个对象的指针就会变成悬空指针,再次访问会导致未定义行为。

1.4 赋值操作符的调用场景

  • 显式赋值a = b;
  • 链式赋值a = b = c;(需返回*this的引用)
  • 隐式转换后赋值a = someOtherType;(需配合类型转换操作符)

1.5 赋值操作符的声明规范

class MyClass {
public:
    // 赋值操作符声明
    MyClass& operator=(const MyClass& rhs);  // 返回引用,参数为const引用
};

关键约束

  • 不能通过const修饰(会阻止成员修改)。
  • 不能重载为全局函数(必须为成员函数)。
  • 不能通过delete直接禁用(但可通过私有化实现)。

二、重载赋值操作符

2.1 基本语法

重载赋值操作符的基本语法如下:

class ClassName {
public:
    ClassName& operator=(const ClassName& other) {
        // 赋值操作的实现
        return *this;
    }
};
  • ClassName& 表示返回值类型是当前类的引用,这样可以支持链式赋值,如 obj1 = obj2 = obj3;
  • operator= 是赋值操作符重载的函数名。
  • const ClassName& other 是参数,通常使用 const 引用,以避免修改传入的对象。

2.2 深拷贝实现

为了解决浅拷贝的问题,可以重载赋值操作符,实现深拷贝。下面是一个改进后的代码:

#include <iostream>
class MyClass {
private:
    int* data;
public:
    MyClass(int d = 0) {
        data = new int(d);
    }
    ~MyClass() {
        delete data;
    }
    MyClass& operator=(const MyClass& other) {
        if (this != &other) {  // 避免自我赋值
            delete data;  // 释放当前对象的内存
            data = new int(*other.data);  // 分配新内存并复制值
        }
        return *this;
    }
    void display() const {
        std::cout << "Data: " << *data << std::endl;
    }
};

int main() {
    MyClass obj1(10);
    MyClass obj2(20);
    obj2 = obj1;  // 调用重载的赋值操作符
    obj1.display();
    obj2.display();
    return 0;
}

 

重载的赋值操作符首先检查是否是自我赋值(this != &other),如果不是,则释放当前对象的内存,然后分配新的内存并复制传入对象的值,实现了深拷贝。

2.3 自我赋值检查

自我赋值检查是重载赋值操作符时的一个重要步骤。如果不进行自我赋值检查,当执行 obj = obj; 时,会先释放当前对象的内存,然后再尝试复制已经被释放的内存,导致未定义行为。因此,在重载赋值操作符时,应该始终检查是否是自我赋值。

三、赋值操作符重载的其他形式

3.1 不同类型之间的赋值 

除了对象之间的赋值,还可以重载赋值操作符,实现不同类型之间的赋值。例如,将一个 int 类型的值赋给自定义类的对象: 

#include <iostream>
class MyClass {
private:
    int data;
public:
    MyClass(int d = 0) : data(d) {}
    MyClass& operator=(int value) {
        data = value;
        return *this;
    }
    void display() const {
        std::cout << "Data: " << data << std::endl;
    }
};

int main() {
    MyClass obj(10);
    obj = 20;  // 调用重载的赋值操作符
    obj.display();
    return 0;
}

 

重载了赋值操作符,使得可以将一个 int 类型的值赋给 MyClass 类型的对象。

3.2 复合赋值操作符重载

复合赋值操作符(如 +=-=*= 等)也可以被重载。下面是一个重载 += 操作符的示例: 

#include <iostream>
class MyClass {
private:
    int data;
public:
    MyClass(int d = 0) : data(d) {}
    MyClass& operator+=(const MyClass& other) {
        data += other.data;
        return *this;
    }
    void display() const {
        std::cout << "Data: " << data << std::endl;
    }
};

int main() {
    MyClass obj1(10);
    MyClass obj2(20);
    obj1 += obj2;  // 调用重载的 += 操作符
    obj1.display();
    return 0;
}

 

重载的 += 操作符将两个对象的 data 成员相加,并将结果存储在当前对象中。

四、赋值操作符重载的注意事项

4.1 只能重载为成员函数

赋值操作符 = 只能重载为类的成员函数,不能重载为非成员函数。这是因为赋值操作符的第一个操作数必须是当前对象(*this),如果重载为非成员函数,就无法满足这个要求。

4.2 避免内存泄漏

在重载赋值操作符时,要特别注意内存管理。如果类中包含指针成员,应该在赋值操作前释放当前对象的内存,避免内存泄漏。

4.3 异常安全性

在重载赋值操作符时,要考虑异常安全性。如果在分配新内存时抛出异常,应该保证对象的状态不变。可以使用 “拷贝并交换” 技术来实现异常安全的赋值操作符。

五、“拷贝并交换” 技术

5.1 原理

“拷贝并交换” 技术是一种实现异常安全的赋值操作符的常用方法。其基本原理是:先创建一个临时对象,将传入对象的值复制到临时对象中,然后交换当前对象和临时对象的资源。如果在复制过程中抛出异常,当前对象的状态不会改变。

5.2 代码示例

#include <iostream>
#include <algorithm>  // 包含 std::swap

class MyClass {
private:
    int* data;
    int size;
public:
    MyClass(int s = 0) : size(s) {
        data = new int[size];
    }
    ~MyClass() {
        delete[] data;
    }
    MyClass(const MyClass& other) : size(other.size) {
        data = new int[size];
        std::copy(other.data, other.data + size, data);
    }
    void swap(MyClass& other) {
        std::swap(data, other.data);
        std::swap(size, other.size);
    }
    MyClass& operator=(MyClass other) {  // 传值调用,会调用拷贝构造函数
        this->swap(other);
        return *this;
    }
    void display() const {
        std::cout << "Size: " << size << std::endl;
    }
};

int main() {
    MyClass obj1(10);
    MyClass obj2(20);
    obj2 = obj1;  // 调用重载的赋值操作符
    obj2.display();
    return 0;
}

 

赋值操作符的参数采用传值调用,会调用拷贝构造函数创建一个临时对象。然后通过 swap 函数交换当前对象和临时对象的资源,最后临时对象在函数结束时自动销毁,释放其占用的内存。

六、总结

赋值操作符重载是 C++ 中一个重要的特性,它允许我们为自定义类重新定义赋值操作的行为。在重载赋值操作符时,要注意避免浅拷贝带来的问题,进行自我赋值检查,考虑不同类型之间的赋值和复合赋值操作符的重载。同时,要遵循只能重载为成员函数的规则,注意内存管理和异常安全性。“拷贝并交换” 技术是一种实现异常安全的赋值操作符的有效方法。通过掌握赋值操作符重载的相关知识,可以编写出更加健壮和灵活的 C++ 代码。

希望本文能帮助你更好地理解和掌握 C++ 中赋值操作符重载的相关内容。如果你有任何疑问或建议,欢迎在评论区留言。

七、相关代码总结

7.1 深拷贝赋值操作符重载代码 

#include <iostream>
class MyClass {
private:
    int* data;
public:
    MyClass(int d = 0) {
        data = new int(d);
    }
    ~MyClass() {
        delete data;
    }
    MyClass& operator=(const MyClass& other) {
        if (this != &other) {  // 避免自我赋值
            delete data;  // 释放当前对象的内存
            data = new int(*other.data);  // 分配新内存并复制值
        }
        return *this;
    }
    void display() const {
        std::cout << "Data: " << *data << std::endl;
    }
};

int main() {
    MyClass obj1(10);
    MyClass obj2(20);
    obj2 = obj1;  // 调用重载的赋值操作符
    obj1.display();
    obj2.display();
    return 0;
}

7.2 不同类型赋值操作符重载代码 

#include <iostream>
class MyClass {
private:
    int data;
public:
    MyClass(int d = 0) : data(d) {}
    MyClass& operator=(int value) {
        data = value;
        return *this;
    }
    void display() const {
        std::cout << "Data: " << data << std::endl;
    }
};

int main() {
    MyClass obj(10);
    obj = 20;  // 调用重载的赋值操作符
    obj.display();
    return 0;
}

 

7.3 复合赋值操作符重载代码 

#include <iostream>
class MyClass {
private:
    int data;
public:
    MyClass(int d = 0) : data(d) {}
    MyClass& operator+=(const MyClass& other) {
        data += other.data;
        return *this;
    }
    void display() const {
        std::cout << "Data: " << data << std::endl;
    }
};

int main() {
    MyClass obj1(10);
    MyClass obj2(20);
    obj1 += obj2;  // 调用重载的 += 操作符
    obj1.display();
    return 0;
}

 

 7.4 “拷贝并交换” 技术实现赋值操作符重载代码

#include <iostream>
#include <algorithm>  // 包含 std::swap

class MyClass {
private:
    int* data;
    int size;
public:
    MyClass(int s = 0) : size(s) {
        data = new int[size];
    }
    ~MyClass() {
        delete[] data;
    }
    MyClass(const MyClass& other) : size(other.size) {
        data = new int[size];
        std::copy(other.data, other.data + size, data);
    }
    void swap(MyClass& other) {
        std::swap(data, other.data);
        std::swap(size, other.size);
    }
    MyClass& operator=(MyClass other) {  // 传值调用,会调用拷贝构造函数
        this->swap(other);
        return *this;
    }
    void display() const {
        std::cout << "Size: " << size << std::endl;
    }
};

int main() {
    MyClass obj1(10);
    MyClass obj2(20);
    obj2 = obj1;  // 调用重载的赋值操作符
    obj2.display();
    return 0;
}

 

八、附录:操作符重载对比表

操作符典型场景返回值类型参数类型是否必须为成员函数
operator=对象赋值T&const T&必须
operator=(T&&)移动赋值(C++11)T&T&&必须
operator+算术加法T(非引用)const T&const T&可选(通常非成员)
operator==相等比较boolconst T&const T&可选(通常非成员)
operator<排序比较boolconst T&const T&可选(通常非成员)

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

byte轻骑兵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值