C++面向对象编程之六:重载操作符(<<,>>,+,+=,==,!=,=)

news2024/11/27 8:27:09

重载操作符

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_num
    return 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;
}

好了,关于重载操作符,篇幅比较长,本文对重载操作符(<<,>>,+,+=,==,!=,=)进行了讲解,还有一些比较重要的操作符,比如前++,后++,前--,后--,下标操作符[]等,还没有介绍,就放到下一篇博文吧!

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

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

相关文章

Java 最小路径和

最小路径和中等给定一个包含非负整数的 m x n 网格 grid &#xff0c;请找出一条从左上角到右下角的路径&#xff0c;使得路径上的数字总和为最小。说明&#xff1a;每次只能向下或者向右移动一步。示例 1&#xff1a;输入&#xff1a;grid [[1,3,1],[1,5,1],[4,2,1]]输出&…

求“二维随机变量的期望E(X)与方差D(X)”例题(一)

离散型 设随机变量(X,Y)的联合分布律为 X\Y0100.10.210.30.4 (1)求E(X) 先求x的边缘分布律&#xff0c;表格里x0的概率为0.10.2&#xff0c;于是我们可得 X01P0.30.7直接求E(X)即可&#xff0c;得到结果 (2)求E(XY) 直接x与y相乘就行。 记得别乘多了&#xff0c;别的算了又…

代码随想录算法训练营day55 | 动态规划 583. 两个字符串的删除操作 72. 编辑距离

day55583. 两个字符串的删除操作1.确定dp数组&#xff08;dp table&#xff09;以及下标的含义2.确定递推公式3.dp数组如何初始化4.确定遍历顺序5.举例推导dp数组72. 编辑距离1. 确定dp数组&#xff08;dp table&#xff09;以及下标的含义2. 确定递推公式3. dp数组如何初始化4…

idea热部署

Dea 热部署方式汇总&#xff1a;一、开启自动编译修改file->settings->compiler->build project automatically二、开启允许在运行中修改文件按ctrlshifta&#xff0c;搜索Registry双击进去&#xff0c;点击面板搜索running&#xff0c;勾选下面的值&#xff1a;Eclip…

MySQL索引结构分类及其优劣选择详解

什么是索引&#xff1f; 索引&#xff08;index&#xff09;是帮助MySQL高效获取数据的数据结构&#xff08;有序&#xff09;。在数据库系统中&#xff0c;除了存储数据之外&#xff0c;还维护着满足特定查找算法的数据结构&#xff0c;这些数据结构以某种方式引用&#xff0…

Nacos集群设置开机自启动

一、搭建前提需要的环境 -rw-rw-rw-. 1 root root 8491533 Mar 5 20:05 apache-maven-3.3.9-bin.tar.gz -rw-rw-rw-. 1 root root 189815615 Mar 23 2018 jdk-8u162-linux-x64.tar.gz -rw-r--r--. 1 root root 25548 Apr 7 2017 mysql57-community-release-el7-10.n…

HTML、CSS学习笔记7(移动适配:rem、less)

一、移动适配 rem&#xff1a;目前多数企业在用的解决方案vw / vh&#xff1a;未来的解决方案 1.rem&#xff08;单位&#xff09; 1.1使用rem单位设置尺寸 px单位或百分比布局可以实现吗&#xff1f; ————不可以 网页的根字号——HTML标签 1.2.rem移动适配 写法&#x…

双碳目标下基于“遥感+”融合技术在碳储量、碳收支、碳循环等多领域监测与模拟

目录 专题一、双碳视角下遥感技术的研究方向 专题二、生态系统碳库的遥感估算—以森林碳储量为例 专题三、生态系统碳收支的遥感模拟—以京津冀地区为例 专题四、区域能源消耗碳排放空间格局模拟—基于夜间灯光数据 专题五、土地利用变化碳排放效应的遥感监测—以城市扩张…

PLSQL Developer 安装指南

PLSQL Developer 是 Oracle 的客户端。 下面以64位破解版的PLSQL Developer为例&#xff0c;进行PLSQL Developer 安装讲解。 0. 下载 PLSQL Developer https://download.csdn.net/download/Shipley_Leo/87557938 1. 根据操作系统选择对应“plsqldev.exe”可执行文件&#xff…

