运算符重载的引入
对于如下类
class Int
{
private:
int value;
public:
Int(int x = 0):value(x) {} //构造函数
Int(const Int & it):value(it.value) {} //拷贝构造函数
~Int() {} //析构函数
};
如果想在主函数实现Int类型的相加,则需要在类里定义一个Add函数:
int main()
{
Int a(10),b(10);
Int c;
c=a.Add(b);
}
Add函数的功能相当于c.value = a.value + b.value
Add函数如下:(类的成员函数)
//类的成员函数
Int Add(Int x)
{
int val = this->value + x.value;
Int tmp(val);
return tmp;
}
对于如上Add函数,C++编译器在编译时会作如下改写, (加this指针)
//类的成员函数
Int Add(Int * const this, Int x)
{
int val = this->value + x.value;
Int tmp(val);
return tmp;
}
在主函数调用中,c=a.Add(b)
会被改写为:
c = Add(&a,b);
该Add函数可以优化,在如上实现的Add函数里,创建了三个对象,如下图示:
为了防止创建这么多的对象,我们可以做如下优化:(传引用,直接返回)
优化后的Add函数
//类的成员函数
Int Add(Int & x)
{
return Int(this->value + x.value);
}
这样return时候会直接把对象建立在临时空间,就只创建一个对象
接下来我们又注意到,Add函数并不改变x与当前对象的状态,即x.value和this->value都是不希望被改变的,因此要用const去修饰Add的参数
//类的成员函数
Int Add(const Int & x) const
{
return Int(this->value + x.value);
}
第一个const修饰&x, 第二个const修饰该方法是一个常方法,修饰的是*this
这样,在Add函数里若出现修改x.value或this->value的情况,就会直接报错,Add函数就比较完美的实现了:
//类的成员函数
Int Add(const Int & x) const
{
return Int(this->value + x.value);
}
以上就实现了两个变量进行相加的函数Add, 但我们知道内置类型使用 ’ + ’ 进行相加,那能不能把Add直接换成+号呢?即想让+号变成个函数名,但是可惜的是,C++遵循了C的要求,运算符不能作为函数名,C++给出了解决办法:在运算符前面加上operator关键字,opertaor与运算符一起使用构成一个函数名,这样就间接地实现了让运算符作为函数名去使用的功能, 这就是运算符的重载
//类的成员函数
Int operator+(const Int & x) const
{
return Int(this->value + x.value);
}
运算符的重载
运算符的重载实际上是一种特殊的函数重载,必须定义一个函数,并告诉C++编译器,当遇到该重载的运算符时调用此函数。这个函数叫做运算符重载函数(通常为类的成员函数)
类外定义运算符重载函数的一般格式:
返回值类型 所属类::operator运算符(参数表)
{ 函数体 }
operator是关键字,它与重载的运算符一起构成函数名,因重载函数函数名的特殊性,C++编译器可以将这类函数识别出来
例如:
//类的成员函数
Int operator+(const Int & x) const
{
return Int(this->value + x.value);
}
在主函数里:
int main()
{
Int a(10),b(20);
Int c;
c = a+b;
}
c = a + b;
就等价于c = a.operator+(b);
编译时会改写成:c = operator+(&a, b);
明白了加法的运算符重载以后,我们可以很容易地写出下面一系列运算符的重载
减法运算符的重载:
//类的成员函数
Int operator-(const Int & x)const
{
int val = this->value - x.value;
return Int(val);
}
乘法运算符的重载:
//类的成员函数
Int operator*(const Int &x)const
{
int val = this->value * x.value;
return Int(val);
}
除法运算符的重载:
//类的成员函数
Int operator/(const Int &x)const
{
if(x.value == 0) exit(EXIT_FAILUE);
int val = this->value/x.value;
return Int(val);
}
以上运算符左右都是同类型对象,那如果左右是不同种类型,运算符的重载又该如何写呢?例对如下加法,是自定义类型 + 整型
int main()
{
Int a(10),b(20),c;
int x = 10;
c = a + x;// 自定义类型 + 整型
//c = a + x; c = a.operator+(x); 编译器改写:c = a.opeartor(&a, x);
}
它的运算符重载函数应该如下:
//类的成员函数
Int operator+(const int x) const
{
int val = this->value + x;
return Int(val);
}
注意:函数参数是内置类型时不加引用比较好,因为引用的本质是指针,编译器编译的时候引用是用指针解释的,用指针对内存访问时会访问两次(读地址,读地址里的值),而不加引用访问内置类型时只访问一次内存,所以不加引用比较好。而若是自己定义的类型,如果不加引用,形参实参结合就会调动拷贝构造函数构建对象,等到函数结束,又要调动析构函数析构对象,所以自定义类型加引用好
其实在前面我们已经实现了Int类对象与对象的相加,那么对象与整型相加可以化为对象与对象相加,就是用那个整型作为构造函数的参数去构造一个对象,所以自定义类型 + 整型
的构造函数还可以这样写:
//类的成员函数
Int operator+(const int x) //自定义型 + 整型
{
return *this + Int(x);
}
若是如下情况:整型 + 自定义类型
int main()
{
Int a(10),b(20),c;
int x = 10;
c = x + a;// 整型 + 自定义型
}
如果把x和a都作为参数传给重载函数,则会出现参数过多的错误,因为重载函数作为类的成员函数还隐含一个this指针,这样加法就有三个参数了,而+是个双目运算符,所以不能这样做!
可以把重载函数放在类外,使之成为一个全局函数,这样它就没有this指针了,但是全局函数又不能访问类的私有数据成员,因此不能直接x + it.value
,但是我们之前已经实现了自定义类型 + 整型
, 所以全局函数只需要改变一下+号两边位置即可,如下:
//全局函数
Int operator+(const int x, const Int &it)
{
return it + x;
}
当在主函数中出现整型 + 自定义的Int类型
时,调动的是全局上的operator+函数,该函数把这两个类型的相加转换为自定义类型 + 整型
,从而再去调动类里面的自定义类型 + 整型
的重载函数
运算符经重载后有两种使用方式:例如对于c = x + a;
- 运算符加:c = x + a;
- 函数加:c = operator+(x,a);
这两种调动方式是等价的
赋值语句的重载
//类的成员函数
void operator=(const Int &it) //不能定义为常方法,赋值语句要修改当前对象的状态
{
this->value = it.value;
}
上述代码虽然可以实现简单的赋值,但不能实现类似c=a=b这样的连续赋值,而我们内置类型是可以的,因此要改进上述代码,使之也可实现连续赋值
拆解c=a=b, 如下图,可以知道赋值号的重载函数要返回当前对象
所以修改后的代码如下:
//类的成员函数
Int operator=(const Int & it)
{
this->value = it.value;
return *this;
}
这里可以用引用返回,因为this的生存期不受该重载函数的影响,所以可以用引用返回,这样好处是return时候不会构建一个临时对象了
//类的成员函数
Int & operator=(const Int & it)
{
this->value = it.value;
return *this;
}
如果要能实现c=c这样的情况,则可以再加一个if判断
//类的成员函数
Int & operator=(const Int & it)
{
if(this != &it)
{
this->value = it.value;
}
return *this;
}
扩充:以值返回和以引用返回
对于如下两个函数,fun1以传值返回,fun2以引用返回Object fun1(Object & it)//以值返回 { return it; } Object & fun2(Object & it)//以引用返回 { return it; } int main() { Object obj1(10); Object obj2(20); Object obj3,obj4; obj3 = fun1(obj1); obj4 = fun2(obj2); }
在主函数中,调动fun1,fun1return时候,fun1函数生存期结束,会构建一个临时对象返回给主函数;而若是调动fun2,以传引用方式返回,编译器就知道it就是obj2的别名,会直接把obj2返回
即传值返回需要构建一个临时对象作为过渡,而引用返回不会构建临时对象作为过渡
重载++、–前置后置的问题
前置++的格式为:
返回类型 所属类::operator++()
{ 函数体 }
后置++的格式为:
返回类型 所属类::operator++(int)
{ 函数体 }
后置++中的参数int仅用作区分,并无实际意义,可以给变量名,也可以不给变量名只写个int
例如对于如下Money类型++运算符的重载:
class Money
{
int yuan,jiao;//1元等于10角
public:
Money operator++();//前置++
Money operator++(int);//后置++
}
前置++,先加再用
Money & Money::operator++()
{
Jiao++;
if(Jiao>=10)//10角等于1元
{
Yuan += 1;
Jiao -= 10;
}
return *this;
}
后置++,先用再加
Money Money::operator++(int)//int区分这是后置++
{
Money temp = *this;
Jiao++;
if(Jiao>=10)
{
Yuan+=1;
Jiao -= 10;
}
return temp;
}
后置++中,由于要返回对象未加之前的值,所以用一个临时对象保存原始对象,然后再对对象进行++运算,最后返回对象的原始值。后置++不能以引用返回,因为返回的是局部对象,函数结束局部对象就被销毁
不允许重载的运算符
C++允许重载的运算符有42个,如下表:
C++中不可以重载的运算符有五个,如下表:
重载运算符的限制
- 不可臆造新的运算符,只能对C++自身提供的运算符进行重载
- 不能改变运算符原有的优先级、结合性和语法结构,不能改变运算符操作数的个数
- 重载运算符含义必须清楚,不能有二义性