类和对象:运算符重载
本篇文章来介绍一下C++中的运算符重载,以及与运算符重载有关的三个默认默认成员函数:赋值运算符重载,普通对象取地址与const对象取地址操作符重载,也就是下面图片中6个默认成员函数的后三个,前三个默认成员函数在之前文章中已经讲过类和对象:构造函数,析构函数与拷贝构造函数_一棵西兰花的博客-CSDN博客https://blog.csdn.net/qq_72916130/article/details/132789343?spm=1001.2014.3001.5501,大家不太清楚的可以去看一下。
本篇文章主要通过一个日期类来进行讲解。我会把完整的日期类放到后面。
class Date
{
public://...
private:int _year;int _month;int _day;
};
1.什么是运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
注意:
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this指针
- .* :: sizeof ? : . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
光看定义是不行的,我们实现以下 > 和 == 的重载
// >运算符重载
//因为>号有两个操作数,因为有一个是隐藏的this指针,所以这里只有一个参数
bool Date::operator>(const Date& d)
{if (_year < d._year){return false;}else if (_year == d._year && _month < d._month){return false;}else if (_year == d._year && _month == d._month && _day <= d._day){return false;}else{return true;}
}// ==运算符重载
bool Date::operator==(const Date& d)
{return _year == d._year && _month == d._month && _day == d._day;
}
有一个 > 和 == 或< 和 == ,其他比较运算符就可以有这两个推导出来,不用再一个一个写。
2.赋值运算符重载
1.赋值运算符重载格式
- 参数类型:const T&,传递引用可以提高传参效率,加const是为了防止参数被修改
- 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
- 检测是否自己给自己赋值
- 返回*this :要符合连续赋值的含义
代码:
Date& Date::operator=(const Date& d)//可以不用引用,但没有必要
{if (this != &d)//自己给自己赋值{_year = d._year;_month = d._month;_day = d._day;}return *this;
}
对于返回*this,因为我们平时的赋值运算符可以实现 类似与 a = b = c = d 的方式,显示调用就是a.operator(b.operator(c.operator(d))),如果operator=函数没有返回值,就会出现错误。
2.赋值运算符只能重载成类的成员函数不能重载成全局函数
// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
Date& operator=(Date& left, const Date& right)
{if (&left != &right){left._year = right._year;left._month = right._month;left._day = right._day;}return left;
}// 编译失败:
// error C2801: “operator =”必须是非静态成员
原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
3.用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实
现吗?当然像日期类这样的类是没必要的。
注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。因为他会将地址也进行简单的值拷贝(浅拷贝),在进行析构时回释放两次,会导致程序崩溃。
3.前置++与后置++重载
前置++与后置++的操作数只有一个,而且只有返回值不同,一个前置++返回自己加一,后置++需要在构造一个Date 来存放没有加一时的结果进行返回。只有返回值不同,没有办法区分两个函数,只好在后置++加入一个占位参数,没有什么用途,只是为了区分前置与后置,编译器也是通过这个方式来识别。
C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递
代码:
// 前置++
Date& Date::operator++()
{*this += 1;return *this;
}// 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,
//故需在实现时需要先将this保存一份,然后给this+1
// 而temp是临时对象,因此只能以值的方式返回,不能返回引用
// 后置++ 传入的值不用接收
Date Date::operator++(int)
{Date tmp(*this);*this += 1;return tmp;
}
-- 也一样:
// 后置--
Date Date::operator--(int)
{Date tmp(*this);*this -= 1;return tmp;
}
// 前置--
Date& Date::operator--()
{*this -= 1;return *this;
}
4.const成员
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,这里的const需要加在形参列表的后面,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
我们来看看下面的代码:
这里类成员函数Print() 没有用const修饰。
const Date d1(2023, 9, 23);//const修饰的对象调用不了成员函数,因为权限放大d1.Print();//d1.print(&d1) 隐藏的传入this的指针是const Data* 类型
Print() 没有被const 修饰,函数形参this指针是 普通的 Date* 类型,而被const修饰的对象调用Print()函数,传入的this指针是const Data* 类型 ,会发生权限的放大,权限可以缩小,平移,但是不能放大,会报错。
请思考下面的几个问题:
- const对象可以调用非const成员函数吗?
- 非const对象可以调用const成员函数吗?
- const成员函数内可以调用其它的非const成员函数吗?
- 非const成员函数内可以调用其它的const成员函数吗?
答案:1,3不可以,权限放大。2,4可以,权限缩小。
我们可以通过函数重载使不同权限的对象调用不同的函数。如下面代码的Print:
class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << "Print()" << endl;cout << "year:" << _year << endl;cout << "month:" << _month << endl;cout << "day:" << _day << endl << endl;}void Print() const{cout << "Print()const" << endl;cout << "year:" << _year << endl;cout << "month:" << _month << endl;cout << "day:" << _day << endl << endl;}
private:int _year; // 年int _month; // 月int _day; // 日
};
void Test()
{Date d1(2022,1,13);d1.Print();const Date d2(2022,1,13);d2.Print();
}
当然Print函数用const 修饰是没有什么意义的。我们看下面的取地址及const取地址操作符重载。
5.取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义 ,编译器默认会生成
class Date
{
public ://这种方式返回指针的内容可以修改,可读可写Date* operator&(){return this ;}//这种方式返回指针的内容也不能修改,只读const Date* operator&()const{return this ;}
private :int _year ; // 年int _month ; // 月int _day ; // 日
};
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容,不返回this,返回nullptr等等。
6. Date 类的实现
Date.h
class Date
{public:// 获取某年某月的天数int GetMonthDay(int year, int month);//全缺省的构造函数Date(int year = 1900, int month = 1, int day = 1);// 赋值运算符重载// d2 = d3 -> d2.operator=(&d2, d3)Date& operator=(const Date& d);// 日期+=天数Date& operator+=(int day);// 日期+天数Date operator+(int day);// 日期-天数Date operator-(int day);// 日期-=天数Date& operator-=(int day);void Print();//下面两个是运算符重载,也构成函数重载// 前置++ //++d1 -> d1.operator++()Date& operator++();// 后置++//d1++ -> d1.operator++(0)//传一个整形即可,只是用来区分前置++//加了一个int参数,进行占位,跟前置++构成函数重载进行区分//本质后置++调用,编译器进行了特殊处理Date operator++(int);//传入的值不用接收// 后置--Date operator--(int);// 前置--Date& operator--();// >运算符重载bool operator>(const Date& d);// ==运算符重载bool operator==(const Date& d);// >=运算符重载bool operator >= (const Date& d);// <运算符重载bool operator < (const Date& d);// <=运算符重载bool operator <= (const Date& d);// !=运算符重载bool operator != (const Date& d);// 日期-日期 返回天数int operator-(const Date& d);const Date* operator&()const;Date* operator&();private:int _year;int _month;int _day;
};
Date.cpp
// 获取某年某月的天数
int Date::GetMonthDay(int year, int month)
{static int MonthDay[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };if (month == 2 && (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)){return 29;}return MonthDay[month];
}//全缺省的构造函数
//声明和定义缺省参数的值,不能都给,必须在声明中给,
//因为在编译时头文件中看到的是声明,而声明中没有提供缺省值,会报错。到不了后面的链接,通过函数名修饰规则来找到函数的定义
Date::Date(int year, int month, int day)
{_year = year;_month = month;_day = day;//检查日期日期是否合法if (month < 1 || month>12 || day < 1||_day > GetMonthDay(_year, _month)){cout << "非法日期\n" << endl;exit(-1);}
}// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& Date::operator=(const Date& d)//可以不用引用,但没有必要
{if (this != &d)//自己给自己赋值{_year = d._year;_month = d._month;_day = d._day;}return *this;
}// 日期+=天数
Date& Date::operator+=(int day)//0次拷贝 0次赋值
{if (day < 0){return *this -= (-day);}_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);_month++;while (_month > 12){_month = 1;_year++;}}return *this;
}
// 日期+天数
Date Date::operator+(int day)//两次拷贝
{if (day < 0){return *this - (-day);}Date ret(*this);ret += day;return ret;
}// 日期-天数
Date Date::operator-(int day)
{if (day < 0){return *this + (-day);}Date ret = *this;ret -= day;return ret;
}// 日期-=天数
Date& Date::operator-=(int day)
{if (day < 0){return *this += (-day);}_day -= day;while (_day < 1){_month--;while (_month < 1){_month = 12;_year--;}_day += GetMonthDay(_year, _month);}return *this;
}//只读函数可以加const,内部不涉及修改成员//void Date::Print(const Data* this)
//void Date::Print() //可以同时存在,因为this指针类型不同,如果只有const版本也可调用,因为权限可以缩小
//{//不过在这里没有意义
// cout << _year << "/" << _month << "/" << _day << endl;
//}
void Date::Print() const
{cout << _year << "/" << _month << "/" << _day << endl;
}// 前置++
Date& Date::operator++()
{*this += 1;return *this;
}// 后置++
Date Date::operator++(int)
{Date tmp(*this);*this += 1;return tmp;
}
// 后置--
Date Date::operator--(int)
{Date tmp(*this);*this -= 1;return tmp;
}
// 前置--
Date& Date::operator--()
{*this -= 1;return *this;
}// >运算符重载
bool Date::operator>(const Date& d)
{if (_year < d._year){return false;}else if (_year == d._year && _month < d._month){return false;}else if (_year == d._year && _month == d._month && _day <= d._day){return false;}else{return true;}
}// ==运算符重载
bool Date::operator==(const Date& d)
{return _year == d._year && _month == d._month && _day == d._day;
}// >=运算符重载
bool Date::operator >= (const Date& d)
{return *this > d || *this == d;
}// <运算符重载
bool Date::operator < (const Date& d)
{return !(*this >= d);
}// <=运算符重载
bool Date::operator <= (const Date& d)
{return !(*this > d);
}// !=运算符重载
bool Date::operator != (const Date& d)
{return !(*this == d);
}//跟日期-天数构成函数重载
// 日期-日期 返回天数
int Date::operator-(const Date& d)
{Date max = *this;Date min = d;int flag = 1;if (max < min){min = *this;max = d;flag = -1;}int count = 0;while (max != min){--max;count++;}return count * flag;
}//日常自动生成的就可以
//不要被取到有效地址
//const Date* Date::operator&()const
//{
// return nullptr;
//}
const Date* Date::operator&()const
{return this;
}
//这个接口是读写
Date* Date::operator&()//函数重载,一个给const对象用,一个给非const对象用
{return this;
}
本篇结束!
相关文章:

