运算符重载
我们平常用的+、-、*、/等基本类型,如果我们想用对象也进行如此的操作,就需要我们自定义类并重新实现这样的算法,这就是我们要说的运算符重载,有点像函数重载的味道。
运算符重载不允许我们定义新的运算符也不允许改变它的优先级,它的优先级按照原来本身的优先级进行计算。其中有以下几种运算符不能重载:
作用域解析运算符 ::
条件运算符 ?:
直接成员访问运算符 .
sizeof 运算符 sizeof
解除对指向类成员的指针引用运算符 .*
运算符的关键定为operator,后面我们常用的运算符号,operator和运算符之间有没有空格都可以,像<、>等中间不用有空格 ,但像特殊的运算符要有空格 ,如operartor new,operator delete等,如果它们之间没有空格,则编译器以为是一个函数名,就会达不到想要的效果,故,没有歧义的情况下我们就不要有空格,但有歧义的时候就把中间加上个空格。
看下面的例子:
#include <iostream>
using namespace std;
class CBox
{
public:
CBox(double lv, double wv, double hv);
CBox();
double Volume() const;
bool operator >(const CBox& box) const; //重载>
bool operator >(const double& Value) const;//重载>
bool operator==(const CBox& box) const;//重载相等符号
private:
double m_length;
double m_width;
double m_height;
};
CBox::CBox(double lv,double wv, double hv):m_length(lv),m_width(wv),m_height(hv)
{
printf("Construct Created!\n");
}
CBox::CBox()
{
m_height=m_width=m_length=0;
printf("Constructed translated params values 0");
}
double CBox::Volume() const
{
return m_length * m_height * m_width;
}
bool CBox::operator>(const CBox& box) const
{
return this->Volume()>box.Volume();
}
bool CBox::operator>(const double& Value) const
{
return this->Volume()>Value;
}
bool CBox::operator==(const CBox& box) const
{
return (this->m_height==box.m_height)&&(this->m_length==box.m_length)&&(this->m_width==box.m_width);
}
void main()
{
CBox box1(15.5,20.6,3);
CBox box2(2,5,8.66);
if (box1>box2)
printf("box1 is bigger than box2\n");
else
printf("box1 is lower than box2\n");
printf("the box1 volume: %0.6f\n",box1.Volume());
if (box1>20.5)
printf("box1 volume 大于20.5\n");
else
printf("box1 volume 小于20.5\n");
CBox box3(1.2, 2.2, 3.3);
CBox box4(1.2, 2.2, 3.3);
if (box3==box4)
printf("box3等于box4\n");
else
printf("box3不等于box4\n");
}
重载符中,第一个const表明实参对象不需要改动它的数据。 &表明类引用,不用再产生一个备份而增大系统开销,最后const表明调用本函数的主体对象的内部数据不让改动,即不影响此对像。
bool operator >(const double& Value) const;是用当前对象和double类型的比较(如:20.0>abox)。而如果我们想比较 一个值和一个对象的大小如何比较?
这个我们可以写一个普通的函数,即不用写在本类中
bool operator>(const double& Value, const CBox& box)
{
return Value>box.volume();
}
以上数据成员类型为基本的数据类型,如果我们像前些篇章中用到的CShowmsg类,它里面有动态数据成员分配,再用这些可以吗?
重载符=
如: msg1=msg2等,我们重载=号,因为msg1中有个pmessage指针类型成员,而msg2中也有,就会发现msg1=msg2时,它们的pmessage会指向同一个文本地址,当msg1删除时就会删除这个文本,这时msg2对像是存在的,但其pmessage的地址对像不存在了,这时就会出现错误。
所以,此时,我们可以用以下的重载符=这样处理:
CShowmsg& operator=(const CShowmsg& amsg)
{
delete [] pmessage;
pmessage=new char[strlen(amsg.pmessage)+1];\
strcpy(this->pmessage,amsg.pmessage);
return *this;
}
如果有三个CShowmsg对像,msg1=msg2=msg3,这样的情况,如何处理?
因为=号中是从右向左开始的,所以这样是正确的,msg3赋值给msg2,会先把msg2中的pmessage分配的内存删除,然后再建立根据msg3中pmessage 的大小来建msg2中的pmessage.
如果msg1=msg1这样的话又怎么处理呢?即自己赋值给自己。
msg1中有个pmessage,在用重载=时会先把自己的pmessage内存给销毁掉,这时你再赋给自己就不对了,因为pmessage不存在了,就会出错,此时怎么办?
这时我们要做个判断,如果是自己的话就返回自己,什么也做。如下:
CShowmsg& operator=(const CShowmsg& amsg)
{
if (this==&amsg) //检查当前右边的指针地址是否和左值指针一致
return *this;
delete [] pmessage;
pmessage=new char[strlen(amsg.pmessage)+1];\
strcpy(this->pmessage,amsg.pmessage);
return *this;
}
以下为全新的例子,运算符等号重载:
#include <iostream>
#include <cstring>
using namespace std;
class CShowmsg
{
private:
char* pmessage;
public:
void Showit() const
{
cout<<pmessage<<endl;
}
void Reset()
{
char* ptmp=pmessage;
while(*ptmp)
*(ptmp++)='*';
}
CShowmsg& operator=(const CShowmsg& amsg)
{
if(this==&amsg)
return *this;
delete [] pmessage;
pmessage=new char[strlen(amsg.pmessage)+1];
strcpy(pmessage,amsg.pmessage);
return *this;
}
CShowmsg(const char* text="Default message")
{
pmessage=new char[strlen(text)+1];
strcpy(pmessage,text);
}
~CShowmsg()
{
cout<<"Destructor Called!\n";
delete [] pmessage;
}
};
void main()
{
CShowmsg msg1("The devil takes of his own.");
CShowmsg msg2;
cout<<"msg2 contains - ";
msg2.Showit();
cout<<endl;
msg2=msg1;
cout<<"msg2 contains - ";
msg2.Showit();
cout<<endl;
msg1.Reset();
cout<<"msg1 now contains - ";
msg1.Showit();
cout<<endl;
cout<<"msg2 still contains - ";
msg2.Showit();
cout<<endl;
}
显示结果:
从以上例子看出,msg1和msg2两个对象没有一点关系了。
结论:如果需要给类的数据成员动态分配空间,则必须实现赋值去处符。
加减法运算符重载我们在此忽略,相对比较简单。
递增或递减运算符重载即++和--
++和--因在变量前后而不一样,那我们如何重载呢?
看下下面的代码:
#include <iostream>
using namespace std;
class Length
{
private:
double len;
public:
Length& operator++() //先加再计算
{
len++;
return *this;
}
const Length operator++(int)//先计算再加
{
return *this;
}
Length& operator--() //先减再计算
{
len--;
return *this;
}
const Length operator--(int) //先计算再减
{
return *this;
}
};
void main()
{
}
区分前缀或后缀运算符重载的首要方法是形参列表,前缀形式滑形参,后缀形式有一个int类型的形参。后缀运算符的形参只是为了和前面前缀运算符区分 开来,除此之外它在函数中没用任何用处。
前缀的函数是在用于表达式之前将之递增或递减,因此当前对象在递增或递减之后,我们只需要返回该对象的引用即可。
而在后缀形式中,操作数是在其当前值被表达式使用之后递增或递减的,要实现这点,需要在递增或递减当前对象之前创建当前对象的副本,并在修改过当前对象之后返回新创建的副本对象。