运算符重载
运算符重载的概念
- 重载,重新载入,就比如之前那讲的函数重载,对一个已有的函数赋值一个新的定义,因此同一个名字就可以有不同的含义。
- 运算符也是可以重载的,比如cout在输出一个变量的时候,能接收不同类型的数据并输出,他就是重载了<<运算符,这个就是运算符重载。
- 所以运算符指的是对已有的运算符重新定义新的运算规则,已适应不同的数据类型,当然重载之后之前的运算规则还是有的。
运算符重载示例
- 几乎所有的运算符都可以被重载
- 运算符重载基本出现在类中和结构体中
- 运算符可以理解为函数的一个表现
语法:
- 关键字:operator
- 函数返回值 operator运算符(参数列表)
下面举一个简单的例子:重载+运算符
+运算符的特性:是双目运算符,两操作数操作完之后不改变任何一个操作数,把结果返回出去
对以下代码的重要部分进行讲解:
MyPoint MyPoint::operator+(MyPoint const&srcPoint) const;
&(引用):调用重载+运算符会发生值拷贝,基于本例,会拷贝八字节,所以使用引用优化拷贝过程:原本拷贝八字节,使用引用后变为四字节。
const:第一个const用来消除引用的弊端(引用有个弊端,在函数体内就可以修改它的值,根据+运算符的特性,不能修改其值,所以我们加上const)
第二个const将函数变为常量函数,使用本身不能被修改。
#include<iostream>
class MyPoint
{
int x, y;
public:
MyPoint(int x, int y);
//+运算符的特性:是双目运算符,两操作数操作完之后不改变任何一个操作数,把结果返回出去
MyPoint operator+(MyPoint const& srcPoint) const; //+运算符重载定义示例
};
MyPoint::MyPoint(int x, int y)
{
this->x = x;
this->y = y;
}
MyPoint MyPoint::operator+(MyPoint const&srcPoint) const
{
MyPoint tempPoint(0, 0);
tempPoint = *this; //*this表示调用者对象,谁调用+,就把它赋给tempPoint
tempPoint.x += srcPoint.x;
tempPoint.y += srcPoint.y;
return tempPoint;
}
void main()
{
MyPoint m1(1, 1);
MyPoint m2(2, 2);
MyPoint m3(0, 0);
m3 = m1 + m2; //运算符重载的显示调用
m3 = m1.operator+(m2); //和上面的调用等效
}
运算符重载的特殊示例
- 重载运算符,这个重载的运算符还是满足原来的规则,不能说重载+,结果做的是-的事,这样会使在运算符重载的运用上增加很大的难度。
- 运算符重载的参数,类中重载调用对象会占一个参数,就是this会占一个参,参数列表就是用来表示运算符的操作数的。
- 对于运算符重载的调用,可以直接使用运算符,也可以通过对象出来调用。
- 考虑返回值,不同的运算符有不同的返回值,要记得满足运算符原来的规则。
赋值运算符的重载
#include<string.h>
class MyString
{
char *pStr;
int len;
public:
MyString();
MyString(char *str);
MyString(MyString const&other); //拷贝构造
~MyString();
public:
MyString operator+(MyString const& srcStr) const;
};
MyString::MyString() //构造函数
{
pStr = NULL;
len = 0;
}
MyString::~MyString() //析构函数
{
if (pStr)
{
delete[]pStr;
pStr = NULL;
}
}
MyString::MyString(MyString const&other) //拷贝构造函数
{
pStr = NULL;
len = other.len;
if (len != 0)
{
pStr = new char[len];
strcpy(pStr, other.pStr);
}
}
MyString::MyString(char *str)
{
if (str == NULL)
{
pStr = NULL;
len = 0;
}
else
{
len = strlen(str) + 1;
if (len != 0)
{
pStr = new char[len];
strcpy(pStr, str);
}
}
}
MyString MyString::operator+(MyString const& srcStr) const //重载+运算符
{
MyString tempStr;
tempStr.len = len + srcStr.len - 1;
tempStr.pStr = new char[tempStr.len];
strcpy(tempStr.pStr, pStr);
strcat(tempStr.pStr, srcStr.pStr);
return tempStr;
}
void main()
{
MyString m1("1234");
MyString m2("ABCD");
MyString m3 = m1 + m2; //调用拷贝构造
MyString m4;
m4 = m1 + m2; //调用赋值运算符给m4赋值
int c = 0;
}
- 通过调试以上代码,发现报错,报错的原因是赋值运算符没有进行重载。
- 对赋值运算符进行重载。注意:如果没有书写重载赋值运算符,系统会提供一个有操作的赋值运算符操作,如果有堆内存的操作,必须重写赋值运算符的操作。
- 在声明中,因为赋值运算符的特性是会改变调用者对象,所以不能使用常量函数,而且会返回调用者自身,所以使用第一个引用。
- 重载赋值运算符的代码
MyString& MyString::operator=(MyString const& srcStr)
{
//在赋值之前不确定自己的堆内存有没有数据
if (pStr != NULL)
{
delete[]pStr;
}
pStr = NULL;
len = srcStr.len;
if (len != 0)
{
pStr = new char[len];
strcpy(pStr, srcStr.pStr);
}
return *this; //返回调用者自身
}
自增运算符的重载
本次示例是基于以上代码,对 前置++ 和 后置++ 运算符进行重载。
- 前置++和后置++重载的代码如下
MyString MyString::operator++(int)
{
MyString tempStr = *this; //先报错自身改变之前的值
for (int i = 0; i < len - 1; ++i) // 自身改变
{
(*(pStr + i))++;
}
return tempStr; //返回改变前的值
}
MyString& MyString::operator++()
{
for (int i = 0; i < len - 1; ++i)
{
(*(pStr + i))++;
}
return *this;
}
使用友元重载函数
类在已经实现且部分修改的情况下,需要进行运算符重载就可以通过友元的方式来进行重载。
例1:使用友元重载+运算符(基于以上示例)
当在运算符重载里做两个数的相加,它不属于任何对象,就给它做成友元函数。
友元不属于类和对象,所以几目就几个参数。
//这里只贴出部分代码,MyString是一个类
friend MyString operator+(MyString const&srcStr1, MyString const& srcStr2)
{
MyString tempStr;
tempStr.len = srcStr1.len + srcStr2.len - 1;
tempStr.pStr = new char[tempStr.len];
strcpy(tempStr.pStr, srcStr1.pStr);
strcat(tempStr.pStr, srcStr2.pStr);
return tempStr;
}
例2:输入、输出的重载
-
没有进行重载前,使用cin或cout时报错
-
对输入输出进行重载
istream& operator>>(istream &is, MyString& str)
{
char tempArr[1024] = {};
is >> tempArr;
if (str.pStr != NULL)
{
delete[]str.pStr;
}
str.len = strlen(tempArr) + 1;
str.pStr = new char[str.len];
strcpy(str.pStr, tempArr);
return is;
}
ostream& operator<<(ostream&os, MyString const& str)
{
os << str.pStr;
return os;
}