【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

Deepseek:前端程序员如何转行网络安全?

Deepseek:前端程序员如何转行网络安全?

前端程序员转行网络安全是可行的,因为前端开发中的许多技能(如JavaScript、HTTP协议、浏览器安全等)在网络安全领域也非常有用。以下是一些具体的步骤和建议,帮助你顺利过渡: 1. 利用现有技能 JavaScript:前端程序员精通JavaScript,可以用于编写安全工具、分析前端漏洞(如XSS)。 HTTP/HTTPS协议:前端开发中对HTTP请求、响应、状态码的理解,有助于分析Web应用的安全问题。 浏览器安全:熟悉浏览器的同源策略、CORS、Cookie安全等,这些是Web安全的基础。 2. 学习网络安全核心知识 Web漏洞:重点学习前端相关的漏洞,如: XSS(跨站脚本攻击):利用JavaScript注入恶意代码。 CSRF(跨站请求伪造):利用用户身份执行未经授权的操作。 点击劫持(Clickjacking):诱骗用户点击隐藏的恶意内容。 其他常见漏洞:如SQL注入、文件上传漏洞、SSRF(服务器端请求伪造)等。 安全头与策略:学习如何通过HTTP头(如CSP、HSTS、

By Ne0inhk

最新版 springdoc-openapi-starter-webmvc-ui 常用注解详解 + 实战示例

当然可以!在 Spring Boot 3 + SpringDoc OpenAPI(Swagger 3 替代方案)生态中,springdoc-openapi-starter-webmvc-ui 是目前官方推荐的集成方式。它提供了一套丰富的注解,用于精细化控制 API 文档的生成,提升前端、测试、产品等协作方的体验。 ✅ 最新版 springdoc-openapi-starter-webmvc-ui 常用注解详解 + 实战示例 📌 当前最新稳定版本:springdoc-openapi 2.5+(2025年仍适用) 📌 所有注解位于包:io.swagger.v3.oas.annotations.* 🧩 一、核心注解概览 注解作用适用位置@OpenAPIDefinition全局 API 信息配置(标题、版本、联系人等)@Configuration 类@Tag标记 Controller 或方法所属的“标签/

By Ne0inhk
web网络安全-每日一练-Training-WWW-Robots

web网络安全-每日一练-Training-WWW-Robots

练习题目:Training-WWW-Robots 练习网站(攻防世界):https://adworld.xctf.org.cn/ 解题步骤 1、打开题目场景 在这个小小的训练挑战中,你将了解Repbots_exclusion_standard。 robots.txt文件被网络爬虫用于检查它们是否被允许爬取和索引你的网站,或者只是网站的部分内容。 有时这些文件会暴露目录结构,而不是保护内容不被爬取。 祝你玩得开心! 2、利用Robots协议 发现根目录下有一个 f10g.php 的文件,访问这个文件内容 3、访问网站根目录的 f10g.php 文件 得到正确答案 知识点讲解:Web 安全信息收集:robots.txt 的原理、利用与防御实战 ⚠️ 警告: 本文仅用于授权测试和安全学习,未经授权扫描目标属于违法行为。 一、写在前面:关于「Repbots」的纠正

By Ne0inhk

WebVOWL 本体可视化工具完整部署手册

WebVOWL 本体可视化工具完整部署手册 【免费下载链接】WebVOWLVisualizing ontologies on the Web 项目地址: https://gitcode.com/gh_mirrors/we/WebVOWL 概述简介 WebVOWL 是一款专业的网络本体可视化工具,能够将复杂的 RDF 和 OWL 数据转换为直观的图形化展示。该工具采用现代化的 Web 技术栈,为语义网研究和本体工程提供了强大的可视化支持。 环境要求与前置准备 在开始部署之前,请确保您的系统满足以下基本要求: 系统环境要求: * Node.js 运行环境(推荐最新稳定版本) * 基本的命令行操作知识 * 现代浏览器支持(Chrome、Firefox、Safari、Edge) 软件版本确认: 通过命令行输入以下命令检查当前环境: node --version npm --version 完整部署流程 第一步:

By Ne0inhk