c++征途 --- 类和对象 --- 运算符重载

对运算符进行重定义,使其适应不同的数据类型 

目录

第一部分 --- 加号运算符重载

第二部分 --- 左移运算符重载

补充知识点:关于cout和cin

第三部分 --- 递增运算符重载

第四部分 --- 赋值运算符重载

第五部分 --- 关系运算符重载

第六部分 --- 函数调用运算符重载


第一部分 --- 加号运算符重载

 

 编译器中已有的加号运算符只能够实现编译器中内置的数据吗类型的相加,而不能够实现自定义类型数据的相加功能(后面的运算符同理)

对于编译器中内置的数据类型,编译器知道如何运算

为了解决这个问题,人们引入了运算符重载技术

1.运算符重载的本质就是 -- 新建一个运算符函数来给现有运算符添加新的运算功能

2.operator+ --- 当我们创建一个加法运算符函数的时候(为加法运算符增添功能,且不会影响加法运算符原有的功能)、

我们需要起一个函数名,为了统一操作,编译器给了一个统一的函数名,即这个 operator+

3.这个加法运算符函数函数有两种存在形式 --- 成员函数形式和全局函数形式

4.写了这个加法运算符函数之后,加法运算符的功能就得到了增加(函数内增加了什么功能,它就增加了什么功能) 

5.当我们用成员函数的形式调用加法运算函数的时候

 相加的两个对象分别是调用成员函数的对象和传给成员函数的参数

6.当我们用全局函数的形式调用加法运算符函数时

相加的两个对象分别是函数中的两个形参 

调用函数是本质,直接用加法运算符是简化

7.

 运算符重载时也可以实现函数重载

1.所有的加法运算符的出现实际上是在调用函数,函数的参数是加法运算符左右两边的数据,函数的函数名是 operator+,且加法运算符的函数调用是根据函数重载原则来调用的

2.比如说两个int型数据相加,就有一个专门的 operator+函数来实现,两个double型,一个int一个double的相加?都是由专门的operator+函数来实现的

3.编译器中内置了一些operator+函数供我们使用,但是内置的终究是有限的,当出现自定义的数据类型相加的时候,由于函数内没有内置对应的operator+函数来处理这种情况,所以出现这种代码的时候编译器就会报错

4.为了解决这种错误满足我们的需求,c++允许我们自己去添加operator+函数(加法运算符函数  --- 拓展来说就是编译器允许我们自己添加运算符函数)

5.当我们添加了对应的operator+函数之后,编译器就能够处理本来无法处理的相加情况了

6.有这么多的同函数名的operator+函数,当我们使用加法运算符调用operator+函数的时候,编译器如何知道是调用哪一个函数呢? ---  答案就是我们之前学的函数重载技术 --- 在同一个函数名的情况,同一个函数作用域的情况下,编译器可以根据函数括号内的形参数量,类型,顺序的差别来分辨调用哪一个函数

7.根据第6点我们可以知道,使用加法运算符的时候,其左右两边的数据的类型是什么是很重要的因为这决定了我们调用哪一个operator+函数(其它运算符同理)


第二部分 --- 左移运算符重载

1.左移运算符是这个: <<

2.左移运算符的作用:输出数据类型

3.如果没有进行左移运算符运算符重载的话,左移运算符只能够输出编译器重内置的数据类型,而不能够输出我们的自定义类型

4.如果想输出自定义类型的话就需要我们进行左移运算符重载 --- 也就是给左移运算符添加函数

5.左移运算符重载的方式和加法运算符重载一样:有成员函数重载和全局函数重载两种方式,但是我们通常不会利用成员函数来重载左移运算符,因为我们无法实现cout在左侧,输出对象在右侧

6.左移运算符函数的函数名是 : operator<< 

7.对第6点的拓展:运算符函数的函数名格式是: operator运算符 


补充知识点:关于cout和cin

istream和ostream都是类,一个是标准输入流类,另一个是标准输出流类

