C++ 运算符重载(一) | 输入/输出,相等/不等,复合赋值,下标,自增/自减,成员访问运算符

文章目录

  • 输出运算符<<
  • 输入运算符>>
  • 相等/不等运算符
  • 复合赋值运算符
  • 下标运算符
  • 自增/自减运算符
  • 成员访问运算符


输出运算符<<

通常情况下,输出运算符的第一个形参是一个 非常量ostream对象的引用 。之所以 ostream 是非常量是因为向流写入内容会改变其状态;而该形参是引用是因为我们无法直接复制一个 ostream 对象。

第二个形参一般来说是一个 常量的引用,该常量是我们想要打印的类类型。第二个形参是引用的原因是我们希望避免复制实参;而之所以该形参可以是常量是因为(通常情况下)打印对象不会改变对象的内容。

为了与其他输出运算符保持一致,operator<< 一般要返回它的 ostream 形参。

通常我们需要在类中重载 << 以避免查看成员时输出操作过于繁琐:

class A {friend ostream& operator<<(ostream& os, const A& a);int i = 1;double d = 3.14;
};
ostream& operator<<(ostream& os, const A& a) {os << a.i << " " << a.d;return os;
}

在这里插入图片描述

值得注意的几点:

  1. 减少格式化操作(如:换行符): 目的是给用户更大的自由去决定输出的格式,如果我们自带换行,那么用户就无法在同一行内解接着打印一些描述性文本了。
  2. 输入输出运算符必须是非成员函数: 如果是某个类的成员函数,则输入输出运算符也必须是 istreamostream 成员(详见下文),但是这两个类(istreamostream)属于标准库,而我们无法给标准库中的类添加任何成员。
  3. 可以将IO运算符声明为友元: 既然我们的 IO操作 又想访问类的私有成员,又不能是类的成员函数,那么声明成友元是最佳选择。

用例子来解释一下第二点:

我们都知道重载运算符的返回类型一定要与它的实际操作相匹配,因此,重载 == 返回值为 bool ;重载 + 返回值为 类的引用 ……

输入输出运算符是 IO类 的成员函数,因此其返回类型是 IO类本身 ,那么如果某个类将 重载的输入输出运算符 作为 成员函数 的话,返回类型 就会变成 这个类本身,重载的输入输出运算符的左侧运算对象则是这个类的一个对象:

class B {int i = 1;double d = 3.14;
public:ostream& operator<<(ostream& os) {os << i << " " << d;return os;}
};
B b;
b << cout; // 这样调用不符合我们的输出习惯

如此一来改变了 << 的调用方式,也就不算构成重载了,如果既要 cout << b;,还要 <<B 的成员。那么就要在 ostream类 中添加 ostream& operator<<(B&); ,可正如前文所说,ostream 属于标准库,我们无法给标准库中的类添加任何成员。ostream<< 的各类重载如下:
在这里插入图片描述


输入运算符>>

  • 通常情况下,输入运算符的第一个形参是运算符将要读取的流的引用
  • 第二个形参是将要读入到的对象的引用(对象不能是常量,因为将数据读入到这个对象中实际是修改了这个对象)
  • 通常会返回某个给定流的引用

与输出运算符不同的是,输入运算符必须处理输入失败的情况:

class A {friend istream& operator>>(istream& is, A& a);friend ostream& operator<<(ostream& os, const A& a);int i = 1;double d = 3.14;
};istream& operator>>(istream& is, A& a) {int i1;double d1;is >> i1 >> d1;if (is) {a.i = i1;a.d = d1;}else a = A();return is;
}ostream& operator<<(ostream& os, const A& a) {os << a.i << " " << a.d;return os;
}

重写输入运算符后,可以对输入的数据进行对应处理:
在这里插入图片描述
当没有输入/输入错误时,用构造函数创建一个临时量,然后调用赋值运算符为 a 赋值:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

当有多个输入时,对输入进行处理:
在这里插入图片描述
在这里插入图片描述


相等/不等运算符

对于类而言,判断相等需要比较每一项数据成员,因此有必要对相等运算符进行重载。

如果定义了 operator==,则这个类也应该定义 operator!=。对于用户来说,当他们能使用 == 时肯定也希望能使用 !=,反之亦然。