类和对象:运算符重载
本篇文章来介绍一下C中的运算符重载,以及与运算符重载有关的三个默认默认成员函数:赋值运算符重载,普通对象取地址与const对象取地址操作符重载,也就是下面图片中6个默认成员函数的后三个,前三个默认成员函数在之前文章…...

Vue中使用VueAMap
npm 安装 npm install vue-amap --save注册:高德地图 // 在main.js中注册:高德地图 import VueAMap from "vue-amap"; Vue.use(VueAMap); VueAMap.initAMapApiLoader({key: "你的高德key",plugin: ["AMap.AutoComplete", //输入提示插件"A…...

Vue中的路由介绍以及Node.js的使用
🏅我是默,一个在CSDN分享笔记的博主。📚📚 🌟在这里,我要推荐给大家我的专栏《Vue》。🎯🎯 🚀无论你是编程小白,还是有一定基础的程序员,这个专栏…...

将本地项目上传至Github详解
目录 1 前言2 本地代码上传2.1 命令行方法2.2 图形界面法2.3 结果 1 前言 GitHub是一个面向开源及私有软件项目的托管平台,因为只支持Git作为唯一的版本库格式进行托管,故名GitHub 。开发者常常将github作为代码管理平台,方便代码存储、版本…...

Vivado下PLL实验
文章目录 前言一、CMT(时钟管理单元)1、CMT 简介2、FPGA CMT 框图3、MMCM 框图4、PLL 框图 二、创建工程1、创建工程2、PLL IP 核配置3、进行例化 三、进行仿真1、创建仿真文件2、进行仿真设置3、进行行为级仿真 四、硬件验证1、引脚绑定2、生成比特流文…...

