运算符重载只是一种语法上的方便,实际上它是另一种函数调用的方式,其不同之处在于函数的参数不是出现在圆括号内,而是紧贴在一些字符旁边;调用运算符时要把运算符放置在参数之间、参数之后、参数之前;编译器决定调用哪一个“函数”。
在C++中,可以定义一个处理类的新运算符,函数的名字由关键字operator及其后紧跟的运算符组成。
一、语法
函数名字是operator@,这里@代表了被重载的运算符。参数的个数取决于两个因素:
1)运算符是一元的还是二元的;
2)运算符被定义为全局函数(对于一元是一个参数,对于二元是两个参数)还是成员函数(对于一元没有参数,对于二元是一个参数——此时该类的对象用做左侧参数,唯一的参数是出现在运算符右侧的那个操作数)。
例如:
#define MY_STRING_NEW(_T_, _size_) new _T_[_size_]
#define MY_STRING_NEW_char(_size_) MY_STRING_NEW(char, _size_)
#define MY_STRING_DELETE(_p_) delete []_p_;
class AString
{
public:
AString(const AString &s, char c)
{
SetStartLen(s.Len() + 1);
char *chars = _chars;
unsigned len = s.Len();
memcpy(chars, s, len);
chars[len] = c;
chars[(size_t)len + 1] = 0;
}
~AString() { MY_STRING_DELETE(_chars); }
unsigned Len() const
{
return _len;
}
void SetStartLen(unsigned len)
{
_chars = 0;
_chars = MY_STRING_NEW_char(len + 1);
_len = len;
}
friend AString operator+(const AString &s, char c)
{
return AString(s, c);
}
AString &operator+=(char c)
{
unsigned len = _len;
char *chars = _chars;
chars[len++] = c;
chars[len] = 0;
_len = len;
return *this;
}
private:
char *_chars;
unsigned _len;
};
二、可重载的运算符
虽然几乎所有C中的运算符都可以重载,但运算符重载的使用是受限制的(不能使用C中没有意义的运算符,不能改变运算符的优先级,不能改变运算符的参数个数等)。
1、一元运算符
全局函数形式(非成员的友元函数):
// Non-member functions
class Integer {
long i;
Integer* This() { return this;}
public:
Integer(long ll = 0) : i(ll) {}
friend const Integer& operator+(const Integer& a); // No side effects takes const&
friend const Integer operator-(const Integer& a);
friend const Integer operator~(const Integer& a);
friend Integer* operator&(Integer& a);
friend int operator!(const Integer& a);
friend const Integer& operator++(Integer& a); // Prefix
friend const Integer operator++(Integer& a, int); // Postfix
friend const Integer& operator--(Integer& a); // Prefix
friend const Integer operator--(Integer& a, int); // Postfix
};
// Global operators
const Integer& operator+(const Integer& a)
{
return a;
}
const Integer operator-(const Integer& a)
{
return Integer(-a.i);
}
const Integer operator~(const Integer& a)
{
return Integer(~a.i);
}
Integer* operator&(Integer& a)
{
return a.This(); // &a is recursive
}
int operator!(const Integer& a)
{
return !a.i;
}
// Prefix,return incremented value
const Integer& operator++(Integer& a)
{
a.i++;
return a;
}
// Postfix,return the value before increment
const Integer operator++(Integer& a, int)
{
Integer before(a.i);
a.i++;
return before;
}
// Prefix, return decremented value
const Integer& operator--(Integer& a)
{
a.i--;
return a;
}
// Postfix,return the value before decrement
const Integer operator--(Integer& a, int)
{
Integer before(a.i);
a.i--;
return before;
}
成员函数形式:
// Member functions
class Byte {
unsigned char b;
public:
Byte(unsigned char bb = 0) : b(bb) {}
const Byte& operator+() const
{
return *this;
}
const Byte operator-() const
{
return Byte(-b);
}
const Byte operator~() const
{
return Byte(~b);
}
Byte operator!() const
{
return Byte(!b);
}
Byte* operator&()
{
return this;
}
const Byte& operator++() // Prefix
{
b++;
return *this;
}
const Byte operator++(int) // Postfix
{
Byte before(b);
b++;
return before;
}
const Byte& operator--() // Prefix
{
--b;
return *this;
}
const Byte operator--(int)
{
Byte before(b);
--b;
return before;
}
};
对于自增和自减,用户所见到的是对前缀和后缀版本调用不同的函数,实际上这两个函数调用有不同的标记(编译器为int参数传递一个哑元常量值用来为后缀版产生不同的标记)。
2、二元运算符
全局函数形式:
// Non-member functions
class Integer {
long i;
public:
Integer(long ll = 0) : i(ll) {}
friend const Integer operator+(const Integer& left, const Integer& right);
friend const Integer operator-(const Integer& left, const Integer& right);
friend const Integer operator*(const Integer& left, const Integer& right);
friend const Integer operator/(const Integer& left, const Integer& right);
friend const Integer operator%(const Integer& left, const Integer& right);
friend const Integer operator^(const Integer& left,
const Integer& right);
friend const Integer operator&(const Integer& left, const Integer& right);
friend const Integer operator|(const Integer& left, const Integer& right);
friend const Integer operator<<(const Integer& left, const Integer& right);
friend const Integer operator>>(const Integer& left, const Integer& right);
friend Integer& operator+=(Integer& left, const Integer& right);
friend Integer& operator-=(Integer& left, const Integer& right);
friend Integer& operator*=(Integer& left, const Integer& right);
friend Integer& operator/=(Integer& left, const Integer& right);
friend Integer& operator%=(Integer& left, const Integer& right);
friend Integer& operator^=(Integer& left, const Integer& right);
friend Integer& operator&=(Integer& left, const Integer& right);
friend Integer& operator|=(Integer& left, const Integer& right);
friend Integer& operator>>=(Integer& left, const Integer& right);
friend Integer& operator<<=(Integer& left, const Integer& right);
friend int operator==(const Integer& left, const Integer& right);
friend int operator!=(const Integer& left, const Integer& right);
friend int operator<(const Integer& left, const Integer& right);
friend int operator>(const Integer& left, const Integer& right);
friend int operator<=(const Integer& left, const Integer& right);
friend int operator>=(const Integer& left, const Integer& right);
friend int operator&&(const Integer& left, const Integer& right);
friend int operator||(const Integer& left, const Integer& right);
};
// Global operators
const Integer operator+(const Integer& left, const Integer& right)
{
return Integer(left.i + right.i);
}
const Integer operator-(const Integer& left, const Integer& right)
{
return Integer(left.i - right.i);
}
const Integer operator*(const Integer& left, const Integer& right)
{
return Integer(left.i * right.i);
}
const Integer operator/(const Integer& left, const Integer& right)
{
ASSERT(right.i != 0);
return Integer(left.i / right.i);
}
const Integer operator%(const Integer& left, const Integer& right)
{
ASSERT(right.i != 0);
return Integer(left.i % right.i);
}
const Integer operator^(const Integer& left, const Integer& right)
{
return Integer(left.i ^ right.i);
}
const Integer operator&(const Integer& left, const Integer& right)
{
return Integer(left.i & right.i);
}
const Integer operator|(const Integer& left, const Integer& right)
{
return Integer(left.i | right.i);
}
const Integer operator<<(const Integer& left, const Integer& right)
{
return Integer(left.i << right.i);
}
const Integer operator>>(const Integer& left, const Integer& right)
{
return Integer(left.i >> right.i);
}
Integer& operator+=(Integer& left, const Integer& right)
{
//if(&left == &right){}
left.i += right.i;
return left;
}
Integer& operator-=(Integer& left, const Integer& right)
{
left.i -= right.i;
return left;
}
Integer& operator*=(Integer& left, const Integer& right)
{
left.i *= right.i;
return left;
}
Integer& operator/=(Integer& left, const Integer& right)
{
ASSERT(right.i != 0);
left.i /= right.i;
return left;
}
Integer& operator%=(Integer& left, const Integer& right)
{
ASSERT(right.i != 0);
left.i %= right.i;
return left;
}
Integer& operator^=(Integer& left, const Integer& right)
{
left.i ^= right.i;
return left;
}
Integer& operator&=(Integer& left, const Integer& right)
{
left.i &= right.i;
return left;
}
Integer& operator|=(Integer& left, const Integer& right)
{
left.i |= right.i;
return left;
}
Integer& operator>>=(Integer& left, const Integer& right)
{
left.i >>= right.i;
return left;
}
Integer& operator<<=(Integer& left, const Integer& right)
{
left.i <<= right.i;
return left;
}
int operator==(const Integer& left, const Integer& right)
{
return left.i == right.i;
}
int operator!=(const Integer& left, const Integer& right)
{
return left.i != right.i;
}
int operator<(const Integer& left, const Integer& right)
{
return left.i < right.i;
}
int operator>(const Integer& left, const Integer& right)
{
return left.i > right.i;
}
int operator<=(const Integer& left, const Integer& right)
{
return left.i <= right.i;
}
int operator>=(const Integer& left, const Integer& right)
{
return left.i >= right.i;
}
int operator&&(const Integer& left, const Integer& right)
{
return left.i && right.i;
}
int operator||(const Integer& left, const Integer& right)
{
return left.i || right.i;
}
成员函数形式:
// Member functions
class Byte {
unsigned char b;
public:
Byte(unsigned char bb = 0) : b(bb) {}
const Byte operator+(const Byte& right) const
{
return Byte(b + right.b);
}
const Byte operator-(const Byte& right) const
{
return Byte(b - right.b);
}
const Byte operator*(const Byte& right) const
{
return Byte(b * right.b);
}
const Byte operator/(const Byte& right) const
{
ASSERT(right.b != 0);
return Byte(b / right.b);
}
const Byte operator%(const Byte& right) const
{
ASSERT(right.b != 0);
return Byte(b % right.b);
}
const Byte operator^(const Byte& right) const
{
return Byte(b ^ right.b);
}
const Byte operator&(const Byte& right) const
{
return Byte(b & right.b);
}
const Byte operator|(const Byte& right) const
{
return Byte(b | right.b);
}
const Byte operator<<(const Byte& right) const
{
return Byte(b << right.b);
}
const Byte operator>>(const Byte& right) const
{
return Byte(b >> right.b);
}
Byte& operator=(const Byte& right)
{
if( this == &right )
{
// self-assignment 代码检测自赋值
return *this;
}
b = right.b;
return *this;
}
Byte& operator+=(const Byte& right)
{
b += right.b;
return *this;
}
Byte& operator-=(const Byte& right)
{
b -= right.b;
return *this;
}
Byte& operator*=(const Byte& right)
{
b *= right.b;
return *this;
}
Byte& operator/=(const Byte& right)
{
ASSERT(right.b != 0);
b /= right.b;
return *this;
}
Byte& operator%=(const Byte& right)
{
ASSERT(right.b != 0);
b %= right.b;
return *this;
}
Byte& operator^=(const Byte& right)
{
b ^= right.b;
return *this;
}
Byte& operator&=(const Byte& right)
{
b &= right.b;
return *this;
}
Byte& operator|=(const Byte& right)
{
b |= right.b;
return *this;
}
Byte& operator>>=(const Byte& right)
{
b >>= right.b;
return *this;
}
Byte& operator<<=(const Byte& right)
{
b <<= right.b;
return *this;
}
int operator==(const Byte& right) const
{
return b == right.b;
}
int operator!=(const Byte& right) const
{
return b != right.b;
}
int operator<(const Byte& right) const
{
return b < right.b;
}
int operator>(const Byte& right) const
{
return b > right.b;
}
int operator<=(const Byte& right) const
{
return b <= right.b;
}
int operator>=(const Byte& right) const
{
return b >= right.b;
}
int operator&&(const Byte& right) const
{
return b && right.b;
}
int operator||(const Byte& right) const
{
return b || right.b;
}
};
在运算符重载中,所有赋值运算符都有代码检测自赋值,其最重要的地方是operator=,有些地方不需要,如operator+=。
3、不常用的运算符
下标运算符operator[],必须是成员函数并且它只接受一个参数。
运算符new 和delete用于控制动态存储分配并能按许多种不同的方法进行重载。
逗号运算符“operator,”调用的目标不是函数参数表,而是被逗号分隔开的、没有被括号括起来的对象。除了使语言保持一致性外,没有许多实际用途。
operator->,当希望一个对象表象得像一个指针时,通常用它。由于这样一个对象比一般的指针有着更多与生俱来的灵巧性,于是常被称作灵巧指针。如果想用类包装一个指针以使指针安全,或者在迭代器普通的用法中,这样做会特别有用。指针间接引用运算符一定是一个成员函数,它必须返回一个对象(或对象的引用),该对象也有一个指针间接引用运算符;或必须返回一个指针,被用于选择指针间接引用运算符箭头所指向的内容。
operator->*是一个二元运算符(指向成员的指针间接引用运算符),它是专为模仿内部数据类型的成员指针行为而提供的。在定义operator->*时要注意它必须返回一个对象,对于这个对象,可以用正在调用的成员函数为参数调用operator()。operator()的函数调用必须是成员函数,它是唯一的允许在它里面有任意个参数的函数。要想创建一个operator->*,必须先创建带有operator()类(这是operator->*将返回对象的类)。
4、不能重载的运算符
这样限制的原因通常是出于安全的考虑:如果这些运算符被重载,将会破坏安全机制,使事情变得困得或混淆现有的习惯。
1)成员选择operator.;
2)成员指针间接引用operator.*;
3)域运算符::;
4)sizeof运算符;
5)条件运算符?:;
注意,运算符重载中,C/C++中没有Fotran语言的求幂运算符operator**,不存在用户定义的运算符(难以决定其优先级),不能改变优先级规则。
5、非成员运算符
运算符可能是成员运算符或非成员运算符,如果没有什么差异,选择成员运算符,这样做强调了运算符和类的联合。当左侧操作数是当前的对象时,运算符会工作得很好。
但是有时左侧运算符特别是类的对象(这种情况通常出现在输入输出流重载operator<< 和>>,因为输入输出流是一个基本C++库,我们可能想为定义的大部分类重载运算符),就需要非成员运算符。例如:
#include <iostream>
using namespace std;
class IntArray{
enum { sz = 5 };
int i[sz];
public:
IntArray() { memset(i, 0, sz * sizeof(*i)); }
int& operator[](int x)
{
ASSERT(x >= 0 && x < sz);
return i[x];
}
friend ostream& operator<<(ostream& os, const IntArray& ia);
friend istream& operator>>(istream& is, IntArray& ia);
};
ostream&
operator<<(ostream& os, const IntArray& ia)
{
for (int j = 0; j < ia.sz; ++j)
{
os << ia.i[j];
if ( j != ia.sz - 1 )
{
os << ",";
}
}
os << endl;
return os;
}
istream& operator>>(istream& is, IntArray& ia)
{
for (int j = 0; j < ia.sz; ++j)
{
is >> ia.i[j];
}
return is;
}
三、参数和返回值
虽然可以使用任何需要的方式传递和返回参数,但在大部分情况下应选择如下模式:
1)对于任何函数参数,如果仅需要从参数中读而不改变它,默认地应当作为const引用来传递它;
2)返回值的类型取决于运算符的具体含义,如果使用该运算符的结果是产生一个新值,就需要产生一个作为返回值的新对象;
3)所有赋值运算符均改变左值,因此所有赋值运算符的返回值对于左值应该是非常量引用;
4)对于逻辑运算符,至少得到一个int返回值,最好是bool返回值;
5)对于自增和自减,让前缀版本是非常量的(前缀版本返回改变后的对象),后缀版本是常量的(后缀版本返回改变之前的对象);
6)临时对象自动被定为常量。
返回值优化:
通过传值方式返回要创建新对象时,应注意使用的形式,如在operator+:
return Integer(left.i + right.i);
这是临时对象语法,它是说:“创建一个Integer对象并返回它”。如果创建一个有名字的局部对象并返回它,结果是不一样的,如下:
Integer tmp(left.i + right.i);
return tmp;
将发生三件事:
1)创建tmp对象;
2)拷贝构造函数把tmp拷贝到外部返回值的存储单元里;
3)当tmp在作用域的结尾是调用析构函数。
相反,“返回临时对象”的方式是完全不同的,当编译器看到我们这样做时,它明白对创建的对象没有其他需求,只是返回它,所以编译器直接地把这个对象创建在外部返回值的内存单元。因为不是真正创建一个局部对象,所以仅需要一个普通构造函数调用(不需要拷贝构造函数),且不会调用析构函数,故效率比较高。这种方式常被称作返回值优化。
四、引用计数
如果对象需要大量的内存或过高的初始化,我们也许想避免拷贝(拷贝构造和operator=),解决办法是使用引用计数。可以使一块存储单元具有智能,它知道有多少对象指向它。拷贝构造函数或赋值运算意味着把另外的指针指向现在的存储单元并增加引用计数,消除意味着减小引用计数,如果引用计数为0则意味着销毁这个对象。如果向对象执行写入操作,则需使用写拷贝技术。在向这块存储单元写之前,应该确信没有其他人使用它,如果引用计数大于1,在写之前必须拷贝这块存储单元,这样就不会影响他人了。
五、自动类型转换
在C/C++中,如果编译器看到一个表达式或函数调用使用了一个不合适的类型,它经常会执行一个自动类型转换。在C++中,可以通过定义自动类型转换函数来为用户定义类型达到相同效果(特殊类型的构造函数和重载的运算符)。
1、构造函数转换
如果定义一个构造函数,这个构造函数能把另一类型对象(或引用)作为它的单个参数,那么这个构造函数允许编译器执行自动类型转换。但是有时通过构造函数自动类型可能出现问题,为了避开这个麻烦,可以通过在前面加关键字explicit(只能用于构造函数)。
2、运算符转换
可以创建一个成员函数,这个函数通过在关键字operator后紧跟想要转换到的类型的方法,将当前类型转换为希望的类型。
注意,用构造函数转换,目的类执行转换;使用运算符,是源类执行转换。使用构造函数技术没有办法实现从用户定义类型向内置类型转换,这只有运算符重载可能做到。