相等运算符和不相等运算符中的一个应该把工作委托给另外一个,这意味着其中一个运算符应该负责实际比较对象的工作,而另一个运算符则只是调用那个真正工作的运算符。

class A {friend istream& operator>>(istream& is, A& a);friend ostream& operator<<(ostream& os, const A& a);int i = 1;double d = 3.14;
public:bool operator==(const A& a) {return this->d == a.d && this->i == a.i;}bool operator!=(const A& a) {return !(*this == a);}
};

在这里插入图片描述


复合赋值运算符

复合赋值运算符不一定非得是类的成员,不过我们还是倾向于把包括复合赋值在内的所有赋值运算都定义在类的内部。为了与内置类型的复合赋值保持一致,类中的复合赋值运算符也要返回其左侧运算对象的引用

PS:赋值运算符必须是类的成员

A& operator+=(const A& a) {i += a.i;d += a.d;return *this;
}

在这里插入图片描述


下标运算符

  • 下标运算符必须是成员函数。
  • 为了与下标的原始定义兼容,下标运算符通常以所访问元素的引用作为返回值,这样做的好处是下标可以出现在赋值运算符的任意一端。
  • 如果一个类包含下标运算符,则它通常会定义两个版本:一个返回普通引用,另一个是类的常量成员并且返回常量引用。
class IntVec // IntVec是对标准库vector类的模仿,仅存储int元素
{int* begin; // 指向已分配的内存中的首元素int* end; // 指向最后一个实际元素之后的位置int* cap; // 指向分配的内存末尾之后的位置
public:int& operator[](int n) { return begin[n]; }const int& operator[](int n) const { return begin[n]; } // 第二个const修饰*this
};

下标运算符返回的是元素的引用,当 IntVec 是非常量时,我们可以给元素赋值;而我们对常量对象取下标时,不能对其赋值。


自增/自减运算符

与内置类型一样,重载的自增自减同时要有前置版本后置版本

要想同时定义前置和后置运算符,必须首先解决一个问题,即普通的重载形式无法区分这两种情况。

为了解决这个问题,后置版本接受一个额外的(不被使用)int 类型的形参。当我们使用后置运算符时,编译器为这个形参提供一个值为 0 的实参。尽管从语法上来说后置函数可以使用这个额外的形参,但是在实际过程中通常不会这么做。这个形参的唯一作用就是区分前置版本和后置版本的函数,而不是真的要在实现后置版本时参与运算。

前置版本:

// 仅作伪代码实现
类名& operator++();
类名& operator--();

后置版本:

为了与内置版本保持一致,后置运算符应该返回对象的原值(递增或递减之前的值),返回的形式是一个值而非引用。

类名 operator++(int); // 我们不会用到int形参,因此无需为它命名。
类名 operator--(int);
// 举个例子,但不详细实现Ptr类了,可以将它理解为 IntVec(或真正的顺序容器) 的指针类
Ptr operator++(int){Ptr ret = *this; // 记录当前值++*this; // 调用前置++运算符,前置++需要检查自增的有效性return res; // 返回之前记录的状态
}
/* 显式地调用后置运算符 */
Ptr p(v); // p指向v中的vector
p.operator++(0); // 调用后置版本,尽管0会被忽略,却必不可少,因为编译器只有通过它才知道应该使用后置版本。
p.operator++(); // 调用前置版本

成员访问运算符

箭头运算符(->)必须是类的成员。 解引用运算符(*)则无硬性要求。