而cin和cout则分别是在对应类下创建的对象!

而且cout和cin这两个对象全局只能有一个,如果要把它们作为参数传递的话只能够用引用的方式(取别名)传递


1.左移运算符函数只能用全局函数的方式实现,然后函数中必须有两个参数:其中第一个参数必须是属于ostream流的cout(由于cout全局只能有一个,所以这里的形参创建采用引用(取别名)的方式),另一个则是我们要输出的数据类型

 上面这个函数仍然是有缺陷的 --- 缺陷的地方在于函数返回值

上面这个函数的返回值是void,所以在调用一次左移运算符,执行函数,然后函数返回空

如果再再这个结果后面加一个左移运算符再一次调用的话,就会导致函数出错,因为运算符左边的值(返回值)为空,无法调用函数

1.如果想要实现左移运算符的链式调用的话,我们就必须保证左移运算符左边是cout对象 --- 也就是说调用完左移运算符函数之后,函数的返回值必须是ostream类的cout对象 --- 且是以引用的方式返回 --- 因为cout对象全局只能有一个,如果用值返回的话相当于又创建了一个cout对象,违反了规则,所以我们只能用引用(取别名)返回的方式

2.不要忘了友元技术可以让我们从类外访问具有私有权限的成员属性哦


第三部分 --- 递增运算符重载

1.递增运算符:++

2.作用:通过重载递增运算符,实现自己的整型数据

3.递增运算符一次只能够处理一个数据,然后这个数据可以在递增运算符的左边和右边

4.当数据在递增运算符的左边时,先调用数据,然后再进行++;在右边的时候,先++,再调用数据

5.递增运算符函数的函数名:operator++() --- 前置++直接用这个就行,而后置++如果也用这个的话就会导致函数重复了,所以编译器规定,再函数括号内加上一个int后就表示这是一个后置递增运算符函数:operator++(int) --- int代表占位参数,可以用来区分前置和后置递增

(ps:函数的占位参数无法在函数内部调用,但是在调用函数的时候,必须给占位参数填上对应的数值,不然的话就会导致函数调用失败)

6递增运算符和其它的运算符一样,如果没有进行重载的话,只能够处理编译器中内置的数据类型,而不能处理我们自定义的数据类型 --- 原因是没有没有处理自定义类型的运算符函数

7.递增(++)运算符分为两种,一种是前置递增++,一种是后置递增++,所以递增运算符函数应该有两种实现方式

8.前置运算符实现如下:(成员函数形式)

要点分析: 

一.由于是成员函数,所以说访问成员变量的时候不需要传参,直接访问就可以了

二.对数据++操作完之后,我们还要将数据返回(上面这个函数中的被++的数据是一个对象,所以我们要返回的就是对象本身,用this指针就可以实现了)

三.函数的返回类型必须用引用返回而不是值返回,原因是我们必须保证“可以对同一个数据多次++”的需求的实现。如果用值返回的话,我们相当于创建了一个新的但一样的数据返回回去了,对这个数据再一次++的话只能对新数据造成影响,对于旧数据是没有作用的。

所以说我们需要采用引用(取别名)的方式,保证我们一直指向同一个内存空间

9.后置运算符实现如下:

一.用int的就是后置递增函数,没有的是前置递增函数

二.后置递增函数的实现:先用临时数据保存当前值,然后再对当前值++,然后再返回保存有原始值的临时数据

三.注意后置递增函数的返回形式是值返回,为社么呢?

因为我们的函数的返回值我们在函数中创建的临时变量,如果用引用的话,我们返回的是一个别名,然后这个别名指向的内存空间是临时变量的内存空间,可是问题是函数中创建的临时变量的内存空间会在函数结束之后被释放,此时的别名指向的内存空间是不存在的,这种指向是非法的!

所以我们采用值返回的形式

四.一个数据的后置++只能用一次,不然会导致程序判断出错

综上:前置++返回类型是引用(对于一个数据可连续使用),后置++返回类型是值(对于一个数据只能够用一次)


