2.5 运算符重载
运算符重载 就是赋予已有的运算符多重定义,是一个一种静态联编的多肽
运算符重载的目的,就是实现类对象的运算操作
运算符重载函数
重载时,一般在类中定义一个特殊的函数,以便通知编译器,遇到该重载运算符时调用该函数,并由该函数来完成该运算符应该完成的操作.这种特殊的函数称为运算符重载函数.它常是类的成员函数或友元函数,运算符的操作数通常也是该类的对象
定义:
<函数类型><类名>::operator <重载的运算符>(<形参表>)
{...} // 函数体
// 定义一个复数类,然后重载"+"运算符,使这个运算符能够直接完成复数的加运算
#include <iostream>
using namespace std;
class CComplex
{
public:
CComplex(double r = 0,double i = 0)
{
realPart = r;
imagePart = i;
}
void print()
{
cout << "实部 = " << realPart << ",虚部 = " <<imagePart << endl;
}
CComplex operator + (CComplex &c); // 重载运算符+
CComplex operator + (double r); // 重载运算符+
private:
double realPart; // 复数的实部
double imagePart; // 复数的虚部
};
// 用于实现两个复数的加法
CComplex CComplex::operator + (CComplex &c) // 参数是CComplex引用对象
{
CComplex temp;
temp.realPart = realPart + c.realPart;
temp.imagePart = imagePart + c.imagePart;
return temp;
}
// 用于实现一个复数与一个实数的加法
CComplex CComplex::operator + (double r) // 参数是double类型的数据
{
CComplex temp;
temp.realPart = realPart +r;
temp.imagePart = imagePart;
return temp;
}
int main()
{
CComplex c1(12,20),c2(50,70),c;
c = c1+c2;
/**
* 先将"c1+c2"解释为"c1.operator + (c2)"
* 然后调用函数"operator + (CComplex &c)",将实参c2传给形参c
* 最后"return temp",即将函数中的返回值赋给等号左边的c
*/
c.print();
c = c1 +20;
/**
* 先将"c1+20解释为"c1.operator + (20)"
* 然后调用函数"operator + (double r)",将实参20传给形参r
* 最后"return temp",即将函数中的返回 值赋给等号左边的c
*/
c.print();
return 0;
}
/**
* 实部 = 62,虚部 = 90
* 实部 = 32,虚部 = 20
*/
在编译”c1+c2”时,由于成员函数都隐含一个this指针,因此,解释的”c1.operator + (c2)”等价于”operator + (&c1,c2)”
当重载的运算符函数是类的成员函数时,运算符函数的形参个数要比运算符操作个数少一个
双目运算符:如”+”,重载的成员函数,参数应该只有一个,用来指定右操作数
单目运算符:重载的函数,由于操作数就是该对象本身,因此运算符函数不应有函数
运算符重载限制
在C++中,运算符重载有一下限制:
重载的运算符必须是一个已有的合法的C++运算符. C++中不允许重载的运算符:
?:(条件)
.(成员)
*.(成员指针)
::(域作用符)
sizeof(取字节大小)
不能为C++没有的运算符进行重载
当重载一个运算符时,该运算符的操作数个数,优先级和结合性不能改变
运算符重载的方法有两种
类的操作成员函数
友元函数:
=(赋值)
,()(函数调用)
,[](下标)
,->(成员指针)
运算符不能重载为友元函数
友元函数
- 友元重载方法的格式:
friend <函数类型>operator <重载的运算符>(<形参>) // 单目运算符重载
{}
friend <函数类型>operator <重载的运算符>(<形参 1,形参 2>) // 双目运算符重载
{}
- 单目运算符的友元重载(只有一个形参)
- 形参是类的对象:如”++”,”–”,因为操作数必须是左值
- 形参是类的对象或类的引用:如”-(负号)”
- 双目运算符的友元重载函数(有两个形参)
- 两个形参必须有一个是类的对象
// 运算符的友元重载
// 用友元函数实现双目运算符"+",单目运算符"-"的重载,成员函数实现"+="运算
#include <iostream>
using namespace std;
class CComplex
{
public:
CComplex(double r=0,double i=0)
{
realPart = r;
imagePart = i;
}
void print()
{
cout << "实部 = " << realPart << ",虚部 = " << imagePart << endl;
}
CComplex operator + (CComplex &c); // A 重载运算符+
CComplex operator + (double r); // B 重载运算符+
friend CComplex operator + (double r,CComplex &c); // C 友元重载运算符+
friend CComplex operator - (CComplex &c); // 友元重载单目运算符-
void operator += (CComplex &c);
private:
double realPart; // 复数的实部
double imagePart; // 复数的虚部
};
CComplex CComplex::operator + (CComplex &c)
{
CComplex temp;
temp.realPart = realPart + c.realPart;
temp.imagePart = imagePart + c.imagePart;
return temp;
}
CComplex CComplex::operator + (double r)
{
CComplex temp;
temp.realPart = realPart + r;
temp.imagePart = imagePart;
return temp;
}
CComplex operator + (double r,CComplex &c)
{
CComplex temp;
temp.realPart = r + c.realPart;
temp.imagePart = c.imagePart;
return temp;
}
CComplex operator - (CComplex &c)
{
return CComplex(-c.realPart,-c.imagePart);
}
void CComplex::operator += (CComplex &c)
{
realPart += c.realPart;
imagePart += c.imagePart;
}
int main()
{
CComplex c1(12,20),c2(30,70),c;
c = c1 + c2;
c.print();
c = c1 + 20;
c.print();
c = 20 + c2;
c.print();
c2 += c1;
c2.print();
c1 = -c2;
c1.print();
return 0;
}
/**
实部 = 42,虚部 = 90
实部 = 32,虚部 = 20
实部 = 50,虚部 = 70
实部 = 42,虚部 = 90
实部 = -42,虚部 = -90
*/
- 分析
- 类
CComplex
中,对于双目运算符”+”的重载分别用了成员函数和友元函数的方法,即ABC - 当
c = 20 + c2;
时,这里的”20+c2”无法解析成”20.operator + (c2)”,因为”20”不是一个对象.因此,当左操作数是数值而不是对象时,是无法用成员函数来实现运算符重载的,必须用友元函数才能实现.因此,当c = 20 + c2;
时,会自动调用A版本的友元运算符重载函数,即C版本
- 类
转换函数
- 类型转换:将一种类型的值映射为另一种类型的值
- C++的类型转换包含自动隐含和强制转换
- 转换函数是实现强制转换操作的手段之一,它是类中定义的一个非静态成员函数.
- 转换函数的格式:
class <类名>
{
public:
operator <类型>();
// ...
};
其中,类型是要转换后的一种数据类型(可以是基本数据类型或构造数据类型).operater
和类型一起构成了转换函数名.它的作用:将”class <类名>”声明的类对象转换成类型指定的数据类型.
// 转换函数的使用:将数字转换成大写
#include <iostream>
#include <string.h>
using namespace std;
typedef char* USTR;
class CMoney
{
double amount;
public:
CMoney(double a = 0.0)
{
amount = a;
}
operator USTR ();
};
CMoney::operator USTR()
{
USTR basestr[15] = { "分","角","元","拾","佰","仟","万","拾","佰","仟","亿","拾","佰","仟","万" };
USTR datastr[10] = { "零","壹","贰","叁","肆","伍","陆","柒","捌","玖" };
static char strResult[80];
double temp, base = 1.0;
int n = 0;
temp = amount * 100.0;
strcpy(strResult, "金额为:");
if (temp < 1.0)
strcpy(strResult, "金额为:零元零角零分");
else
{
while (temp >= 10.0)
{
// 计算位数
base = base * 10.0;
temp = temp / 10.0;
n++;
}
if (n >= 15)
{
strcpy(strResult, "金额超过范围!");
}
else
{
temp = amount * 100.0;
for (int m = n; m >= 0; m--)
{
int d = (int)(temp / base);
temp = temp - base*(double)d;
base = base / 10.0;
strcat(strResult, datastr[d]);
strcat(strResult, basestr[m]);
}
}
}
return strResult;
}
int main()
{
CMoney money(1234123456789.123);
cout << (USTR)money << endl;
system("pause");
return 0;
}
/*
* 金额为:壹万贰仟叁佰肆拾壹亿贰仟叁佰肆拾伍万陆仟柒佰捌拾玖元壹角贰分
*/
- 注意:转换函数重载用来实现类型转换的操作,但转换函数只能是成员函数,而不能是友元函数.
- 转换函数可以被派生类继承,也可以被说明为虚函数,且在一个类中可以定义多个转换函数.
赋值运算符的重载
- C++中,相同类型的对象之间可以直接相互赋值,但不是所有的同类型对象都可以这个操作.当对象的成员中有数组或动态的数据类型时,就不能直接相互赋值.
- 基于这个原因,必须对赋值运算符”=”进行重载,并在重载函数中重新开辟内存空间或添加其他代码,以保证赋值的正确性.
#include <iostream>
#include <string.h>
using namespace std;
class CName
{
public:
CName(char *s)
{
name = new char[strlen(s)+1];
strcpy(name,s);
}
~CName()
{
if(name)
{
delete []name;
name = NULL;
}
}
void print()
{
cout << name << endl;
}
CName& operator = (CName &a) // 赋值运算符重载
{ // 重载函数operator = ()的返回类型是CName&
if(name)
{
delete []name;
name = NULL;
}
if(a.name)
{
name = new char[strlen(a.name)+1];
strcpy(name,a.name);
}
return *this;
}
private:
char *name;
};
int main()
{
CName d1("Key"),d2("Mouse");
d1.print();
d1 = d2;
d1.print();
return 0;
}
/**
* Key
* Mouse
*/
注意:
1.赋值运算符重载函数operator = ()
的返回类型是CName&
,注意它返回的引用是类的引用而不是对象.因为,C++ 要求赋值表达式左边的表达式是左值,可以进行以下操作
int x,y = 5; // y是左值
(x = y)++; // x是左值
由于引用的实质就是对象的内存空间,所以通过引用可以改变对象的值.如果返回的类型
仅仅类的对象而不是对象的内存空间,因此赋值操作后,不能再作为左值,从而导致程序运行终止.
2.赋值运算符不能重载为友元函数,只能重载为一个非静态成员函数
3.赋值运算符重载函数是唯一一个不能被继承的运算符函数.
自增自减运算符的重载
- 自增”++”和自减”–”运算符是单目运算符,它们又有前缀和后缀之分
- 为了区分这两种运算符,在重载时应将后缀运算符视为双目运算符
obj++或obj--
被看做
obj++0或obj--0
- 又由于这里前缀”++”中的obj必须是一个左值,因此运算符重载函数的返回类型应是引用而不是对象.
- 设类为X,当用成员函数方法来实现前缀”++”和后缀”++”运算符的重载时,则可使用以下格式:
X& operator ++(); // 前缀++
X operator ++(int ); // 后缀++
- 用友元函数方法来实现前缀”++”和后缀”++”运算符的重载,则可使用以下格式:
friend X& operator ++(X&); // 前缀++
friend X operator ++(X&,int); // 后缀++
#include <iostream>
using namespace std;
class CCounter
{
public:
CCounter(int n = 0)
{
unCount = n;
}
CCounter& operator ++(); // 前缀++运算符重载声明
friend CCounter operator ++(CCounter &one,int); // 后缀++运算符友元重载声明
void print()
{
cout << unCount << endl;
}
private:
unsigned unCount;
};
CCounter& CCounter::operator ++() // 前缀++运算符重载实现
{
unCount++;
return *this;
}
CCounter operator ++(CCounter& one,int) // 后缀++运算符友元重载实现
{
CCounter temp = one;
one.unCount++;
return temp;
}
int main()
{
CCounter d1(8),d2;
d2 = d1++;
d1.print();
d2.print();
d2 = ++d1;
d1.print();
d2.print();
++++d1;
d1.print();
return 0;
}
/**
9
8
10
10
12
*/
- 说明
- 这里的前缀”++”是通过成员函数重载来实现,后缀”++”是通过友元函数来实现
- 在友元重载的后缀”++”运算符函数中,由于函数中的形参仅仅用来在格式上区分前缀”++”运算符,本身并不使用,因此,不必为形参指定形参名.