当前位置: 首页 > news >正文

【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::swapclass 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::swapclass 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&可选(通常非成员)

相关文章:

  • 虚幻引擎入门笔记
  • 虚幻基础:角色朝向
  • 伊甸园之东: 农业革命与暴力的复杂性
  • 面试现场“震”情百态:HashMap扩容记
  • Java面试趣事:从死循环到分段锁
  • Vue 3 异步组件
  • n8n工作流自动化平台的实操:解决中文乱码
  • 【Elasticsearch】实现气象数据存储与查询系统
  • MySQL快速入门篇---数据库约束
  • list的两种设计
  • 为什么需要启动探针(StartupProb)?
  • 2845. 统计趣味子数组的数目
  • PMP-第六章 项目进度管理(二)
  • 2025年深圳杯数学建模(东三省)B题【颜色转换】原论文讲解
  • 给文件内容加行号
  • 十一岁少年叶珉雪用艺术点亮公益之路 个人原创公益演唱会传递大爱与担当
  • JVM 一文详解
  • 轻量级RTSP服务模块:跨平台低延迟嵌入即用的流媒体引擎
  • 自定义Dockerfile,发布springboot项目
  • STL之list容器
  • 五一小长假上海“人从众”,全要素旅游交易总额超200亿元
  • 中国驻美大使谢锋:经贸关系不是零和游戏,滥施关税损人害己
  • 竞彩湃|新科冠军利物浦留力?纽卡斯尔全力冲击欧冠
  • 巴菲特批评贸易保护主义:贸易不该被当成武器来使用
  • 印巴局势紧张或爆发军事冲突,印度空军能“一雪前耻”吗?
  • 美国第一季度经济环比萎缩0.3%,特朗普:怪拜登,与关税无关