跳到主要内容 C++ 运算符重载深度解析:赋值与取地址运算符 | 极客日志
C++ 算法
C++ 运算符重载深度解析:赋值与取地址运算符 深入解析 C++ 中的运算符重载技术,重点讲解赋值运算符与取地址运算符的实现细节。内容涵盖赋值运算符重载的规则、返回值选择、自赋值检查及深拷贝与浅拷贝的区别,同时探讨了取地址运算符重载的特殊应用场景及 const 成员函数的配合使用。旨在帮助开发者规避常见错误,掌握高效安全的重载用法。
雾岛听风 发布于 2026/3/28 更新于 2026/4/16 5 浏览前言
在 C++ 中,运算符重载是一项强大的特性,允许开发者自定义运算符的行为,使其适用于用户定义的类型。赋值运算符(operator=)和取地址运算符(operator&)是其中两个关键的重载目标,但它们的实现细节和潜在陷阱往往容易被忽视。
赋值运算符的重载不仅涉及深拷贝与浅拷贝的问题,还需处理自赋值安全性及资源管理的正确性。而取地址运算符的重载则较少被讨论,但其在智能指针、代理类等高级场景中扮演重要角色,尤其是结合 const 成员函数的特性时,能够提供更灵活的语义控制。
本文将从基础语法到实际应用,深入解析这两种运算符的重载机制,帮助开发者规避常见错误并掌握其高效用法。
1. 赋值运算符重载
1.1 运算符重载
当运算符被用于类类型的对象时,C++ 语言允许我们通过运算符重载的形式指定新的含义。C++ 规定类类型对象使用运算符 时,必须转换成调用对应运算符重载 ,若没有对应的运算符重载,则会编译报错。
运算符重载是具有特殊名字的函数,其的名字是由 operator 和后面要定义的运算符共同构成 。和其他函数一样,它也具有其返回类型和参数列表以及函数体。
重载运算符函数的参数个数和该运算符作用的运算对象数量一样多 。一元运算符有一个参数,二元运算符有两个参数,二元运算符的左侧运算对象传给第一个参数,右侧运算对象传给第二个参数。
如果一个重载运算符函数是成员函数 ,则它的第一个运算对象默认传给隐式的 this 指针 ,因此运算符重载作为成员函数时,参数比运算对象少一个 。
运算符重载以后,其优先级和结合性与对应的内置类型运算符保持一致 。
不能通过连接语法中没有的符号来创建新的操作符:比如 operator@。
.*::sizeof?:. 注意以上 5 个运算符不能重载 。
#include <iostream>
using namespace std;
class A {
public :
void func () { cout << "A::func()" << endl; }
};
typedef void (A::* PF) () ;
int main () {
PF pf = &A::func;
A obj;
(obj.*pf)();
;
}
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
return
0
重载操作符至少有一个类类型参数,不能通过运算符重载改变内置类型对象的含义,如:int operator+(int x, int y)。
#include <iostream>
using namespace std;
class Date {
public :
Date (int year = 1 , int month = 1 , int day = 1 ) {
_year = year;
_month = month;
_day = day;
}
void Print () {
cout << _year << "-" << _month << "-" << _day << endl;
}
private :
int _year;
int _month;
int _day;
};
bool operator ==(const Date& d1, const Date& d2)
{
return d1. _year == d2. _year && d1. _month == d2. _month && d1. _day == d2. _day;
}
int main () {
Date d1 (2026 , 3 , 10 ) ;
Date d2 (2026 , 3 , 11 ) ;
operator ==(d1, d2);
return 0 ;
}
一个类需要重载哪些运算符,是看哪些运算符重载后有意义 ,比如 Date 类重载 operator- 就有意义,但是重载 operator+ 就没有意义。
重载 ++ 运算符时,有前置 ++ 和后置 ++ ,运算符重载函数名都是 operator++ ,无法很好的区分。C++ 规定,后置 ++ 重载时,增加一个 int 形参 ,跟前置 ++ 构成函数重载,方便区分。
#include <iostream>
using namespace std;
class Date {
public :
Date (int year = 1 , int month = 1 , int day = 1 ) {
_year = year;
_month = month;
_day = day;
}
void Print () {
cout << _year << "-" << _month << "-" << _day << endl;
}
bool operator ==(const Date& d) {
return _year == d._year && _month == d._month && _day == d._day;
}
Date& operator ++() {
cout << "前置++" << endl;
return *this ;
}
Date operator ++(int ) {
Date tmp;
cout << "后置++" << endl;
return tmp;
}
private :
int _year;
int _month;
int _day;
};
int main () {
Date d1 (2026 , 3 , 10 ) ;
Date d2 (2026 , 3 , 11 ) ;
d1. operator ==(d2);
return 0 ;
}
重载 << 和 >> 时,需要重载为全局函数 ,因为重载为成员函数,this 指针默认抢占了第一个形参位置,第一个形参位置是左侧运算对象,调用时就变成了 **对象 << cout** ,不符合使用习惯和可读性。重载为全局函数把 ostream/istream 放到第一个形参位置就可以了,第二个形参位置当类类型对象。
class Date {
public :
Date (int year = 1 , int month = 1 , int day = 1 ) {
_year = year;
_month = month;
_day = day;
}
private :
int _year;
int _month;
int _day;
};
void Date::operator <<(ostream& out) {
out << _year << _month << _day << endl;
}
int main () {
d1 << cout;
d1. operator <<(cout);
return 0 ;
}
因为存在隐藏的 this 指针,即第一个参数 (this 指针) 是左操作数 ,函数的显示参数是右操作数 ,将 operator<< 错误地实现为 Date 类的成员函数,因此需要改成全局函数。
void operator <<(ostream& out, const Date& d) {
out << d._year << d._month << d._day << endl;
}
1.2 赋值运算符重载 赋值运算符重载 是一个默认成员函数,用于完成两个已经存在的对象直接的拷贝赋值 ,这里要注意跟拷贝构造区分,拷贝构造用于一个对象拷贝初始化给另一个要创建的对象 。
Date d1 (2026 , 3 , 10 ) ;
Date d2 (2026 , 3 , 11 ) ;
d1 = d2;
Date d3 (d2) ;
Date d4 = d2;
赋值运算符重载是一个运算符重载,规定必须重载为成员函数。赋值运算重载的参数建议写成 const 当前类类型引用 ,否则会传值传参会有拷贝;
void operator =(const Date& d) {
_year = d._year;
_month = d._month;
_day = d._day;
}
有返回值,且建议写成当前类类型引用 ,引用返回可以提高效率,有返回值目的是为了支持连续赋值场景。有返回值主要是为了实现连续赋值。
Date& operator =(const Date& d) {
_year = d._year;
_month = d._month;
_day = d._day;
return *this ;
}
#include <iostream>
using namespace std;
class Date {
public :
Date (int year = 1 , int month = 1 , int day = 1 ) {
_year = year;
_month = month;
_day = day;
}
Date (const Date& d) {
cout << " Date(const Date& d)" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
void operator =(const Date& d) {
_year = d._year;
_month = d._month;
_day = d._day;
}
private :
int _year;
int _month;
int _day;
};
int main () {
Date d1 (2026 , 3 , 10 ) ;
Date d2 (d1) ;
Date d3 (2026 , 3 , 11 ) ;
Date d4 = d3;
d1 = d2 = d3;
return 0 ;
}
没有显式实现 时,编译器会自动生成一个默认赋值运算符重载,默认赋值运算符重载行为跟默认拷贝构造函数类似 ,对内置类型成员变量会完成值拷贝/浅拷贝 (一个字节一个字节的拷贝),对自定义类型成员变量会调用他的赋值重载函数。
像 Date 这样的类成员变量全是内置类型且没有指向什么资源 ,编译器自动生成的赋值运算符重载就可以完成需要的拷贝 ,所以不需要我们显示实现赋值运算符重载。像 Stack 这样的类,虽然也都是内置类型,但是 _a 指向了资源 ,编译器自动生成的赋值运算符重载完成的值拷贝/浅拷贝不符合我们的需求,所以需要自己实现深拷贝 (对指向的资源也进行拷贝)。像 MyQueue 这样的类型内部主要是自定义类型 Stack 成员,编译器自动生成的赋值运算符重载会调用 Stack 的赋值运算符重载,也不需要我们显示实现 MyQueue 的赋值运算符重载。这里还有一个小技巧,如果一个类显示实现了析构并释放资源,那么他就需要显示写赋值运算符重载,否则就不需要 。
#include <iostream>
using namespace std;
class Date {
public :
Date (int year = 1 , int month = 1 , int day = 1 ) {
_year = year;
_month = month;
_day = day;
}
Date (const Date& d) {
cout << " Date(const Date& d)" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
Date& operator =(const Date& d) {
if (this != &d) {
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this ;
}
void Print () {
cout << _year << "-" << _month << "-" << _day << endl;
}
private :
int _year;
int _month;
int _day;
};
int main () {
Date d1 (2026 , 3 , 10 ) ;
Date d2 (d1) ;
Date d3 (2026 , 3 , 11 ) ;
d1 = d3;
Date d4 = d1;
return 0 ;
}
2. 取地址运算符重载
2.1 const 成员函数 将 const 修饰的成员函数称之为 const 成员函数,const 修饰成员函数 放到成员函数参数列表的后面 。
const 实际修饰该成员函数隐含的 this 指针,表明在该成员函数中不能对类的任何成员进行修改 。
const 修饰 Date 类的 Print 成员函数,Print 隐含的 this 指针由 Date* const this 变为 const Date* const this
#include <iostream>
using namespace std;
class Date {
public :
Date (int year = 1 , int month = 1 , int day = 1 ) {
_year = year;
_month = month;
_day = day;
}
void Print () const {
cout << _year << "-" << _month << "-" << _day << endl;
}
private :
int _year;
int _month;
int _day;
};
int main () {
Date d1 (2024 , 7 , 5 ) ;
d1. Print ();
const Date d2 (2024 , 8 , 5 ) ;
d2. Print ();
return 0 ;
}
void Print(const Date* const this) const第一个 const(Date* 前) :修饰指针指向的对象 ,表示 this 指针指向的 Date 对象是常量 。在函数内部,不能通过 this 指针修改该对象的成员变量;第二个 const(Date* 后) :修饰指针本身 ,表示 this 指针本身是常量指针,其指向不可修改,即 this 指针重新指向其他对象。(C++ 中 this 指针默认就是常量指针,这里显示强调其不可修改性);第三个 const(函数末尾) :修饰成员函数 ,表示这是一个常量成员函数。在该函数中,不能修改调用对象的任何成员变量,保证函数不会意外改变对象状态。
2.2 取地址运算符重载 取地址运算符重载分为普通取地址运算符重载和 const 取地址运算符重载,一般这两个函数编译器自动生成的就可以够我们用了,不需要去显示实现。除非一些很特殊的场景,比如我们不想让别人取到当前类对象的地址,就可以自己实现一份,胡乱返回一个地址。
class Date {
public :
Date* operator &() {
return this ;
}
const Date* operator &() const {
return this ;
}
private :
int _year;
int _month;
int _day;
};
不想让别人取到当前类对象的地址时,可以返回空,也可以返回任意地址。
class Date {
public :
Date* operator &() {
return nullptr ;
}
const Date* operator &() const {
return nullptr ;
}
private :
int _year;
int _month;
int _day;
};
class Date {
public :
Date* operator &() {
return (Date*)0x2631FF10 ;
}
const Date* operator &() const {
return (Date*)0x2631FF20 ;
}
private :
int _year;
int _month;
int _day;
};
结语 运算符重载是 C++ 强大的特性之一,合理使用可以提升代码的可读性和灵活性。赋值运算符重载需特别注意资源管理和自赋值问题,而取地址运算符重载常用于特殊场景如代理模式或智能指针实现。理解其底层机制能帮助开发者编写更安全高效的代码。