C++面向对象编程之六:重载操作符(<<,>>,+,+=,==,!=,=)
重载操作符
C++允许我们重新定义操作符(例如:+,-,*,/)等,使其对于我们自定义的类类型对象,也能像内置数据类型(例如:int,float,double)等一样直观,可以进行加,减,乘,除,比较大小等等操作。
重载操作符本质是函数,只是这个函数的函数名比较特别,为:operator后接需要重新定义的操作符的符号。例如,重载+号,函数名为:operator+;重载-号,函数名:operator-。因为重载操作符本质是函数,所以实际上就是为某个自定义的数据类类型或枚举类型实现函数重载,比如内置int类型已经有
int operator+(int, int)版本,我们有一个自定义的complex(复数类),要想跟内置的int类型有一样直观的操作两个复数相加,只要我们为自定义的complex(复数类)重定义操作符+,提供complex operator+(complex, complex)版本即可。
重载操作符语法
返回值类型 operator操作符号(形参列表)
{
}重载操作符的两种形式
对于大多数重载操作符来说,可以定义为全局函数或类的成员函数。
1.重载操作符为全局函数,并且通常必须将这个全局函数设置为所操作类的友元
#include <iostream>
using namespace std;class MyInt
{friend MyInt operator+(const MyInt &a, const MyInt &b); //这个函数为MyInt类的友元public:MyInt():m_num(0){}MyInt(const int num):m_num(num){}int getNum() const{return m_num;}private:int m_num;
};//重载操作符为全局函数
MyInt operator+(const MyInt &a, const MyInt &b)
{MyInt temp;temp.m_num = a.m_num + b.m_num; //如果没有把这个全局函数设置为操作类MyInt的友元,则不能直接访问MyInt类的//私有成员变量m_numreturn temp;
}int main(int argc, char *argv[])
{MyInt a(1);MyInt b(2);MyInt c = a + b; //本质是:调用了全局函数,即 MyInt c = operator+(a, b);MyInt d = operator+(a, b); cout << "a + b = " << c.getNum() << endl;cout << "operator+(a, b) = " << d.getNum() << endl;return 0;
}注意:重载操作符为全局函数时,本质是调用了该全局函数。例如:MyInt c = a + b; //本质是:调用了全局函数,即 MyInt c = operator+(a, b);
2.重载操作符为类的成员函数
#include <iostream>
using namespace std;class MyInt
{public:MyInt():m_num(0){}MyInt(const int num):m_num(num){}int getNum() const{return m_num;}MyInt operator+(const MyInt &b) //重载操作符为类成员函数{MyInt temp;temp.m_num = this->m_num + b.m_num;return temp;}private:int m_num;
};int main(int argc, char *argv[])
{MyInt a(1);MyInt b(2);MyInt c = a + b; //本质是:调用了MyInt类的成员函数,即 MyInt c = a.operator+(b);MyInt d = a.operator+(b);cout << "a + b = " << c.getNum() << endl;cout << "a.operator+(b) = " << d.getNum() << endl;return 0;
}注意:重载操作符为类的成员函数时,本质是调用了该类的成员函数。例如:MyInt c = a + b; //本质是:调用了MyInt类的成员函数,即 MyInt c = a.operator+(b);
重载操作符为全局函数和重载操作符为类的成员函数的形参区别
对于重载操作符为全局函数和重载操作符为类的成员函数,形参的个数看上去是有区别的,一般重载操作符为类的成员函数,其形参个数比重载操作符为全局函数的形参个数少1个,因为重载操作符为类的成员函数,类中有一个隐含的this指针形参,限定为重载操作符函数的第一个形参。
重载操作符为全局函数还是重载操作符为类的成员函数,应该怎么选择?
对于重载操作符可以为一个普通的全局函数或一个类的成员函数这两种方式来实现,我的建议是,对于能够使用类的成员函数来实现的,我们应该选择用类的成员函数来实现,因为选择用普通的全局函数来实现,需要把这个全局函数设置为操作类的友元,从而达到可以直接访问该操作类的非共有成员变量的目的,但这破坏类的封装性。
不能重载的操作符
. :类的对象访问操作符
.* ->* :类的对象或或类的对象指针访问类中的函数指针操作符
:: :域操作符
?= :条件操作符
sizeof :长度操作符
# :预处理操作符 重载操作符可以给我们自定义的类类型对象的进行操作符运算的时候更加直观。但不要滥用重载操作符。
1.不能改变内置类型的操作符的含义。例如:int operator+(int, int)
2.不能为内置类型定义额外的新的操作符。例如:不能定义两个数组为操作数的operator+
3.逗号(,),取地址(&),逻辑与(&&),逻辑或(||)这些操作符具有有用的内置含义,不建议重载。
假设我们有一个MyInt类,实现的功能跟内置的类型int一样。那么下面我们为这个MyInt类实现一下相关的操作符重载吧。
重载输出操作符<<
分析:
对于内置的类型int
int a = 0;
cout << a << endl;
<< 操作符有两个操作数,
第一个(左)操作数数据类型为:ostream&,
第二个(右)操作数数据类型为:const int&
所以我们只能重载输出操作符为全局函数,不能为MyInt类的成员函数,
因为重载操作符为类的成员函数的第一个操作数数据类型限定为this指针,
而我们需要的是第一个操作数数据类型为:ostream&,
因为输出a后,后面的操作是还能输出换行的,所以返回值类型为ostream&经过以上分析,我们可以得出,重载输出操作符<<的全局函数为:
ostream& operator<<(ostream &cout, const MyInt a)
#include <iostream>
using namespace std;class MyInt
{friend ostream& operator<<(ostream &cout, const MyInt &a);public:MyInt():m_num(0){}MyInt(const int num):m_num(num){}private:int m_num;
};ostream& operator<<(ostream &cout, const MyInt &a)
{cout << a.m_num;return cout;
}int main(int argc, char *argv[])
{MyInt a;cout << a << endl;return 0;
}
重载输入操作符>>
分析:
对于内置的类型int
int a = 0;
int b = 0;
cin >> a >> b;
>> 操作符有两个操作数,
第一个(左)操作数数据类型为:istream&,
第二个(右)操作数数据类型为:const int&
所以我们只能重载输出操作符为全局函数,不能为MyInt类的成员函数,
因为重载操作符为类的成员函数的第一个操作数数据类型限定为this指针,
而我们需要的是第一个操作数数据类型为:istream&,
因为输入a后,后面的操作是还能输入b的,所以返回值类型为istream&经过以上分析,我们可以得出,重载输入操作符>>的全局函数为:
istream& operator>>(istream &cin, MyInt &a)
#include <iostream>
using namespace std;class MyInt
{friend ostream& operator<<(ostream &cout, const MyInt &a);friend istream& operator>>(istream &cin, MyInt &a);public:MyInt():m_num(0){}MyInt(const int num):m_num(num){}private:int m_num;
};ostream& operator<<(ostream &cout, const MyInt &a)
{cout << a.m_num;return cout;
}istream& operator>>(istream &cin, MyInt &a)
{cin >> a.m_num;return cin;
}int main(int argc, char *argv[])
{MyInt a;MyInt b;cout << "请输入a和b的值,用空格隔开:" << endl;cin >> a >> b;cout << "a = " << a << ",b = " << b << endl;return 0;
}
算术运算符和关系运算符
一般来说,我们应该将算术操作符和关系操作符定义为非成员函数。这是因为,算术操作符和关系操作符中,例如:有左操作数为int a = 1,右操作数为double b = 2.12345,进行 a + b运算时,左操作数a会转成跟右操作数b的精度一样的double类型,即:a + b = 1.000000000000000(双精度保留15位小数)+ 2.123450000000000(双精度保留15位小数),如果将算术操作符和关系操作符定义定义为类的成员函数时,左操作数限定为隐含的this指针,就违背了在C/C++中,如果一个表达式中含有不同类型的常量或变量,在计算时,会将它们自动转换为精度更高的同一种数据类型。
重载相加操作符+
1.返回值类型为:MyInt,因为两个数相加,是不能改变左操作数或右操作数的值的,所以返回值类型为MyInt,而不是MyInt&
2.重载相加操作符+的非成员函数为:MyInt operator+(const MyInt &a, const MyInt &b)
#include <iostream>
using namespace std;class MyInt
{friend ostream& operator<<(ostream &cout, const MyInt &a);friend istream& operator>>(istream &cin, MyInt &a);friend MyInt operator+(const MyInt &a, const MyInt &b);public:MyInt():m_num(0){}MyInt(const int num):m_num(num){}private:int m_num;
};ostream& operator<<(ostream &cout, const MyInt &a)
{cout << a.m_num;return cout;
}istream& operator>>(istream &cin, MyInt &a)
{cin >> a.m_num;return cin;
}MyInt operator+(const MyInt &a, const MyInt &b)
{return MyInt(a.m_num + b.m_num);
}int main(int argc, char *argv[])
{MyInt a(1);MyInt b(2);cout << "a + b = " << a + b << endl;return 0;
}重载复合赋值操作符+=
1.返回值类型为:MyInt&,因为x += y;是要改变左操作数x,并且返回的还是被修改后的x的
2.重载复合赋值操作符+=的非成员函数为:MyInt& operator+=(MyInt &a, const MyInt &b)
#include <iostream>
using namespace std;class MyInt
{friend ostream& operator<<(ostream &cout, const MyInt &a);friend istream& operator>>(istream &cin, MyInt &a);friend MyInt& operator+=(MyInt &a, const MyInt &b);public:MyInt():m_num(0){}MyInt(const int num):m_num(num){}private:int m_num;
};ostream& operator<<(ostream &cout, const MyInt &a)
{cout << a.m_num;return cout;
}istream& operator>>(istream &cin, MyInt &a)
{cin >> a.m_num;return cin;
}MyInt& operator+=(MyInt &a, const MyInt &b)
{a.m_num += b.m_num;return a;
}int main(int argc, char *argv[])
{MyInt a(1);MyInt b(2);a += b;cout << "after a += b, a = " << a << endl;return 0;
}重载相等操作符==,不等操作符-=
1.如果类中有一个操作,是确认该类中的两个对象是否相等的,我们应该将该函数名定义为operator==,而不是定义一个命名函数。因为我们更习惯于用==操作符来比较两个对象是否相等。
2.对于一个类,如果我们定义了operator==,也应该定义operator!=。
3.定义了operator==的类更容易与标准库一起使用。例如:find,默认使用了==操作符,如果定义了==,那么这么算法可以无须任何特殊处理而用于该类类型。
#include <iostream>
using namespace std;class MyInt
{friend ostream& operator<<(ostream &cout, const MyInt &a);friend istream& operator>>(istream &cin, MyInt &a);friend bool operator==(const MyInt &a, const MyInt &b);friend bool operator!=(const MyInt &a, const MyInt &b);public:MyInt():m_num(0){}MyInt(const int num):m_num(num){}private:int m_num;
};ostream& operator<<(ostream &cout, const MyInt &a)
{cout << a.m_num;return cout;
}istream& operator>>(istream &cin, MyInt &a)
{cin >> a.m_num;return cin;
}bool operator==(const MyInt &a, const MyInt &b)
{return a.m_num == b.m_num;
}bool operator!=(const MyInt &a, const MyInt &b)
{return a.m_num != b.m_num;
}int main(int argc, char *argv[])
{MyInt a(1);MyInt b(2);MyInt c(1);if (a == b)cout << "a == b" << endl;else cout << "a != b" << endl;if (a == c)cout << "a == c" << endl;else cout << "a != c" << endl;if (a != b)cout << "a != b" << endl;else cout << "a == b" << endl;if (a != c)cout << "a != c" << endl;else cout << "a == c" << endl;return 0;
}重载赋值操作符=
1.C++是允许类类型对象给同类型的其他对象赋值的,具体可查看我写的这篇文章里利用等号法创建对象的介绍C++面向对象编程之二:构造函数、拷贝构造函数、析构函数,这里不再累赘。类赋值操作符形参的数据类型为该类类型,通常形参为该类类型的const引用,或该类的非const引用或类类型或更多的其他数据类型(例如:MyInt类中,那么该类的赋值操作符形参可为const MyInt &,或MyInt &或MyInt)。如果我们没有定义赋值操作符=,那么编译器将为该类提供一个。所以,类赋值操作符=必须是类成员函数,以便编译器可以知道是否需要为该类提供一个。
2.重载赋值操作符=的返回值类型必须是*this的引用(即左操作符的引用),这样子返回是跟内置数据类型的赋值是一致的。因为赋值返回*this的引用,这样子就不需要创建一个该类的一个临时对象,然后将临时对象返回,然后再调用该类的拷贝构造函数,将该临时对象的值拷贝后,又释放该临时对象,这一系列的系统开销,从而提高代码的执行效率。
#include <iostream>
using namespace std;class MyInt
{friend ostream& operator<<(ostream &cout, const MyInt &a);friend istream& operator>>(istream &cin, MyInt &a);public:MyInt():m_num(0){}MyInt(const int num):m_num(num){}MyInt& operator=(const MyInt &b){m_num = b.m_num;return *this;}private:int m_num;
};ostream& operator<<(ostream &cout, const MyInt &a)
{cout << a.m_num;return cout;
}istream& operator>>(istream &cin, MyInt &a)
{cin >> a.m_num;return cin;
}int main(int argc, char *argv[])
{MyInt a(1);MyInt b = a;cout << "a = " << a << endl;cout << "b = " << b << endl;return 0;
}好了,关于重载操作符,篇幅比较长,本文对重载操作符(<<,>>,+,+=,==,!=,=)进行了讲解,还有一些比较重要的操作符,比如前++,后++,前--,后--,下标操作符[]等,还没有介绍,就放到下一篇博文吧!
相关文章:
C++面向对象编程之六:重载操作符(<<,>>,+,+=,==,!=,=)
重载操作符C允许我们重新定义操作符(例如:,-,*,/)等,使其对于我们自定义的类类型对象,也能像内置数据类型(例如:int,float,double&…...
JS_wangEditor富文本编辑器
官网:https://www.wangeditor.com/ 引入 CSS 定义样式 <link href"https://unpkg.com/wangeditor/editorlatest/dist/css/style.css" rel"stylesheet"> <style>#editor—wrapper {border: 1px solid #ccc;z-index: 100; /* 按需定…...
Django实践-06导出excel/pdf/echarts
文章目录Django实践-06导出excel/pdf/echartsDjango实践-06导出excel/pdf/echarts导出excel安装依赖库修改views.py添加excel导出函数修改urls.py添加excel/运行测试导出pdf安装依赖库修改views.py添加pdf导出函数修改urls.py添加pdf/生成前端统计图表修改views.py添加get_teac…...
java并发入门(一)共享模型—Synchronized、Wait/Notify、pack/unpack
一、共享模型—管程 1、共享存在的问题 1.1 共享变量案例 package com.yyds.juc.monitor;import lombok.extern.slf4j.Slf4j;Slf4j(topic "c.MTest1") public class MTest1 {static int counter 0;public static void main(String[] args) throws InterruptedEx…...
Ast2500增加用户自定义功能
备注:这里使用的AMI的开发环境MegaRAC进行AST2500软件开发,并非openlinux版本。1、添加上电后自动执行的任务在PDKAccess.c中列出了系统启动过程中的所有任务,若需要添加功能,在相应的任务中添加自定义线程。一般在两个任务里面添…...
用Python暴力求解德·梅齐里亚克的砝码问题
文章目录固定个数的砝码可称量重量砝码的组合方法40镑砝码的组合问 一个商人有一个40磅的砝码,由于跌落在地而碎成4块。后来,称得每块碎片的重量都是整磅数,而且可以用这4 块来称从1 至40 磅之间的任意整数磅的重物。问这4 块砝码片各重多少&…...
离散Hopfield神经网络的分类——高校科研能力评价
离散Hopfield网络离散Hopfield网络是一种经典的神经网络模型,它的基本原理是利用离散化的神经元和离散化的权值矩阵来实现模式识别和模式恢复的功能。它最初由美国物理学家John Hopfield在1982年提出,是一种单层的全连接神经网络,被广泛应用于…...
Retrofit核心源码分析(三)- Call逻辑分析和扩展机制
在前面的两篇文章中,我们已经对 Retrofit 的注解解析、动态代理、网络请求和响应处理机制有了一定的了解。在这篇文章中,我们将深入分析 Retrofit 的 Call 逻辑,并介绍 Retrofit 的扩展机制。 一、Call 逻辑分析 Call 是 Retrofit 中最基本…...
源码分析spring如和对@Component注解进行BeanDefinition注册的
Spring ioc主要职责为依赖进行处理(依赖注入、依赖查找)、容器以及托管的(java bean、资源配置、事件)资源声明周期管理;在ioc容器启动对元信息进行读取(比如xml bean注解等)、事件管理、国际化等处理;首先…...
C语言--字符串函数1
目录前言strlenstrlen的模拟实现strcpystrcatstrcat的模拟实现strcmpstrcmp的模拟实现strncpystrncatstrncmpstrstrstrchr和strrchrstrstr的模拟实现前言 本章我们将重点介绍处理字符和字符串的库函数的使用和注意事项。 strlen 我们先来看一个我们最熟悉的求字符串长度的库…...
Webstorm使用、nginx启动、FinalShell使用
文章目录 主题设置FinalShellFinalShell nginx 启动历史命令Nginx页面发布配置Webstorm的一些常用快捷键代码生成字体大小修改Webstorm - gitCode 代码拉取webstorm 汉化webstorm导致CPU占用率高方法一 【忽略node_modules】方法二 【设置 - 代码编辑 - 快速预览文档 - 关闭】主…...
源码分析Spring @Configuration注解如何巧夺天空,偷梁换柱。
前言 回想起五年前的一次面试,面试官问Configuration注解和Component注解有什么区别?记得当时的回答是: 相同点:Configuration注解继承于Component注解,都可以用来通过ClassPathBeanDefinitionScanner装载Spring bean…...
vector的使用及模拟实现
目录 一.vector的介绍及使用 1.vector的介绍 2.vector的使用 1.vector的定义 2.vector iterator的使用 3. vector 空间增长问题 4.vector 增删查改 3.vector 迭代器失效问题(重点) 1. 会引起其底层空间改变的操作 2.指定位置元素的删除操作--erase 3. Li…...
“华为杯”研究生数学建模竞赛2007年-【华为杯】A题:基于自助法和核密度估计的膳食暴露评估模型(附获奖论文)
赛题描述 我国是一个拥有13亿人口的发展中国家,每天都在消费大量的各种食品,这批食品是由成千上万的食品加工厂、不可计数的小作坊、几亿农民生产出来的,并且经过较多的中间环节和长途运输后才为广大群众所消费,加之近年来我国经济发展迅速而环境治理没有能够完全跟上,以…...
刷题(第三周)
目录 [CISCN2021 Quals]upload [羊城杯 2020]EasySer [网鼎杯 2020 青龙组]notes [SWPU2019]Web4 [Black Watch 入群题]Web [HFCTF2020]BabyUpload [CISCN2021 Quals]upload 打开界面以后,发现直接给出了源码 <?php if (!isset($_GET["ctf"]))…...
新C++(14):移动语义与右值引用
当你在学习语言的时候,是否经常听到过一种说法,""左边的叫做左值,""右边的叫做右值。这句话对吗?从某种意义上来说,这句话只是说对了一部分。---前言一、什么是左右值?通常认为:左值是一个表示数据的表达式(…...
TCP相关概念
目录 一.滑动窗口 1.1概念 1.2滑动窗口存在的意义 1.3 滑动窗口的大小变化 1.4丢包问题 二.拥塞控制 三.延迟应答 四.捎带应答 五.面向字节流 六.粘包问题 七.TIME_WAIT状态 八.listen第2个参数 九.TCP总结 一.滑动窗口 1.1概念 概念:双方在进行通信时&a…...
MySQL锁篇
MySQL锁篇 一、一条update语句 我们的故事继续发展,我们还是使用t这个表: CREATE TABLE t (id INT PRIMARY KEY,c VARCHAR(100) ) EngineInnoDB CHARSETutf8;现在表里的数据就是这样的: mysql> SELECT * FROM t; —------- | id | c | —…...
SWF (Simple Workflow Service)简介
Amazon Simple Workflow Service (Amazon SWF) 提供了给应用程序异步、分布式处理的流程工具。 SWF可以用在媒体处理、网站应用程序后端、商业流程、数据分析和一系列定义好的任务上。 举个例子,下图表明了一个电商网站的工作流程,其中涉及了程序执行的…...
java(Class 常用方法 获取Class对象六种方式 动态和静态加载 类加载流程)
ClassClass常用方法获取Class对象六种方式哪些类型有Class对象动态和静态加载类加载流程加载阶段连接阶段连接阶段-验证连接阶段-准备连接阶段-解析初始化阶段获取类结构信息Class常用方法 第一步:创建一个实体类 public class Car {public String brand "宝…...
Prompt Tuning、P-Tuning、Prefix Tuning的区别
一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...
Oracle查询表空间大小
1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南 在数字化营销时代,邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天,我们将深入解析邮件打开率、网站可用性、页面参与时…...
iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈
在日常iOS开发过程中,性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期,开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发,但背后往往隐藏着系统资源调度不当…...
【从零学习JVM|第三篇】类的生命周期(高频面试题)
前言: 在Java编程中,类的生命周期是指类从被加载到内存中开始,到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期,让读者对此有深刻印象。 目录 …...
Python Einops库:深度学习中的张量操作革命
Einops(爱因斯坦操作库)就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库,用类似自然语言的表达式替代了晦涩的API调用,彻底改变了深度学习工程…...
Rust 开发环境搭建
环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行: rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu 2、Hello World fn main() { println…...
给网站添加live2d看板娘
给网站添加live2d看板娘 参考文献: stevenjoezhang/live2d-widget: 把萌萌哒的看板娘抱回家 (ノ≧∇≦)ノ | Live2D widget for web platformEikanya/Live2d-model: Live2d model collectionzenghongtu/live2d-model-assets 前言 网站环境如下,文章也主…...