虚拟平台中的“有意”/“无意”故障注入

构建虚拟平台的重点往往会放在系统运行&#xff0c;尤其是在汽车和航空航天的数字孪生领域。只有确保虚拟平台能够正常运行系统&#xff0c;软件才得以可靠地在虚拟平台上运行。 为确保系统正常运行&#xff0c;通常需要解决如何测试异常、故障和其他问题。该过程通常被称为“…

【Linux】vi和vim编辑器

目录主题主题 三种常见模式&#xff1a; 正常模式 以vim 打开一个档案就直接进入一般模式了(这是默认的模式)。在这个模式中&#xff0c;你可以使用[上下左右]按键来移动光标&#xff0c;你可以使用『删除字符』或『删除整行』来处理档案内容&#xff0c;也可以使用「复制、…

监控开源方案

监控开源方案可观测性指标监控日志监控链路追踪开源产品ZabbixOpen-FalconPrometheusNightingale监控需求 : 系统出现问题 , 能及时感知了解数据趋势&#xff0c;预测未来可能出问题了解系统的水位&#xff0c;为服务扩缩容提供数据支撑感知待优化点&#xff0c;如 : 中间件参…

【Unity VR开发】结合VRTK4.0:创建一个按钮(Togglr Button)

语录&#xff1a; 有人感激过你的善良吗&#xff0c;貌似他们只会得寸进尺。 前言&#xff1a; Toggle按钮是提供简单空间 UI 选项的另一种方式&#xff0c;在该选项中&#xff0c;按钮将保持其状态&#xff0c;直到再次单击它。这允许按钮处于激活状态或停用状态的情况&#…

固态存储设备固件升级方案

1. 前言 随着数字化时代的发展&#xff0c;数字数据的量越来越大&#xff0c;相应的数据存储的需求也越来越大&#xff0c;存储设备产业也是蓬勃发展。存储设备产业中&#xff0c;发展最为迅猛的则是固态存储(Solid State Storage&#xff0c;SSS)。数字化时代&#xff0c;海量…

【ESP32+freeRTOS学习笔记之“ESP32环境下使用freeRTOS的特性分析(新的开篇)”】

目录【ESP32freeRTOS学习笔记】系列新的开篇ESP-IDF对FreeRTOS的适配ESP-IDF环境中使用FreeRTOS的差异性简介关于FreeRTOS的配置关于ESP-IDF FreeRTOS Applications结语【ESP32freeRTOS学习笔记】系列新的开篇 ESP-IDF对FreeRTOS的适配 FreeRTOS是一个可以适用于多个不同MCU开…

JavaWeb开发(三)3.6——代理模式

一、代理模式概述 1.1、代理模式的理解 参考人家的举例&#xff0c;感觉挺形象&#xff0c;容易理解&#xff1a; 就拿明星与经纪人的关系来说&#xff0c;明星就好比被代理类&#xff0c;明星只需要负责唱歌&#xff0c;表演或给粉丝签名等事务&#xff0c;而类似签合同&…

Jenkins从配置到实践

Jenkins从配置到实践 1 持续集成 Continuous integration&#xff08;CI&#xff09; 1.1 什么是持续集成&#xff1f; 持续集成Continuous integration&#xff08;CI&#xff09;是一种软件开发实践&#xff0c;即团队开发成员经常集成他们的工作&#xff0c;通常每个成员…

COLMAP

简介&#xff1a;在使用instant-ngp过程中需要使用COLMAP得到模型的必要输入&#xff0c;比如模型需要的相机外参我们就可以通过COLMAP中的sparse reconstruction稀疏重建得到&#xff1b;而对于depth map深度图我们则需要dense reconstruction稠密重建得到&#xff0c;下面我们…

STM32Cube STM32MP157 M4端CAN通讯实战

1、环境 开发系列&#xff1a;STM32MP157 开发软件&#xff1a;STM32CubeIDE 1.4.0 例程目的&#xff1a;在M4端实现CAN通讯 2、目的 近日&#xff0c;有客户需要在STM32MP157中的M4端实现CAN通讯&#xff0c;我也是初次在M4端编写CAN通讯代码&#xff0c;上网研究了其他人写…

从LeNet到ResNet:深入探索卷积神经网络

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️&#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…