// 伪代码
class Ptr{
public:int& operator*() const {// 检查解引用对象是否在规定范围内return *p[下标]; // *p可以是形如vector的对象}int* operator->() const {return & this->operator*(); //将工作委托给解引用运算符}
};

较之解引用运算符,重载箭头运算符有些限制,重载的箭头运算符必须返回类的指针或者自定义了箭头运算符的某个类的对象。

例如,对于形如 p->mem 的表达式来说,根据 p 类型的不同,表达式分别等价于:

(*p).mem; // p是一个内置的指针类型
p.operator()->mem; // p是类的对象

除此之外,代码都将发生错误。p->mem 的执行过程如下所示:
1.如果 p 是指针,则我们应用内置的箭头运算符,首先解引用该指针,然后从所得的对象中获取指定的成员。如果 p 所指的类型没有名为 mem 的成员,程序会发生错误。
2.如果 p 是定义了 operator-> 的类的一个对象,如果 p.operator()-> 的结果是一个指针,则执行第1步;如果该结果本身含有重载的 operator->(),则重复调用当前步骤。最终,当这一过程结束时程序或者返回了所需的内容,或者返回一些表示程序错误的信息。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/443736.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++ 重载函数调用运算符 | 再探lambda,函数对象,可调用对象

文章目录重载函数调用运算符lambdalambda等价于函数对象lambda等价于类标准库函数对象可调用对象与function可调用对象function函数重载与function重载函数调用运算符 函数调用运算符必须是成员函数。 一个类可以定义多个不同版本的调用运算符&#xff0c;互相之间应该在参数数…

C++ 运算符重载(二) | 类型转换运算符,二义性问题

文章目录类型转换运算符概念避免过度使用类型转换函数解决上述问题的方法转换为 bool显式的类型转换运算符类型转换二义性重载函数与类型转换结合导致的二义性重载运算符与类型转换结合导致的二义性类型转换运算符 概念 类型转换运算符&#xff08;conversion operator&#…

分布式理论:CAP、BASE | 分布式存储与一致性哈希

文章目录分布式理论CAP定理BASE理论分布式存储与一致性哈希简单哈希一致性哈希虚拟节点分布式理论 CAP定理 一致性&#xff08;Consistency&#xff09;&#xff1a; 在分布式系统中的所有数据副本&#xff0c;在同一时刻是否一致&#xff08;所有节点访问同一份最新的数据副…

分布式系统概念 | 分布式事务:2PC、3PC、本地消息表

文章目录分布式事务2PC&#xff08;二阶段提交协议&#xff09;执行流程优缺点3PC&#xff08;三阶段提交协议&#xff09;执行流程优缺点本地消息表&#xff08;异步确保&#xff09;分布式事务 分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分…

数据结构算法 | 单调栈

文章目录算法概述题目下一个更大的元素 I思路代码下一个更大元素 II思路代码132 模式思路代码接雨水思路算法概述 当题目出现 「找到最近一个比其大的元素」 的字眼时&#xff0c;自然会想到 「单调栈」 。——三叶姐 单调栈以严格递增or递减的规则将无序的数列进行选择性排序…

最长下降子序列

文章目录题目解法DP暴搜思路代码实现贪心二分思路代码实现题目 给出一组数据 nums&#xff0c;求出其最长下降子序列&#xff08;子序列允许不连续&#xff09;的长度。&#xff08;类似于lc的最长递增子序列&#xff09; 示例&#xff1a; 输入&#xff1a; 6 // 数组元素个…

Linux 服务器程序规范、服务器日志、用户、进程间的关系

文章目录服务器程序规范日志rsyslogd 守护进程syslog函数openlog函数setlogmask函数closelog函数用户进程间的关系进程组会话系统资源限制改变工作目录和根目录服务器程序后台化服务器程序规范 Linux 服务器程序一般以后台进程&#xff08;守护进程[daemon]&#xff09;形式运…

IO模型 :阻塞IO、非阻塞IO、信号驱动IO、异步IO、多路复用IO

文章目录IO模型阻塞IO非阻塞IO信号驱动IO多路复用IO异步IOIO模型 根据各自的特性不同&#xff0c;IO模型被分为阻塞IO、非阻塞IO、信号驱动IO、异步IO、多路复用IO五类。 最主要的两个区别就是阻塞与非阻塞&#xff0c;同步与异步。 阻塞与非阻塞 阻塞与非阻塞最主要的区别就…

Tomcat服务器集群与负载均衡实现

一、前言 在单一的服务器上执行WEB应用程序有一些重大的问题&#xff0c;当网站成功建成并开始接受大量请求时&#xff0c;单一服务器终究无法满足需要处理的负荷量&#xff0c;所以就有点显得有点力不从心了。另外一个常见的问题是会产生单点故障&#xff0c;如果该服务器坏掉…

Linux服务器 | 事件处理模式:Reactor模式、Proactor模式

文章目录Reactor模式Proactor模式同步I/O模型模拟Proactor模式两者的优缺点ReactorProactor同步I/O模型通常用于实现 Reactor 模式&#xff0c;异步I/O模型通常用于实现 Proactor 模式。&#xff08;不是绝对的&#xff0c;同步I/O也可模拟出 Proactor 模式&#xff09; React…

Linux服务器 | 服务器模型与三个模块、两种并发模式:半同步/半异步、领导者/追随者

文章目录两种服务器模型及三个模块C/S模型P2P模型I/O处理单元、逻辑单元、存储单元并发同步与异步半同步/半异步模式变体&#xff1a;半同步/半反应堆模式改进&#xff1a;高效的半同步/半异步模式领导者/追随者模式组件 &#xff1a;句柄集、线程集、事件处理器工作流程两种服…

字符串匹配之KMP(KnuthMorrisPratt)算法(图解)

文章目录最长相等前后缀next数组概念代码实现图解GetNext中的回溯改进代码实现代码复杂度分析最长相等前后缀 给出一个字符串 ababa 前缀集合&#xff1a;{a, ab, aba, abab} 后缀集合&#xff1a;{a, ba, aba, baba} 相等前后缀 即上面用同样颜色标识出来的集合元素&#…

Android入门(一) | Android Studio的配置与使用

文章目录安装配置Android Studio使用Android Studio模拟器更改Android SDK的路径Hello World&#xff01;安装配置Android Studio 从这一步开始&#xff1a; 一直点 next 即可&#xff0c;直到存储路径的选择上&#xff0c;可以放到非 C 盘&#xff0c;这里我放到 D 盘了&am…

Android 入门(四) | Intent 实现 Activity 切换

文章目录Intent显式 Intent定义两个 xml 文件android:orientationmatch_parent 和 wrap_contentIntent函数定义两个 Activity隐式 Intent更多隐式 Intent 的用法用隐式 Intent 打开系统浏览器自建 Activity 以响应打开网页的 Intent向下一个活动传递数据返回数据给上一个活动In…

Android入门(二) | 项目目录及主要文件作用分析

文章目录项目目录分析app目录分析AndroidManifest.xml 分析MainActivity.kt 分析build.gradle 分析最外层目录下的 build.gradleapp 目录下的 build.gradle项目目录分析 我们来看一下 src/main/res 下的一些文件&#xff1a; .gradle 和 .idea &#xff1a;这两个目录下放置…

Android入门(三) | Android 的日志工具 Logcat

文章目录日志工具类 android.util.LogLogcat 中的过滤器日志工具类 android.util.Log Log 从属日志工具类 android.util.Log &#xff0c;该类提供了五个方法供我们打印日志&#xff1a; Log.v() &#xff1a;用于打印那些最为琐碎的、意义最小的日志信息。对应级别 verbose&…

Android入门(五) | Activity 的生命周期

文章目录Activity 的状态及生命周期实现管理生命周期FirstActivitySecondActivityDialogActivity运行结果旧活动被回收了还能返回吗&#xff1f;Activity 的状态及生命周期 Android 的应用程序运用 栈&#xff08;Back Stack&#xff09; 的思想来管理 Activity&#xff1a; …

Android入门(六) | Activity 的启动模式 及 生产环境中关于 Activity 的小技巧

文章目录Activity 的启动模式standardsingleTopsingleTasksingleInstance技巧了解当前界面是哪个 Activity随时随地退出程序启动活动的最佳写法Activity 的启动模式 standard&#xff1a;默认的启动方式&#xff0c;每次启动一个活动都会重新创建singleTop&#xff1a;如果该活…

Android入门(七) | 常用控件

文章目录TextView 控件&#xff1a;文本信息Button 控件&#xff1a;按钮EditText 控件&#xff1a;输入框ImageView 控件&#xff1a;图片ProgressBar 控件&#xff1a;进度条AlertDialog 控件&#xff1a;提示框ProgressDialog 控件&#xff1a;带有进度条的提示框TextView 控…

Android入门(八) | 常用的界面布局 及 自定义控件

文章目录LinearLayout &#xff1a;线性布局android:layout_gravity &#xff1a;控件的对齐方式android:layout_weight&#xff1a;权重RelativeLayout &#xff1a;相对布局相对于父布局进行定位相对于控件进行定位边缘对齐FrameLayout &#xff1a;帧布局Percent &#xff1…