所谓运算符重载,是指自己用函数实现对一般运算符的运算重写,例如下面的一些运算符
但下面的一些运算符不能用来重载:
还有一些其他的限制:如只能是重写已有的函数,不能修改系统固定已有的数据类型的运算(如int、double
等等),可以在自己创建的数据类型里面进行重载,还有保持操作数目(元数),优先级别与原来的不变。
下面是一般的例子:
一般的写法是在类中定义一个const 数据类型名 :: operator 原运算符号(const 数据类型& that)
当然也可以像第二种Global function写法,即在括号里加入两个参数。**const**
的意义是保证不会发生左值(赋值)运算。
下面是一种典型的类定义,这里要注意的是,
这样的操作,要注意实际的实现形式是下图的蓝色字体,也就是说,这样的运算重载函数要注意顺序,不能实现诸如z = 3 + y
这样的形式运算,因为需要保证前面的第一个receiver是自定义的类。
下面是可以完成z = 3 + y
运算的写法,要用到friend(友元函数),即可以在成员函数中调用变量列表的私有成员(像下面的**lhs.i**
和**rhs.i**
)
下面是一些提示:
关于运算重载的原型
一些运算重载函数有着自己的原型,下面是一些简单的一元运算重载模版
比较特殊的,这里对前置加加/减减和后置加加/减减做一些阐述。
图中的prefix
意思是++x
,postfix意思是x++
,他们有着不同的重载函数,像下面是它们各自的实现形式,因为从本质上讲++X返回的是他自身,而X++返回的是另一个
实际上编译器的运行是这样的:
下面是一些一元运算的重载,可以看到基本上的原型只有两种,其余的都是对前面的复用拓展而来,而且他们都是Inline的,没有性能担忧。
这里留一个Vector容器的 operator[]
(方括号)实现
关于运算符重载中的赋值
在成员之间发生一个类用已有的同类对象构造另一个对象的时候,如果不在类内定义一个operator=
的成员函数去做运算符重载,系统会默认创建一个完成拷贝构造的函数,如下图
如果要自己写一个运算符重载函数,则应当像T& T::operator=(const T& rhs)
这样,并且要带有check for self assignment(就是下面的if(this! = &rhs)……结构)),不然会发生下下图发生的p自己删除自己内存的情况
小结:
关于运算重载的类型转换
类中默认存在一个自动转换函数,会实现如下图所示的第二种xyz = abc
也会完成类型转换(和上一行是一样的)
为了规避隐式转换(也就是像上面这种自动的转换),可以用添加修饰符explicit
来实现,如下:
如上使用explicit修饰符之后,会发生下图中这种编译运行不通过的情况。
如果不想使用explicit,可以使用下面的这种形式,是为了把 X 类对象转变成 T 类,要注意的是,这种成员函数没有返回类型,因为operator 之后的 T 就是返回类型了
下面 是一些可以自动(默认)完成类型转换的情况(这里有一个作为常识的知识:即数据自动转换是从窄到宽(占用字节数),反之则不可),也就是说这些情况下会自动发生,不管编程者是否定义了相关函数。
下面这种情况:
在上面的函数中,可以看到Apple类的类型重载函数和Orange类的重载构造函数(前面没有explicit时候)都会发生f(Orange)
的自动类型转换和调用,但是会调用哪个类的重载函数呢?答案是都有可能,所以一般在这种情况下,都会把构造函数的explict修饰符给加上,防止自动调用。
为了避免上述这种自动调用会出现的一些情况,一般会将类型转换函数定义成X toX() const;
这种形式,想转换时才会去调用,清晰明了。