C++ 操作符重载

article/2025/11/11 15:41:39

输出操作符"<<" 和输入操作运算符">>"

操作符重载,也叫运算符重载,是C++的重要组成部分,它可以让程序更加的简单易懂,简单的运算符使用可以使复杂函数的理解更直观。

操作符重载可对 已有的运算符(C++中预定义的运算符)赋予多重的含义,是一种用于同一运算符作用于不同类的数据时可以导致有不同类型的行为(多态)

一个操作符被能重载的基本前提是:
1.只能为用户自定义的类型进行操作符的重载
2.不能对操作符的语法(优先级,结合性,操作数个数,语法结构,语义) 进行颠覆。
3.不能重载新的自定义操作符

在C++中,左移运算符<<可以和cout一起输出,因此也常被称为"流插入操作符",或"输出操作符"。 但实际上!<<本来没有这样的功能,之所以能和cout一起使用来输出数据,是因为此<<操作符被重载

在C++中,标准库本身已经对左移运算符<<和右移运算符>>分别进行了多次重载,使其能够用于不同数据的输入输出,但是输入输出的对象只能是 C++ 内置的数据类型(例如 bool、int、double 等)和 标准库所包含的类类型(例如 stringcomplexofstreamifstream 等)


重载"<< >>"输入和输出C++内置的数据类型

cout类与输出操作符"<<"的重载:

cout是ostream类的对象,ostream类和cout都是在头文件 <iostream>中声明的,ostream类将<<重载为其成员函数,而且重载多次,例如,为了使 [cout<<"hello world"] 能够成立,程序便重载了<<操作符用于输出char类型数据,具体来看,ostream需要对<<操作符进行以下重载操作:

ostream &ostream::operator<<(const char *s)
{ //...输出s的代码return	*this;
}

为了使 [out<<5] 能够成立(重载<<操作符用于输出int类型数据),ostream 类还需要将<<进行如下重载操作:

ostream & ostream::operator << (int n)
{//...输出n的代码return *this;函数返回当前对象*this指针(当前对象的值的地址)
}

以上述代码为例:
重载操作符函数的返回值类型为ostream的引用(ostream&),并且函数返回当前对象*this指针(当前对象的值的地址),就使得 [cout<<"hello world"<<5] 能够成立。

有了上方的重载函数进行<<操作符的重载 [cout<<"hello world"<<5]就等价于:[cout.operator<<("hello world")).operator<<(5)]

重载函数返回*this,使cout<<"hello world"这个表达式的值依然是cout(更准确的说法是cout的引用,等价于cout),所以能和<<5继续进行运算

cin类与输出操作符">>"的重载:

cin是istream类的对象,是在头文件<iostream>中声明的。istream类>>重载为成员函数,因此cin才能和>>进行联用以输入数据,一般也将>>称为 流提取操作符或者输入操作符


重载"<< >>"符用于输入和输出用户自己定义的类型的数据

例题:假定a,b,c是Comlex复数类对象,现在希望程序输入cin>>a>>b,便能从键盘接收a和b复数形式的输入(输入两个复数),写入cout<<c就能以a+b的形式输出a,b两个复数的和(c的值)。

代码演示:

#include <iostream>using namespace std;//todo案例一:假定c是Comlex复数类对象, 现在希望写cout << c; 就能以a + bi的形式输出c的值, 写cin >> c, 就能从键盘接收a + bi形式的输入,并使得c.real = a, c.imag = b;class Comlex
{
public:double real, imag;//Comlex类构造器Comlex(double r=0, double i=0) :real(r), imag(i) {};🔥Comlex的类构造器的形式列表中的两个形参因为需要进行覆盖输入,所以要设置两个参数的默认值为0,设置了默认值后,在构建Complex类的对象 a 时 ,就可以写成 Complex a ;而无需给对象a添加参数列表🔥为什么在构造器形参列表后面加上冒号:其实冒号后的内容(:real(r), imag(i))是初始化值,意为传入的形参数据用来初始化类成员,相当于://	Comlex(double r=0, double i=0) //	{//		real(r)//		imag(i)//	}Comlex operator +(Comlex& c);//"+"号运算符的重载函数声明(用于复数的计算)friend ostream& operator <<(ostream& os, const Comlex &c);//流输出<<输出操作符的重载函数声明friend istream& operator >>(istream& is,  Comlex& c);//流插入>>输入操作符的重载函数声明🔥为什么"+"号运算符的重载函数是类成员函数,而两个流插入操作符的重载函数一定要采用友元函数呢?因为,在重载输入输出运算符时,只能采用全局函数的方式(因为我们不能在ostream和istream类中编写成员函数),这才是友元函数真正的应用场景!}ostream& operator <<(ostream& os, const Comlex& c)
{os << "("<<c.real << "," << c.imag << "i)";//以a+b的形式输出return os;//return *this;//todo返回对象this指针(返回当前对象值)
}istream& operator >> (istream& is,  Comlex& c)
{is >> c. real >> c.imag;return is;
}Comlex Comlex::operator +(Comlex& c)
{return 	Comlex(real + c.real, imag + c.imag);
}int main()
{Comlex a,b,c;cin >> a >>b; 对象a输入的值为1.5 5.5 | 对象b输入的值为5.6 -4.2c = a + b;cout <<a << "+" << b << "==" << c << endl;return 0;
}

运行结果:
在这里插入图片描述


代码分析:

重载输出操作符<<

类内声明(函数类型为类内的友元函数):

friend ostream& operator <<(ostream& os, const Comlex &c);

为什么"+"号运算符的重载函数是类成员函数,而两个流插入操作符的重载函数一定要采用友元函数呢?
因为,在重载输入输出运算符时,只能采用全局函数的方式(因为我们不能在ostream和istream类中编写成员函数 且此重载函数需访问Comlex自定义类的所有访问级别的成员),这才是友元函数真正的应用场景!

类外定义:

ostream& operator <<(ostream& os, const Comlex& c)
{os << "("<<c.real << "," << c.imag << "i)";//以a+b的形式输出return os;
}

ostream&-返回类型
返回类型是ostream的引用(ostream &),意为返回的值属于ostream类(也就是ostream类的对象cout),一般来说,在调用operator<<()重载函数时传递给他的是哪一个流,它返回的就应是那个流类型的一个引用

operator <<-重载函数名:
调用此重载函数时省略operator 关键字

参数列表中:

ostream& os:
第一个输入参数,因为由cout<<可知,<<运算符的第一个运算量是cout,而cout是ostream类的一个对象,因此函数的第一个参数是ostream类型的引用(ostream& os),os是将要向他写数据的流(ostream流)的引用,它是以"引用传递"的方式传递参数的。

Comlex& c:
第二个输入参数,c是要输出的自定义类(Comlex类)的引用,也可以说是传入到的数据流里的数据值
不同的operator<<()重载函数就是因为这个输入参数才相互区别的。

由上面可知,因为这两个操作符的第一个运算量的类型(调用重载函数 "opeator >> "的类 )不是我们正在定义的类,所以<< >>的重载不能用成员函数。因此,将这两个运算符的重载函数作为友元函数或普通函数

对于上面的重载函数来说,之所以返回值为ostream类型的引用,是为了能连续输出多个值


重载输入操作符>>

类内声明(函数类型为类内的友元函数):

friend istream& operator >>(istream& is,  Comlex& c)

注意:相较于重载输出操作符函数,重载输入操作符函数参数列表的自定义类引用(第二个参数)不是const静态引用的,这是因为这里面的第二个参数需要从键盘中获取不定的值的,这个值是动态的,不能使用const进行静态引用。

类外定义:

istream& operator >> (istream& is,  Comlex& c)
{is >> c. real >> c.imag;return is;
}

通过输出和输入重载函数的声明和定义,有以下几个点比较难理解,在此做出解释:🎯🎯

1.为什么输入输出符重载函数的形参列表需要两个不同类型的引用来传递参数?
首先要知道 要想输出或输入一个值 需要两个操作数 例如 [cout<<a] ,输出符的右值的类型为系统内置的值或用户自定义的值。而输出符的左值,则是属于ostream和istream类的对象cout或cin。这也就要求了我们如果想像原本使用输入输出操作符一样使用重载后,可以输出用户自定义类型的输入输出操作符的话,就需要将重载操作符的右值的类型改为系统内置的值或用户自定义的值

2.为什么输入输出符重载函数的返回类型需要是ostream或istream类型的引用?
由上可知,输出输入运算符的左值的类型,也就是调用输出操作符重载函数的类型,其属于ostream和istream类,声明返回类型为ostream或istream类后: 当[cout<<a]此时左值cout为ostream类,此时达成了调用重载函数的第一个条件,系统已经知道我们需要输出某个东西,至于需要输出的是C++内置类型还是用户定义的类型,需要由右边的参数决定。若此时 a 被输入为用户自定义的complex类型的参数,则重载函数的第二个调用条件达成。此时系统便会调用<<的重载函数,输出用户自定义的类型数据。

来自:
C++重载<<和>>(C++重载输出运算符和输入运算符)🔍
重载<<🔍


扩展内容 对运算符重载案例三的代码进行改进(运用输出操作符重载)

先看一段代码,这段代码为运算符重载项目的第三个案例(重载运算符完成分数有理数四则运算)的调用print方法打印结果的代码
在这里插入图片描述
由于上述代码中,main函数里边需要多次调用print方法才能实现分数打印,这样很麻烦,本项目将通过重载<<(插入器,左移操作符)操作符来实现print打印分数的功能。

为什么main函数里边需要多次调用print方法才能实现分数打印?
因为在这个例子中,ostream(输出流)文件库对新的Rationl类一无所知,所以不能直接用<<来输出我们的有理数

当然 , 我们无法在现有的 ostream 类中专门添加一个新的operator << ()的重载方法,所以我们只能定义一个正常的函数在外部重载这个操作符,这与重载方法的语法大同小异,唯一的区别是不再有一个对象可以用来调用<<重载函数,而不得不通过第一个输入参数向这个重载方法传递对象。

class Rational
{
public:Rational(int num, int denom);//num =分子 denom=分目Rational operator + (Rational& rhs); //rhs = right hand side =操作符右手边的参数Rational operator - (Rational& rhs);Rational operator * (Rational& rhs);Rational operator / (Rational& rhs);void print();
private:void normralize();//对分数进行简化处理int numerator;//分子int denominator;//分母friend ostream& operator<<(ostream& os, Rational f);//todo因为重载 "<<" 操作符的这个函数的类属于ostream类而不属于Rational类,这意味着它无权访问Rational类的私有成员 //为了能让它访问Rational类的私有成员 ,我们将其声明为Rational类的友元函数(即此函数不属于此类但有权访问此类的私有成员)};Rational::Rational(int num, int denom)
{numerator = num;denominator = denom;normralize();
}//todonormalize() 对分数的简化操作包括
//1.只允许分子为负数,如果分母为负数则把负数部分转移到分子部分如 -1/2 == -1/2 
//2.利用欧几里得算法(辗转求余原理)将分数进行简化 2/10	=>  1/5;
void Rational::normralize()
{//确保分母为正if (denominator < 0){numerator = -numerator;denominator = -denominator;}//欧几里得算法 abs-求绝对值的函数 需要包含stdlib.h头文件int a = abs(numerator);int b = abs(denominator);//不断求余数 最后得到最大公约数while (b > 0){int t = a % b;a = b;b = t;}numerator /= a;denominator /= a;
}//todo分数的+法
//a   c    a*d    c*b	  a*d+c*b
//- + - = ----- + ---- = -----------
//b   d    b*c	b*c	     b*c
Rational Rational::operator+(Rational& rhs)
{int a = numerator;int b = denominator;int c = rhs.numerator;int d = rhs.denominator;int e = a * b + c * d;int f = b * d;return Rational(e, f);
}//todo分数的-法
//a   c    a   -c
//- - - =  - +  -
//b   d    b	d
Rational Rational::operator-(Rational& rhs)
{rhs.numerator = -rhs.numerator;return operator+(rhs);
}//todo分数的*法
//a   c     a*c
//- * - =  -----
//b   d    b*d
Rational Rational::operator*(Rational& rhs)
{int a = numerator;int b = denominator;int c = rhs.numerator;int d = rhs.denominator;int e = a * c;int f = b * d;return Rational(e, f);
}
//todo分数的/法
//a    c       a   d
//-  /  -   =  - *  -
//b    d      b	    c
Rational Rational::operator/(Rational& rhs)
{int t = rhs.numerator;rhs.numerator = rhs.denominator;rhs.denominator = t;return operator *(rhs);
}void Rational::print()
{if (numerator % denominator == 0)cout << numerator / denominator;elsecout << numerator << "/" << denominator;
}ostream& operator<<(ostream& os, Rational f);//todo因为此函数不属于任何类 是一个独立的函数 所以我们需要对此函数在主函数前进行声明int main()
{Rational f1(2, 16);Rational f2(7, 8);//todo由于运算符重载中的第三个案例(分数有理数四则运算)中,main函数里边需要多次调用print方法才能实现分数打印//这样很麻烦,本项目将通过重载<<(插入器,左移操作符)操作符来实现print打印分数的功能。//todo因为在这个例子中,iostream库对新的Rationl类一无所知,所以不能直接用<<来输出我们的有理数//因为重载的含义本身就是用相同的名字去实现不同的功能:只要输入参数方面有差异存在就不会有问题//todo当然 , 我们无法在现有的 ostream 类中专门添加一个新的operator << ()的重载方法//所以我们只能定义一个正常的函数在外部重载这个操作符,这与重载方法的语法大同小异//todo唯一的区别是不再有一个对象可以用来调用<<重载函数,而不得不通过第一个输入参数向这个重载方法传递对象cout << f1 << "+" << f2 << "==" << (f1 + f2) << "\n";cout << f1 << "-" << f2 << "==" <<  (f1 - f2) << "\n";cout << f1 << "*" << f2 << "==" <<  (f1 * f2) << "\n";cout << f1 << "/" << f2 << "==" <<   (f1 / f2) << "\n";//todo 只要插入器<<右侧插入的数据为重载函数传入的Rational类参数(例如 f1 f2 ...) 便执行操作符<<的重载函数// (若右侧插入值不为重载函数传入的Rational类参数 例如 ”+“ ==“ ...) 便执行操作符<<原本的函数return 0;
}ostream& operator<<(ostream& os, Rational f)//todo在主函数下方 定义操作符重载函数
{os << f.numerator << "/" << f.denominator;return os;
}

运行结果:在这里插入图片描述


http://chatgpt.dhexx.cn/article/WcmfZgXC.shtml

相关文章

C++-操作符重载

定义&#xff1a; Salesitem.h /* * This file contains code from "C Primer, Fifth Edition", by Stanley B. * Lippman, Josee Lajoie, and Barbara E. Moo, and is covered under the * copyright and warranty notices given in that book: * * "Copyrig…

c++操作符重载

转自https://www.cnblogs.com/xudong-bupt/p/3557525.html 1.什么是操作符重载 可以使用分词将操作符重载理解为&#xff1a;操作符重载。 C中的操作符很多&#xff0c;如&#xff0c;-&#xff0c;*&#xff0c;\等等。 C中的重载也是C中面向对象多态的体现。 简单说操作符重…

操作符重载——C/C++学习笔记

此篇文章来自于网上&#xff0c;作为自己学习中的笔记&#xff0c;若有侵权行为&#xff0c;请告之&#xff0c;24小时之内必删除&#xff01;下面就转入正题吧&#xff01; 一、什么是操作符重载&#xff1f; 一看到重载&#xff0c;很容易就让人联想到成员函数重载&#xff0…

什么是操作符重载

一、什么是操作符重载 操作符重载可以分为两部分&#xff1a;“操作符”和“重载”。说到重载想必都不陌生了吧&#xff0c;这是一种编译时多态&#xff0c;重载实际上可以分为函数重载和操作符重载。运算符重载和函数重载的不同之处在于操作符重载重载的一定是操作符。我们不妨…

前端知识点总结 Vue JS CSS

前端知识点 MVVM和MVC的区别什么是Vue生命周期钩子函数触发顺序 VueVue优点父子通信&#xff0c;兄弟通信指令V-if和V-show区别 Vue-loaderVue-key的作用v-modalVue data必须是函数的问题Vue slot Vue-router多个router-viewroute与router的区别导航守卫懒加载 ES6JavaScript同…

JavaScript进阶之手写Promise

前言 Promise异步编程的一种解决方案&#xff0c;比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现&#xff0c;ES6 将其写进了语言标准&#xff0c;统一了用法&#xff0c;原生提供了Promise对象。这里手写一次&#xff0c;希望能和大家一起彻底…

【数据库】MongoDB数据库详解

目录 一&#xff0c;数据库管理系统 1&#xff0c; 什么是数据库 2&#xff0c;什么是数据库管理系统 二&#xff0c; NoSQL 是什么 1&#xff0c;NoSQL 简介 2&#xff0c;NoSQL数据库 3&#xff0c;NoSQL 与 RDBMS 对比 三&#xff0c;MongoDB简介 1&#xff0c; MongoDB 是什…

面试06,[长亮科技]()(offer)、[荔枝]()FM(在确定部门和薪资)、[涂鸦智能]()(第一轮电话面半小时,待后续)、华资软件(HR面)、[广州速游]()(已挂)。至于公司怎么样不加以言论。

作者&#xff1a;Carson-Zhao 链接&#xff1a;https://ac.nowcoder.com/discuss/522002?type2&order0&pos16&page1&ncTraceId&channel-1&source_iddiscuss_tag_nctrack 来源&#xff1a;牛客网 总结一下这几天的面试吧&#xff01;从19号到现在23号…

SQL Foundation(1--13)

1&#xff1a;关系数据库的由来&#xff1a; IBM的工程师Dr E F codd 的关系型数据库模型发表于1970 论文名称&#xff1a; A relational Model of data for Large Shared Data Bank (这个在wiki 和google上可以搜到) SQL: Structured query language&#xff1a; oracle官方…

一、快速入门 MongoDB 数据库

文章目录 一、NoSQL 是什么1.1 NoSQL 简史1.2 NoSQL 的种类及其特性1.3 NoSQL 特点1.4 NoSQL 的优缺点1.5 NoSQL 与 SQL 数据库的比较 二、MongoDB 基础知识2.1 MongoDB 是什么2.2 MongoDB 的体系结构2.3 MongoDB 的特点2.4 MongoDB 键特性2.5 MongoDB 的核心服务和工具2.6 Mon…

数据库总结(考研复试和期末复习皆可用)

数据库总结 点击下载该文档 密码&#xff1a;cqoq 本人自制了简答题的速记卡片 地址&#xff0c;大家可以参考使用。[下载Markji App 使用] 第一章 绪论 1.1 数据库系统概述 数据库管理系统(DBMS)的功能: 数据定义功能数据组织、存储和管理数据库操纵功能数据库的事务和运行…

一步步教你轻松学KNN模型算法

一步步教你轻松学KNN模型算法 ( 白宁超 2018年7月24日08:52:16 ) 导读&#xff1a;机器学习算法中KNN属于比较简单的典型算法&#xff0c;既可以做聚类又可以做分类使用。本文通过一个模拟的实际案例进行讲解。整个流程包括&#xff1a;采集数据、数据格式化处理、数据分析、数…

Oracle实战详解

Oracle实战详解 1.oracle介绍 ORACLE数据库系统是美国ORACLE公司&#xff08;甲骨文&#xff09;提供的以分布式数据库为核心的一组软件产品&#xff0c;是目前最流行的客户/服务器(CLIENT/SERVER)或B/S体系结构的数据库之一。比如SilverStream就是基于数据库的一种中间件。ORA…

数据库|SQL / MySQL的基本理论用法

本文从数据库MySQL的数据类型、关系模型、增删改查语句、管理MySQL、实用SQL语句、事务等方面进行介绍。 数据类型 对于一个关系表&#xff0c;除了定义每一列的名称外&#xff0c;还需要定义每一列的数据类型。关系数据库支持的标准数据类型包括数值、字符串、时间等&#xf…

KNN模型算法研究与案例分析

KNN模型算法研究与案例分析( 白宁超 2018年8月29日15:39:13 ) 导读&#xff1a;机器学习算法中KNN属于比较简单的典型算法&#xff0c;既可以做聚类又可以做分类使用。本文通过一个模拟的实际案例进行讲解。整个流程包括&#xff1a;采集数据、数据格式化处理、数据分析、数据归…

SQL总结

目录 简介 在Android中存储数据有时会用到数据库&#xff0c;Android给我们提供了 一系列的API来操作数据库&#xff0c;非常简单&#xff0c;我们只需要输入对应的SQL语句&#xff0c;甚至不懂SQL语句&#xff0c;只传入对应的参数即可使用。还有一些第三方库&#xff0c;如G…

Windows开机启动项设置详解

一、开机启动原理 Windows系统都有一个“启动”文件夹&#xff0c;把需要打开的程序的快捷方式或脚本放到“启动”文件夹里&#xff0c;就可以实现开机自启动。 启动”文件夹分为两种&#xff1a;“系统启动文件夹”和“用户启动文件夹”。 系统启动文件夹 Win10系统“启动”…

「C#」设置开机启动

自己写了个监控键盘按键的小程序。 在界面上实时显示按下的键&#xff0c;但是想实现程序的开机自启如何实现呢。 开机自启动一种是在windows的“C:\Users\用户名\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup”家快捷方式。但是这种方法自测有时候不成功。…

centos7 设置开机启动项

高端的废话就是没有引言这种废话。 1.这里我已我的centos7为例输入&#xff1a; systemctl list-unit-files #查看开机启动表如图下&#xff1a; 最左边就是服务 &#xff0c;最右边就是状态 。如当你想要服务器开机启动firewalld&#xff08;防火墙&#xff09;输入 system…

计算机怎么管理自启,电脑如何设置开机启动项

大家都知道将程序添加到开机启动项进入系统就可以自动打开了&#xff0c;但是有些流氓软件会强制进入开机启动项&#xff0c;这就导致电脑开机速度变慢&#xff0c;内存占用过多&#xff0c;运行卡顿的问题。下面&#xff0c;我就教大家如何设置开机启动 现在几乎家家户户都配备…