第四部分 --- 赋值运算符重载

1.赋值运算符: = 

1.堆区数据由程序员开辟也由程序员释放

2.如果在类中开辟了堆区,则这个堆区的释放要放在析构函数中进行,释放关键字(delete)

1.如果没有对赋值运算符进行运算符重载 ,直接将对象B赋值给对象A的话 --- A = B(A和B是属于同一个类的对象),编译器会直接将B的成员变量的值直接赋给A对象中对应的成员变量

 1.想要实现深拷贝有两种方式:

第一种是修改成员函数,使其由直接的值赋予变为先创建自己的堆区,然后将值传给堆区,然后再用自己的指针维护这个新堆区的地址

第二种则是对赋值运算符进行运算符重载,即创建一个具有在进行对象赋值的时候 --- 如果有上面这种堆区情况出现的话,被赋值的一方会自己创建一个堆区,且赋值方的数据会被赋值到这个堆区中的赋值运算符函数

2.编译器提供的赋值运算符都是浅拷贝(即简单的值拷贝,无关内存 --- 有关内存的话就是深拷贝了)

 1.出现连续赋值的时候,赋值的顺序是从右往左赋值

2.我们实现的赋值运算符函数需要能够进行连续赋值,所以我们的赋值运算符函数必须具有返回值,且返回的是被赋值的变量(A = B,A就是被赋值的那个,B就是赋值的那个)

 

 1.上面赋值运算符重载函数是成员函数形式,且实现的是对象之间的赋值

2.这个赋值运算符重载函数必须实现连续赋值的功能,所以要有返回值,且返回值是被赋值的对象

3.如果赋值对象中有堆区情况的话,我们需要先判断被赋值对象是否有堆区,如果有的话先释放掉这个堆区。释放完之后再创建一个新的堆区,然后将赋值对象堆区中的数据传给这个新的堆区,这样子我们就完成了任务

4.返回方式要用引用返回,因为我们要返回的是这个被返回对象本身,而不是返回它的拷贝


第五部分 --- 关系运算符重载

1.关系运算符:> ,< , != , == ,>= , <= 这些进行对比的运算操作符

2.未进行运算符重载的关系运算符能够比较的也只有编译器中内置的数据类型

上图是一个关系运算符重载的例子 --- ==关系运算符

1.这个==运算符函数的返回类型是bool类型(通过布尔类型创建的量只有两个 -- true(真)和false(假))

2.!=运算符,> , < , >= , <=...运算符的运算符重载方式和上面这个==运算符一致


第六部分 --- 函数调用运算符重载

1.函数调用运算符是: ()

2.函数调用运算符的作用是调用函数,其能够处理的是位于其左边的函数名,如果左边不是函数名的话,由于没有对应的函数调用运算符函数来进行处理,就会导致出错

3.

 一.上面这个重载调用运算符函数是在类中实现的,是MyPrint类中的成员函数

4.由3可知,当函数调用运算符函数为类中的成员函数的时候,我们调用函数调用运算符的方式可多一个形式:通过运算符所在的类创建的对象的对象名(函数调用运算符函数的参数)

5.函数调用运算符函数的实现方式是类中的成员函数,其调用方式是对象名 + (函数调用运算符函数的参数)

由于其调用方式与函数的调用方式相似,所以这种对象名 + (函数调用运算符函数的参数)的形式又被称为仿函数

6.补充知识点:匿名函数对象

1. 匿名函数对象的第一部分是:匿名对象 --- 没有对象名的对象

匿名对象创建方式: 类名()

匿名对象的特点:匿名对象被使用一次之后就会被释放

2.匿名函数对象的第二部分是:仿函数

上面这一部分 MyAdd()(100 , 100)中的第二个括号就是在进行仿函数调用

首先创建一个匿名对象,然后再使用这个匿名对象一次来进行仿函数调用

综上,将两部分结合起来就是匿名函数对象

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值