【C++】C++ 重载深度解析:赋值运算与取地址运算符,你真的会了吗?

【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;

赋值运算符重载的特点:

  1. 赋值运算符重载是一个运算符重载,规定必须重载为成员函数。赋值运算重载的参数建议写成 const 当前类类型引用,否则会传值传参会有拷贝;
//d1 = d2//d是d2的别名,d1用this指针voidoperator=(const Date& d){ _year = d._year; _month = d._month; _day = d._day;}
  1. 有返回值,且建议写成当前类类型引用,引用返回可以提高效率,有返回值目的是为了支持连续赋值场景。有返回值主要是为了实现连续赋值。
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;}
  1. 没有显式实现时,编译器会自动生成一个默认赋值运算符重载,默认赋值运算符重载行为跟默认拷贝构造函数类似,对内置类型成员变量会完成值拷贝/浅拷贝(一个字节一个字节的拷贝),对自定义类型成员变量会调用他的赋值重载函数。
  2. 像 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++强大的特性之一,合理使用可以提升代码的可读性和灵活性。赋值运算符重载需特别注意资源管理和自赋值问题,而取地址运算符重载常用于特殊场景如代理模式或智能指针实现。理解其底层机制能帮助开发者编写更安全高效的代码。

愿诸君能一起共渡重重浪,终见缛彩遥分地,繁光远缀天



Read more

GitHub 寻宝指南:四种高效发现优质开源项目的方法

GitHub 寻宝指南:四种高效发现优质开源项目的方法

文章目录 * 引言:从“收藏家”到“寻宝猎人”,升级你的 GitHub 发现技能 * 方法一:利用 GitHub 自身的功能(基础) * 1. GitHub Explore (探索) * 2. GitHub 高级搜索 * 方法二:借助社区整理的精选列表(高效) * 1. Awesome Lists * 2. 关注领域专家 * 方法三:善用第三方辅助网站(多维) * 1. Star History * 2. LibHunt * 方法四:拥抱 AI 进行智能搜索(前沿) * GitHub 的 AI 搜索 (Ask Copilot) * 实战演示: * 结语:

By Ne0inhk
IDEA和GIT实现cherry pick拣选部分变更到新分支

IDEA和GIT实现cherry pick拣选部分变更到新分支

前言 在工作中,当你出现一些情况,需要将一个分支的部分变动提取出来,只需要更新提取出来的情况就需要用到当前文章提到的git的功能 并且正常情况下,你工作是没有权限直接合并到生产分支,并且前一个需求还没合并到生产分支,如果你想要复用这部分的改动逻辑,那么就需要用到这个操作,也叫cherry-pick拣选 核心作用 核心作用是将一个或多个已有的提交(commit)复制到当前所在的分支上。 你可以把它想象成在一棵果树上,只挑选(pick)几颗你想要的,而不是把整根树枝都搬过来。 为什么需要它? 主要用于那些不需要合并整个分支,而只需要其中几个特定提交的情况。 将修复补丁应用到多个分支 这是最常见、最经典的场景。假设你有一个bugfix分支上修复了一个关键 bug,这个提交的 hash 是 a1b2c3d。现在你需要将这个修复同时应用到: main 分支(生产环境) develop 分支(开发环境) 可能还有旧的维护分支 v1.x 你不需要将整个 bugfix 分支合并到这些分支上,只需要在每个目标分支上执行: git cherry-pick a1b2c3d 意外在错

By Ne0inhk
夜莺-Nightingale-开源云原生监控分析系统部署 Prometheus 作为时序库使用(配置多数据源)

夜莺-Nightingale-开源云原生监控分析系统部署 Prometheus 作为时序库使用(配置多数据源)

夜莺-Nightingale-开源云原生监控分析系统部署 Prometheus 作为时序库使用(配置多数据源) * 一、前言 * 二、Prometheus安装步骤 * 1. 下载并安装Prometheus * 2. 关键配置:启用Remote Write接收器 * 3. 创建Systemd服务 * 4. 启动并验证服务 * 三、验证Remote Write功能 * 四、修改夜莺配置文件对接时序库 * 1. 再增加一个Prometheus 时序库。 * 2. 重启夜莺监控(N9E)服务: * 3. 夜莺数据源管理新增数据源 * 五、常见问题解决 * 1. 夜莺转发数据时报404错误 * 2. 权限问题 * 3. 端口冲突 * 六、总结 * 参考链接 💐The Begin💐点点关注,收藏不迷路💐 一、前言 Prometheus是一款开源的监控系统和时序数据库,

By Ne0inhk
今日AI榜单速览(GitHub Trending AI Top3)

今日AI榜单速览(GitHub Trending AI Top3)

🔥 个人主页:杨利杰YJlio❄️ 个人专栏:《Sysinternals实战教程》《Windows PowerShell 实战》《WINDOWS教程》《IOS教程》《微信助手》《锤子助手》《Python》《Kali Linux》《那些年未解决的Windows疑难杂症》🌟 让复杂的事情更简单,让重复的工作自动化 今日AI热榜 * 1 1 今日榜单速览(GitHub Trending AI Top3) * 2 2 ruvnet / RuView:WiFi DensePose 的“无线透视”路线 * 2 我的一句话总结 * 2 为什么今天它能冲到第一? * 2 图:它的可视化界面长这样(很直观) * 2 我如何最快验证(不折腾工具链) * 3 3 K-Dense-AI / claude-scientific-skills:给

By Ne0inhk