世界球精选!C++ | 运算符重载
函数重载(函数多态)是指用户能够定义多个名称相同但参数列表不同的函数,旨在使用户能够用同名的函数来完成相同的基本操作,即使这种操作被用于不同的数据类型。
运算符重载将重载的概念扩展到运算符上,允许赋予C++运算符多种含义。
实际上,很多C++(也包括C语言)运算符已经被重载。例如,将*运算符用于地址,将得到存储在这个地址中的值;但用于两个数字时,得到的是它们的乘积。
【资料图】
C++允许运算符重载扩展到用户自定义的类型,例如,允许使用+将两个对象相加。编译器将根据操作数的数目和类型决定使用哪种假发定义。重载运算符可使代码看起来更自然。例如,将两个数组相加通常使用for循环实现:
for(int i=0; i< 10; i++){ A[i] = B[i] + C[i];}
但在C++中,可以定义一个表示数组的类,并重载+运算符。于是便有如下语句:
A = B + C;
1.2 运算符重载函数格式要重载运算符,需使用被称为运算符函数的特殊函数形式。运算符函数的格式如下:
operatorOP(param1, param2, ...)
例如,operator+()
表示重载+
运算符,operator*()
表示重载*
运算符。OP
必须是有效的C++运算符,不能虚构一个新的符号,例如不能有operator@()
这样的函数,因为C++中没有@
运算符。
+
time.h
#ifndef TIME_H#define TIME_Hclass Time{private:int hours;int minutes;public:Time();Time(int h, int m = 0); // 注意:此处不能h和m都设置默认值,否则将和Time()冲突void addMin(int m);void addHour(int h);void resetTime(int h = 0, int m = 0);Time operator+(const Time &t) const; // 运算符重载void show() const;};#endif
如果Time(int h = 0, int m = 0);均设置默认值,使用时创建对象Time t;编译器将不知道是该调用默认构造函数还是带参构造函数(因为带参构造两个参数均有默认值,均可以省略)。
头文件管理:
在同一个文件中只能将同一个头文件包含一次。记住这个规则很容易,但很可能在不知情的情况下将头文件包含多次。例如,可能使用包含了另外一个头文件的头文件。有一种标准的C/C++技术可以避免多次包含同一个头文件。它是基于预处理器编译指令#ifndef(即if not defined)
的。下面的代码片段意味着仅当以前没有使用预处理器编译指令#define定义名称TIME_H时,才处理#ifndef
和#endif
之间的语句:
#ifndef TIME_H...#endif
编译器是首次遇到该文件时,名称TIME_H
没有定义(我们一般根据include文件名来选择名称,并加上一些下划线,以创建一个在其他地方不太可能被定义的名称)。
time.cpp
#include #include "time.h"Time::Time(){hours = minutes = 0;}Time::Time(int h, int m){hours = h;minutes = m;}void Time::addMin(int m){minutes += m;hours += minutes / 60;minutes %= 60;}void Time::addHour(int h){hours += h;}void Time::resetTime(int h, int m){hours = h;minutes = m;}Time Time::operator+(const Time &t) const{Time sum;sum.minutes = minutes + t.minutes;sum.hours = hours + t.hours + sum.minutes/60;sum.minutes %= 60;return sum;}void Time::show() const{std::cout << hours << "小时 " << minutes << "分钟" << std::endl;}
usetime.ccpp
#include#include"time.h"int main(){using std::cout;using std::endl;Time time1(2, 45);time1.show();Time time2(1, 56);time2.show();Time time3 = time1 + time2;time3.show();Time time4 = time1.operator+(time2);time4.show();Time time5 = time1 + time2 + time3;time5.show();return 0;}
输出:
2小时 45分钟1小时 56分钟4小时 41分钟4小时 41分钟9小时 22分钟
2. 运算符重载注意事项
Time time5 = time1 + time2 + time3;
是如何被转换为函数调用的:由于
+
是从左向右结合,因此上述语句首先被转换为:
Time time5 = time1.operator+(time2 + time3);
然后,函数参数本身被转换为一个函数调用,结果为:
Time time5 = time1.opearator+(time2.operator+(time3));
。
重载后的运算符必须至少有一个操作数是用户自定义的类型,这将防止用户为标准类型重载运算符。
使用运算符时不能违反运算符原来的句法规则。例如,不能将求模运算符(%)重载成使用一个操作数。同样,不能修改运算符的优先级。因此,如果将加号运算符重载成将两个类相加,则新的运算符与原来的加号具有相同的优先级。
不能创建新的运算符。例如,不能定义operator**()函数来表示求幂。
不能重载下面的运算符:
sizeof
:sizeof运算.
:成员运算符.*
:成员指针运算符::
:作用域解析运算符?:
:条件运算符typeid
:一个RTTI运算符const_cast
:强制类型转换运算符dynamic_cast
:强制类型转换运算符reinterpret_cast
:强制类型转换运算符static_cast
:强制类型转换运算符下表中的大多数运算符都可以通过成员或非成员函数进行重载,但下面的运算符值能通过成员函数进行重载
。
=
:赋值运算符()
:函数调用运算符[]
:下标运算符->
:通过指针访问类成员的运算符3. 运算符重载与友元:重载运算符<<
3.1 <<
的第一种重载版本要使Time类知道使用cout,必须使用友元函数。这是因为下面这样的语句使用两个对象,其中一个时ostream类对象(cout):
cout << time;
如果使用一个Time成员函数来重载<<,Time对象将是第一个操作数,这就意味着必须这样使用<<:
time << cout;
而这样会令人迷惑,但通过友元函数,可以像下面这样重载运算符:
void operator<<(ostream &os, const Time &t){ os << t.hours << t.minutes;}
time.h
#ifndef TIME_H#define TIME_H#includeclass Time{private:int hours;int minutes;public:Time();Time(int h, int m = 0);void addMin(int m);void addHour(int h);void resetTime(int h = 0, int m = 0);Time operator+(const Time &t) const;// void show() const; 去除show方法,转而由<<重载函数替代friend void operator<<(std::ostream &os, const Time &t); // 友元函数对<<进行重载};#endif
time.cpp
#include #include "time.h"Time::Time(){hours = minutes = 0;}Time::Time(int h, int m){hours = h;minutes = m;}void Time::addMin(int m){minutes += m;hours += minutes / 60;minutes %= 60;}void Time::addHour(int h){hours += h;}void Time::resetTime(int h, int m){hours = h;minutes = m;}Time Time::operator+(const Time &t) const{Time sum;sum.minutes = minutes + t.minutes;sum.hours = hours + t.hours + sum.minutes/60;sum.minutes %= 60;return sum;}void operator<<(std::ostream &os, const Time &t){os << t.hours << "小时 " << t.minutes << "分钟";}
usetime.cpp
#include#include"time.h"int main(){using std::cout;using std::endl;Time time1(2, 45);cout << time1;return 0;}
输出:
3.2<<
的第二种重载版本<<的第一种重载版本虽然成功使得cout << time;
正常工作,但是不支持连续输出:
cout << “时间为:” << time << endl;
而这是由于在iostream定义中,<<
运算符要求左边必须是一个ostream对象,由于cout是ostream对象,所以重载后cout << time;
满足这种要求。但是重载之后,cout << x << y;
中cout<
因此,第二种重载版本便是将重载函数operator<<()的返回值设置成ostream对象的引用即可:
ostream& operator<<(ostream &os, const Time &t){ os << t.hours << t.minutes; return os;}
注意:返回类型是ostream&,而不是ostream。这是因为若返回类型是ostream,将调用ostream类的拷贝构造函数,但是ostream的拷贝构造函数是private权限。而返回类型是ostream&,意味着函数的返回值就是传递给该函数的对象。
time.h
#ifndef TIME_H#define TIME_H#includeclass Time{private:int hours;int minutes;public:Time();Time(int h, int m = 0);void addMin(int m);void addHour(int h);void resetTime(int h = 0, int m = 0);Time operator+(const Time &t) const;friend std::ostream& operator<<(std::ostream &os, const Time &t); // <<的第二种重载版本};#endif
time.cpp
#include #include "time.h"Time::Time(){hours = minutes = 0;}Time::Time(int h, int m){hours = h;minutes = m;}void Time::addMin(int m){minutes += m;hours += minutes / 60;minutes %= 60;}void Time::addHour(int h){hours += h;}void Time::resetTime(int h, int m){hours = h;minutes = m;}Time Time::operator+(const Time &t) const{Time sum;sum.minutes = minutes + t.minutes;sum.hours = hours + t.hours + sum.minutes/60;sum.minutes %= 60;return sum;}std::ostream& operator<<(std::ostream &os, const Time &t){os << t.hours << "小时 " << t.minutes << "分钟";return os;}
usetime.cpp
#include#include"time.h"int main(){using std::cout;using std::endl;Time time1(2, 45);cout << "时间为:" << time1 << endl;return 0;}
输出:
时间为:2小时 45分钟
4. 重载运算符=
4.1 编译器默认给类提供了一个默认的赋值运算符重载函数默认的赋值运算符重载函数进行了简单的赋值操作。
demo
#include #include using namespace std;class Person{friend void Display(Person &p){ cout << "id:" << p.id << " name:" << p.name << endl; }private: int id; string name;public: Person(int id, string name){ this->id = id; this->name = name; }};int main(){ Person p1(1, "leo"); Person p2(2, "mike"); Display(p1); Display(p2); p1 = p2; Display(p1); Display(p2); return 0;}
输出:
id:1 name:leoid:2 name:mikeid:2 name:mikeid:2 name:mike
4.2 当类含有成员指针时,默认的赋值运算符重载函数将出现异常#include #include using namespace std;class Person{friend void Display(Person &p){ cout << "id:" << p.id << " name:" << p.name << endl; }private: int id; char *name;public: Person(int id, const char *name){ this->id = id; this->name = new char[strlen(name)+1]; strcpy(this->name, name); } ~Person(){ if(this->name != NULL){ delete[] this->name; this->name = NULL; } }};void test(){ Person p1(1, "leo"); Person p2(2, "mike"); Display(p1); Display(p2); p1 = p2; Display(p1); Display(p2);}int main(){ test(); return 0;}
输出(报错):
id:1 name:leoid:2 name:mikeid:2 name:mikeid:2 name:mikefree(): double free detected in tcache 2
注意,由于未给Person重载赋值运算符,将调用编译器默认提供的赋值运算符重载函数。但是默认的赋值运算符重载函数只进行了简单的赋值操作,即浅拷贝。浅拷贝示意图如下。
所以,当类含有成员指针时,需要为其重载赋值运算符函数,代码如下:
// 重载赋值运算符操作Person& operator=(const Person &p){ if(this->name != NULL){ delete[] name; name = NULL; } this->name = new char[strlen(p.name) + 1]; strcpy(this->name, p.name); this->id = p.id; return *this;}
Q1:为什么需要先判断this->name是否为空?由于调用赋值运算符重载函数时,此时当前对象(*this)已经创建完毕,那么就有可能this->name指向堆内存。这个时候如果不对其name指针进行判断而直接进行赋值,会导致this原指向内存得不到及时释放。
Q2:为什么返回引用类型*this?为了实现连续赋值。如p1 = p2 = p3
,意义为p3赋值给p2,然后p2赋值给p1。如果Person的赋值重载函数返回的是Person而不是Person&,那么p2 = p3
这个表达式将会产生一个新的匿名对象,然后将这个匿名对象赋予p1。
C++规定为了实现连续赋值,赋值操作符必须返回一个引用指向操作符的左侧实参。这是为class实现赋值操作符必须遵循的协议。这个协议不仅适用于标准的赋值形式,也适用于+=、-=、*=等。
void test(){ Person p1(1, "leo"); Person p2(2, "mike"); p1 = p2; cout << &(p1 = p2) << endl; cout << &p1 << endl;}
输出:
0x61fdd00x61fdd0
5. 重载运算符++
++
属于一元运算符。一般有前置操作和后置操作两种情况(即前置加加++i
和后置加加i++
)。
而对一元运算符进行重载需实现“编译器能够根据运算符出现在作用对象的前面还是后面来调用不同的函数”。
C++规定:
当编译器看到++a(即前置++)时,它就会调用a对象所属类重载的operator++()方法;当编译器看到a++(即后置++)时,它就会调用a对象所属类重载的operator++(int)方法。注:
C++标准规定++a的显示调用形式为:a.operator++();后置重载函数operator++(int)中之所以有一个int型形参,目的是为了绕过语法的限制,该形参并没有什么实质的作用。(因为方法重载需要满足参数个数或参数类型不一致)
demo:
#include using namespace std;class Complex{// 友元函数:重载<<运算符friend ostream& operator<<(ostream& os, Complex &co){ os << "A: " << co.mA << " B: " << co.mB; return os;}public: Complex(){ this->mA = 0; this->mB = 0; } // 重载前置++ Complex& operator++(){ mA++; mB++; return *this; } // 重载后置++ const Complex operator++(int){ Complex tmp(*this); ++(*this); return tmp; } // 重载前置-- Complex& operator--(){ mA--; mB--; return *this; } // 重载后置-- const Complex operator--(int){ Complex tmp(*this); --(*this); return tmp; } private: int mA; int mB;};int main(){ Complex cp; cout << cp << endl; // A: 0 B: 0 Complex re = cp++; cout << re << endl; // A: 0 B: 0 cout << cp << endl; // A: 1 B: 1 ++cp; cout << cp << endl; // A: 2 B: 2 return 0;}
输出:
A: 0 B: 0A: 0 B: 0A: 1 B: 1A: 2 B: 2
Q1: a++的返回类型为什么要是const对象呢?
有两个原因:
如果不是const对象,a(++)++这样的表达式就可以通过编译。但是,其效果却违反了我们的直觉 。a其实只增加了1,因为第二次自增作用在一个临时对象上。另外,对于内置类型,(i++)++这样的表达式是不能通过编译的。自定义类型的操作符重载,应该与内置类型保持行为一致 。a++的返回类型如果改成非const对象,肯定能通过编译,但是我们最好不要这样做。
Q2: ++a的返回类型为什么是引用呢?
这样做的原因应该就是:与内置类型的行为保持一致。前置++返回的总是被自增的对象本身。因此,++(++a)的效果就是a被自增两次。
6. 智能指针类&重载指针运算符6.1 引子#include#includeusing namespace std;class Person{private:string name;public:Person(){ name="no name"; }Person(string name){ this->name=name; }void show(){ cout << "name=" << name << endl;}};int main(){Person *p = new Person("Mike");p->show();delete p; // 如果忘记释放,那么就会造成内存泄漏p = NULL;return 0;}
输出:
name=Mike
对象在堆区开辟内存,不会自动调用析构函数,若不利用delete进行手动释放,将会造成内存泄漏。
6.2 智能指针类:托管new出来的对象的释放智能指针类:
class SmartPoint{private:Person *p;public:SmartPoint(){ p=NULL; }SmartPoint(Person *p){cout << "SmartPoint带参构造" << endl;this->p=p;}~SmartPoint(){cout << "SmartPoint析构" << endl;if(p!=NULL){delete p;p = NULL;}}};
使用demo:
void test_func(){Person *p = new Person("Mike");p->show();SmartPoint sm(p);}int main(){test_func();return 0;}
输出:
name=MikeSmartPoint带参构造SmartPoint析构
6.3 重载指针运算符(*和->)class SmartPoint{private:Person *p;public:SmartPoint(){ p=NULL; }SmartPoint(Person *p){cout << "SmartPoint带参构造" << endl;this->p=p;}~SmartPoint(){cout << "SmartPoint析构" << endl;if(p!=NULL){delete p;p = NULL;}}Person* operator->(){ return this->p; } // 重载->运算符号Person& operator*(){ return *(this->p); } // 重载*运算符号};
使用:
void test_func(){Person *p = new Person("Mike");SmartPoint sm(p);sm->show(); // 本质sm->->show(); 编译器简化为sm->show()(*sm).show(); // *sm相当于-----sm.operator*(),返回值是(*person)}int main(){test_func();return 0;}
输出:
SmartPoint带参构造name=Mikename=MikeSmartPoint析构
标签:
为您推荐
- 环球今亮点!反诈宣传进课堂 用心守护“钱袋子”
- 四川省天然气管道接入业务办理指引印发
- ST红太阳董秘回复:根据2023年3月24日中国证监会向公司出具的《行政处罚及市场禁入事先告知书》
- 每日热门:最新护理工作计划七篇(通用)
- 粤菜菜谱大全_粤菜菜谱
- 一看就知道你阴沟深大 阴沟深大
- 【机构评级】 大摩:予中银香港(02388)“与大市同步”评级 目标价降至27.1港元
- IonQ公司2023Q1营收430万美元 全年预计约1900万美元
- 10299 元起,联想拯救者 Y9000P 2023 游戏本魄白今日上市
- 卡米拉·莫罗:性感又俊朗 天天简讯
- 全球通讯!177pic漫画最新贴吧_177pic top
- 王俊凯分享周杰伦歌曲《扯》 疑似辟谣与经纪人穿情侣装 速讯
- 焦点热文:钢铁是怎样炼成的读后感200字左右_钢铁是怎样炼成的读后感200字
- 英媒:欧盟和七国集团计划禁止重启俄天然气管道
- 易点云(02416)于2023年5月15日-2023年5月18日招股 拟全球发行5857.5万股-全球微头条
- 松山区向阳街道清秀园社区:以花为礼 传递感恩
- 德力西漏电保护器型号规格_漏电保护器型号规格 每日视讯
- 什么是校园足球特色学校_什么是校园足球-世界热闻
- 西红柿滑肉汤做法王刚?
- 环球微动态丨能颠锅做美食、会穿针缝衣服 这些“中华小当家”手可真巧
- 2023青岛·全球创投风投大会市南分论坛举办_当前短讯
- 什么是太白粉_太白粉简介
- 秦始皇洛水歌是什么样的 表达了什么意思_环球观速讯
- 环球最新:打架被打的人住了院怎么办
- 湖南山东高考分数线 山东高考分数线一览表 世界聚焦
- 菜鸟裹裹喂鸟提醒怎么关
- 三亚湾对比琼中买哪个合算?三亚三亚湾房子升值空间大的区域
- 环球热资讯!线上线下齐发力 200多家企业将提供超过6000个就业机会
- 荨麻疹图片治疗方法 荨麻疹图片
- u盘无法放置超过4g_u盘不能放超过4g文件
- 湖南“品牌观察行动”启动-速递
- 每日信息:今日辟谣(2023年5月12日)
- 上海海港vs青岛海牛首发:奥斯卡、巴尔加斯领衔 三外援PK四外援
- 椽子 椽
- 计算机中视频文件格式有哪些_视频文件格式有哪些-今日热议
- 蒜苔纱肉丝如何做好吃?
- 中通官网投诉电话号_中通官网投诉电话
- 每日消息!爱博医疗:拟不超1亿元增资入股优你康
- 康力电梯: 高级管理人员薪酬管理办法
- 富士通发布可穿戴空调,售价39500日元 _全球热点评