【C++】C++ 重载深度解析:赋值运算与取地址运算符,你真的会了吗?
| 🔭 个人主页:散峰而望 |
|---|
《C语言:从基础到进阶》《编程工具的下载和使用》《C语言刷题》
《C++》《算法竞赛从入门到获奖》《人工智能》《AI Agent》
愿为出海月,不做归山云
🎬博主简介

【C++】C++ 重载深度解析:赋值运算与取地址运算符,你真的会了吗?
前言
在C++中,运算符重载是一项强大的特性,允许开发者自定义运算符的行为,使其适用于用户定义的类型。赋值运算符(operator=)和取地址运算符(operator&)是其中两个关键的重载目标,但它们的实现细节和潜在陷阱往往容易被忽视。
赋值运算符的重载不仅涉及深拷贝与浅拷贝的问题,还需处理自赋值安全性及资源管理的正确性。而取地址运算符的重载则较少被讨论,但其在智能指针、代理类等高级场景中扮演重要角色,尤其是结合const成员函数的特性时,能够提供更灵活的语义控制。
本文将从基础语法到实际应用,深入解析这两种运算符的重载机制,帮助开发者规避常见错误并掌握其高效用法。
1. 赋值运算符重载
1.1 运算符重载
- 当运算符被用于类类型的对象时,C++ 语言允许我们通过运算符重载的形式指定新的含义。C++ 规定类类型对象使用运算符时,必须转换成调用对应运算符重载,若没有对应的运算符重载,则会编译报错。
- 运算符重载是具有特殊名字的函数,其的名字是由 operator 和后面要定义的运算符共同构成。和其他函数一样,它也具有其返回类型和参数列表以及函数体。
- 重载运算符函数的参数个数和该运算符作用的运算对象数量一样多。一元运算符有一个参数,二元运算符有两个参数,二元运算符的左侧运算对象传给第一个参数,右侧运算对象传给第二个参数。
- 如果一个重载运算符函数是成员函数,则它的第一个运算对象默认传给隐式的 this 指针,因此运算符重载作为成员函数时,参数比运算对象少一个。
- 运算符重载以后,其优先级和结合性与对应的内置类型运算符保持一致。
- 不能通过连接语法中没有的符号来创建新的操作符:比如
operator@。 .*::sizeof?:.注意以上 5 个运算符不能重载。
#include<iostream>usingnamespace std;// 编译报错:“operator +”必须至少有一个类类型的形参 //int operator+(int x, int y)//{// return x + y;//}classA{public:voidfunc(){ cout <<"A::func()"<< endl;}};typedefvoid(A::* PF)();//成员函数指针类型 intmain(){// C++规定成员函数要加&才能取到函数指针 PF pf =&A::func; A obj;//定义ob类对象temp // 对象调用成员函数指针时,使用.*运算符 (obj.*pf)();return0;}- 重载操作符至少有一个类类型参数,不能通过运算符重载改变内置类型对象的含义,如:
int operator+(int x, int y)。
#include<iostream>usingnamespace std;classDate{public:Date(int year =1,int month =1,int day =1){ _year = year; _month = month; _day = day;}voidPrint(){ cout << _year <<"-"<< _month <<"-"<< _day << endl;}//private:int _year;int _month;int _day;};// 重载为全局的面临对象访问私有成员变量的问题 // 有几种方法可以解决: // 1、成员放公有 // 2、Date提供getxxx函数 // 3、友元函数 // 4、重载为成员函数 booloperator==(const Date& d1,const Date& d2)//参数要有类类型{return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day;}intmain(){ Date d1(2026,3,10); Date d2(2026,3,11);// 运算符重载函数可以显示调用 operator==(d1, d2);// 编译器会转换成 operator==(d1, d2); d1 == d2;return0;}- 一个类需要重载哪些运算符,是看哪些运算符重载后有意义,比如 Date 类重载 operator- 就有意义,但是重载 operator+ 就没有意义。
- 重载 ++ 运算符时,有前置 ++ 和后置 ++ ,运算符重载函数名都是operator++ ,无法很好的区分。C++ 规定,后置 ++ 重载时,增加一个 int 形参,跟前置 ++ 构成函数重载,方便区分。
#include<iostream>usingnamespace std;classDate{public:Date(int year =1,int month =1,int day =1){ _year = year; _month = month; _day = day;}voidPrint(){ cout << _year <<"-"<< _month <<"-"<< _day << endl;}booloperator==(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;};intmain(){ Date d1(2026,3,10); Date d2(2026,3,11);// 运算符重载函数可以显示调用 d1.operator==(d2);// 编译器会转换成 d1.operator==(d2); d1 == d2;// 编译器会转换成 d1.operator++(); ++d1;// 编译器会转换成 d1.operator++(0); d1++;return0;}- 重载 << 和 >> 时,需要重载为全局函数,因为重载为成员函数,this 指针默认抢占了第一个形参位置,第一个形参位置是左侧运算对象,调用时就变成了
对象 << cout,不符合使用习惯和可读性。重载为全局函数把ostream/istream放到第一个形参位置就可以了,第二个形参位置当类类型对象。
classDate{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;}intmain(){ d1 << cout; d1.operator<<(cout);return0;}
因为存在隐藏的 this 指针,即第一个参数(this指针)是左操作数,函数的显示参数是右操作数,将 operator<< 错误地实现为 Date 类的成员函数,因此需要改成全局函数。
voidoperator<<(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 当前类类型引用,否则会传值传参会有拷贝;
//d1 = d2//d是d2的别名,d1用this指针voidoperator=(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>usingnamespace std;classDate{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;}voidoperator=(const Date& d){ _year = d._year; _month = d._month; _day = d._day;}private:int _year;int _month;int _day;};intmain(){ Date d1(2026,3,10); Date d2(d1); Date d3(2026,3,11); Date d4 = d3; d1 = d2 = d3;//报错:E0349 没有与这些操作数匹配的 "=" 运算符 return0;}- 没有显式实现时,编译器会自动生成一个默认赋值运算符重载,默认赋值运算符重载行为跟默认拷贝构造函数类似,对内置类型成员变量会完成值拷贝/浅拷贝(一个字节一个字节的拷贝),对自定义类型成员变量会调用他的赋值重载函数。
- 像 Date 这样的类成员变量全是内置类型且没有指向什么资源,编译器自动生成的赋值运算符重载就可以完成需要的拷贝,所以不需要我们显示实现赋值运算符重载。像 Stack 这样的类,虽然也都是内置类型,但是 _a 指向了资源,编译器自动生成的赋值运算符重载完成的值拷贝/浅拷贝不符合我们的需求,所以需要自己实现深拷贝(对指向的资源也进行拷贝)。像 MyQueue 这样的类型内部主要是自定义类型 Stack 成员,编译器自动生成的赋值运算符重载会调用 Stack 的赋值运算符重载,也不需要我们显示实现 MyQueue 的赋值运算符重载。这里还有一个小技巧,如果一个类显示实现了析构并释放资源,那么他就需要显示写赋值运算符重载,否则就不需要。
#include<iostream>usingnamespace std;classDate{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;}// 传引用返回减少拷⻉ // d1 = d2; Date&operator=(const Date& d){// 不要检查自己给自己赋值的情况 if(this!=&d){ _year = d._year; _month = d._month; _day = d._day;}// d1 = d2表达式的返回对象应该为d1,也就是*this return*this;}voidPrint(){ cout << _year <<"-"<< _month <<"-"<< _day << endl;}private:int _year;int _month;int _day;};intmain(){ Date d1(2026,3,10); Date d2(d1); Date d3(2026,3,11); d1 = d3;// 需要注意这里是拷贝构造,不是赋值重载 Date d4 = d1;return0;}2. 取地址运算符重载
2.1 const成员函数
将 const 修饰的成员函数称之为 const 成员函数,const 修饰成员函数放到成员函数参数列表的后面。
const 实际修饰该成员函数隐含的 this 指针,表明在该成员函数中不能对类的任何成员进行修改。
const 修饰 Date 类的 Print 成员函数,Print 隐含的 this 指针由 Date* const this 变为 const Date* const this
#include<iostream>usingnamespace std;classDate{public:Date(int year =1,int month =1,int day =1){ _year = year; _month = month; _day = day;}// void Print(const Date* const this) constvoidPrint()const{ cout << _year <<"-"<< _month <<"-"<< _day << endl;}private:int _year;int _month;int _day;};intmain(){// 这里非const对象也可以调用const成员函数是一种权限的缩小 Date d1(2024,7,5); d1.Print();const Date d2(2024,8,5); d2.Print();return0;}void Print(const Date* const this) const第一个 const(Date* 前) :修饰指针指向的对象,表示 this 指针指向的 Date 对象是常量。在函数内部,不能通过 this 指针修改该对象的成员变量;第二个 const(Date* 后) :修饰指针本身,表示 this 指针本身是常量指针,其指向不可修改,即 this 指针重新指向其他对象。(C++ 中 this 指针默认就是常量指针,这里显示强调其不可修改性);第三个 const(函数末尾) :修饰成员函数,表示这是一个常量成员函数。在该函数中,不能修改调用对象的任何成员变量,保证函数不会意外改变对象状态。2.2 取地址运算符重载
取地址运算符重载分为普通取地址运算符重载和 const 取地址运算符重载,一般这两个函数编译器自动生成的就可以够我们用了,不需要去显示实现。除非一些很特殊的场景,比如我们不想让别人取到当前类对象的地址,就可以自己实现一份,胡乱返回一个地址。
classDate{public: Date*operator&(){returnthis;}const Date*operator&()const{returnthis;}private:int _year ;// 年 int _month ;// 月 int _day ;// 日 };不想让别人取到当前类对象的地址时,可以返回空,也可以返回任意地址。
//返回空classDate{public: Date*operator&(){returnnullptr;}const Date*operator&()const{returnnullptr;}private:int _year ;// 年 int _month ;// 月 int _day ;// 日 };//返回任意地址classDate{public: Date*operator&(){return(Date*)0x2631FF10;}const Date*operator&()const{return(Date*)0x2631FF20;}private:int _year ;// 年 int _month ;// 月 int _day ;// 日 };结语
运算符重载是C++强大的特性之一,合理使用可以提升代码的可读性和灵活性。赋值运算符重载需特别注意资源管理和自赋值问题,而取地址运算符重载常用于特殊场景如代理模式或智能指针实现。理解其底层机制能帮助开发者编写更安全高效的代码。
愿诸君能一起共渡重重浪,终见缛彩遥分地,繁光远缀天。