简单理解推挽输出和开漏输出
推挽输出原理图: 特点: 1、INT1时,OUTVDD;INT0时,OUTGND。 2、推挽输出的两种输出状态,一种是PMOS管S级端的电压VDD,一种是NMOS管S端的地GND。 开漏输出原理图: 特点: …...

C++之va_start、vasprintf、va_end应用总结(二百二十六)
简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 人生格言: 人生…...
OpenCV自学笔记十一:形态学操作(一)
目录 1、腐蚀 2、膨胀 3、通用形态学函数 4、开运算 5、闭运算 1、腐蚀 腐蚀(Erosion)是数字图像处理中的一种形态学操作,用于消除图像中边界附近的细小区域或缩小对象的大小。腐蚀操作通过卷积输入图像与结构元素(也称为腐…...

封装全局异常处理
文章目录 1 定义错误码类2 定义业务异常类3 全局异常处理器4 使用5 前端请求效果总结 1 定义错误码类 可以定义各种错误码枚举,比如业务,系统相关的报错信息 /*** 错误代码* 错误码** author leovany* date 2023/09/23*/ public enum ErrorCode {SU…...

python的requests响应请求,结果乱码,即使设置了response.encoding也没有用的解决方法
一、问题 如图: 一般出现乱码,我们会有三种解决方式,如下但是图中解决了发现还是不行, response.encodingresponse.apparent_encoding通过看网页源码对response.encodingutf8指定编码格式或者直接通过response.content.decode()来获得源码 出…...

PyCharm 手动下载插件
插件模块一直加载失败,报错信息: Marketplace plugins are not loaded. Check the internet connection and refresh. 尝试了以下方法,均告失败: pip 换源Manage Plugin Repositories...HTTP 代理设置...关闭三个防火墙 最后选…...

Gnomon绑定基础(约束 IK 节点)
点约束 方向约束 父约束 目标约束 修改后 对象方向 IK控制柄 直的骨骼,指定IK怎么弯曲 直的骨骼,指定IK怎么弯曲 样条曲线 数学节点 乘除节点 混合节点 注意...

STL常用遍历,查找,算法
目录 1.遍历算法 1.1for_earch 1.2transform 2.常用查找算法 2.1find,返回值是迭代器 2.1.1查找内置数据类型 2.1.2查找自定义数据类型 2.2fin_if 按条件查找元素 2.2.1查找内置的数据类型 2.2.2查找内置数据类型 2.3查找相邻元素adjeacent_find 2.4查找指…...
BCC源码内容概览(1)
接前一篇文章:BCC源码编译和安装 本文参考官网中的Contents部分的介绍。 BCC源码根目录的文件,其中一些是同时包含C和Python的单个文件,另一些是.c和.py的成对文件,还有一些是目录。 跟踪(Tracing) exam…...

mysql限制用户登录失败次数,限制时间
mysql用户登录限制设置 mysql 需要进行用户登录次数限制,当使用密码登录超过 3 次认证链接失败之后,登录锁住一段时间,禁止登录这里使用的 mysql: 8.1.0 这种方式不用重启数据库. 配置: 首先进入到 mysql 命令行:然后需要安装两个插件: 在 mysql 命令行中执行: mysql> INS…...
从利用Arthas排查线上Fastjson问题到Java动态字节码技术(下)
上一篇从Arthas的源码引出了Java动态字节码技术,那么这一篇就从几种Java字节码技术出发,看看Arthas是如何通过动态字节码技术做到无侵入的源码增强; Java大部分情况下都是解释执行的,也就是解释.class文件,所以如果我们…...

Ubuntu中安装Anaconda 如何将 路径导入为全局变量
第一步:将你的anaconda 路径复制下来,在终端输入对应路径。 echo export PATH"/home/你的用户名/anaconda3/bin:$PATH" >> ~/.bashrc 第二步:在终端输入下面命令或者重启系统。 source ~/.bashrc 在对应的anaconda安装目…...

【QT】Qt的随身笔记(持续更新...)
目录 Qt 获取当前电脑桌面的路径Qt 获取当前程序运行路径Qt 创建新的文本文件txt,并写入内容如何向QPlainTextEdit 写入内容QTimerQMessageBox的使用QLatin1StringQLayoutC在c头文件中写#include类的头文件与直接写class加类名有何区别mutable关键字前向声明 QFontQ…...

【LeetCode-简单题】589. N 叉树的前序遍历
文章目录 题目方法一:单循环栈做法方法二:递归 题目 方法一:单循环栈做法 关键在于子节点的入栈顺序,决定了子节点的出栈顺序, 因为是前序遍历 所以压栈顺序先让右边的入栈 依次往左 这样左边的节点会在栈顶 这样下次…...
Linphone3.5.2 ARM RV1109音视频对讲开发记录
Linphone3.5.2 ARM RV1109音视频对讲开发记录 说明 这是一份事后记录,主要记录的几个核心关键点,有可能很多细节没有记上,主要是方便后面自己再找回来! 版本 3.5.2 一些原因选的是这样一个旧的版本! 新的开发最好选新一些的版…...

华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...
django filter 统计数量 按属性去重
在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...

什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...
Element Plus 表单(el-form)中关于正整数输入的校验规则
目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入(联动)2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...

优选算法第十二讲:队列 + 宽搜 优先级队列
优选算法第十二讲:队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...

Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)
目录 一、👋🏻前言 二、😈sinx波动的基本原理 三、😈波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、🌊波动优化…...