运算符重载是C++极为重要的语言特性之一,本文将用代码实例回答——C++哪些运算符可以重载?如何重载?实现运算符重载时需要注意哪些?
C++98,C++0x,C++11对“哪些运算符重载可以重载”有一致的规定,具体如下:
其中,很少使用的是“,”(逗号运算符)。
标准同样规定了不可重载的运算符:
其中,“::”是作用域运算符,
“?:”是条件运算符。
两个较少使用的运算符是 .* 和 ->* 分别是:
.* 对象调用成员函数指针;
->* 对象指针调用成员函数指针;
标准还指出,有的运算符可以同时支持“一元运算”和“二元运算”:
下文通过实现几个模拟内置类型的类来展示具体的运算符重载方法应当如何实现。
实现运算符重载时应注意:
回想一下C++里整型支持支持那些运算?具体有:
算术运算:
正,如 +a;负,如 -a;
加,如 a + b;减,如 a - b;乘,如 a * b;除,如 a / b;取余(模),如 a % b;
自增自减(整型特有):
自增,如 a++,++a;自减,如 a--,--a;
比较运算:
大于,如 a > b;小于,如 a < b;
等于,如 a == b;不等于,如 a != b;
大于等于,如 a >= b;小于等于,如 a <= b;
位运算:
按位取反,如 ~a;
左移,如 a << 2;右移,如 a >> 3;
按位与,如 a & b;按位或,如 a | b;按位异或,如 a ^ b;
赋值运算:
赋值,a = 5;
复合赋值:
+=,-=,*=,/=,%=,(算数运算与赋值运算复合)
&=,|=,^=,<<=,>>=,(位运算与赋值运算复合)
下面是这个Integer的代码:
class Integer { public: Integer(int ival) : value(ival) {} Integer(const Integer& iobj) : value(iobj.value) {} // operator int() { return value; } // conversion to built-in int Integer operator+() const { return Integer(value); } Integer operator-() const { return Integer(-value); } Integer operator+(const Integer& rhs) const { return Integer(value + rhs.value); } Integer operator-(const Integer& rhs) const { return Integer(value - rhs.value); } Integer operator*(const Integer& rhs) const { return Integer(value * rhs.value); } Integer operator/(const Integer& rhs) const { return Integer(value / rhs.value); } Integer operator%(const Integer& rhs) const { return Integer(value % rhs.value); } // prefix Integer operator++() { return Integer(++value); } Integer operator--() { return Integer(--value); } // suffix Integer operator++(int) { int old = value; value++; return Integer(old); } Integer operator--(int) { int old = value; value--; return Integer(old); } // compare: bool operator<(const Integer& rhs) const { return value < rhs.value; } bool operator>(const Integer& rhs) const { return value > rhs.value; } bool operator==(const Integer& rhs) const { return value == rhs.value; } bool operator!=(const Integer& rhs) const { return value != rhs.value; } bool operator<=(const Integer& rhs) const { return value <= rhs.value; } bool operator>=(const Integer& rhs) const { return value >= rhs.value; } // bit operations: Integer operator~() const { return Integer(~value); } Integer operator<<(unsigned n) const { return Integer(value << n); } Integer operator>>(unsigned n) const { return Integer(value >> n); } Integer operator&(const Integer& rhs) const { return Integer(value & rhs.value); } Integer operator|(const Integer& rhs) const { return Integer(value | rhs.value); } Integer operator^(const Integer& rhs) const { return Integer(value ^ rhs.value); } // assignment: Integer operator=(const Integer& rhs) { return value = rhs.value; } // compound assignment: Integer operator+=(const Integer& rhs) { return value += rhs.value; } Integer operator-=(const Integer& rhs) { return value -= rhs.value; } Integer operator*=(const Integer& rhs) { return value *= rhs.value; } Integer operator/=(const Integer& rhs) { return value /= rhs.value; } Integer operator%=(const Integer& rhs) { return value %= rhs.value; } Integer operator&=(const Integer& rhs) { return value &= rhs.value; } Integer operator|=(const Integer& rhs) { return value |= rhs.value; } Integer operator^=(const Integer& rhs) { return value ^= rhs.value; } Integer operator<<=(const Integer& rhs) { return value <<= rhs.value; } Integer operator>>=(const Integer& rhs) { return value >>= rhs.value; } // private: int value; };
实现运算符重载函数时,需要注意的是末尾是否要加const?
这取决与操作是否会改变当前对象的成员值,如果不改变则不加,改变则加。
Integer类只是为了展示如何使用运算符重载,并没有多少实用价值。
一下是Integer类的测试:
void testInteger() { Integer i = 123; #define SEPRATER ":\t" #define TRACE_INTEGER(iobj) printf(#iobj SEPRATER "%d\n", (iobj).value) #define TRACE_BOOL(exp) printf(#exp SEPRATER "%s\n", (exp) ? "true" : "false") #define TRACE_HEX(iobj) printf(#iobj SEPRATER "%p\n", (iobj).value) TRACE_INTEGER(i); TRACE_INTEGER(+i); TRACE_INTEGER(-i); Integer j = 5; TRACE_INTEGER(i+j); TRACE_INTEGER(i-j); TRACE_INTEGER(i*j); TRACE_INTEGER(i/j); TRACE_INTEGER(i%j); TRACE_INTEGER(++i); TRACE_INTEGER(i); TRACE_INTEGER(--i); TRACE_INTEGER(i); TRACE_INTEGER(i++); TRACE_INTEGER(i); TRACE_INTEGER(i--); TRACE_INTEGER(i); TRACE_BOOL(i>j); TRACE_BOOL(i<j); TRACE_BOOL(i==j); TRACE_BOOL(i!=j); TRACE_BOOL(i>=j); TRACE_BOOL(i<=j); TRACE_HEX(i); TRACE_HEX(~i); TRACE_HEX(i<<4); TRACE_HEX(i>>4); TRACE_HEX(i<<24); TRACE_HEX(i & ~0xF); // i & ~0xF <<== same as ==>> i & Integer(~0xF), because C++ implicit conversion. TRACE_HEX(i | 0xF0); TRACE_HEX(i ^ 0xF0); TRACE_INTEGER(i); TRACE_INTEGER(j); TRACE_INTEGER(i=j); TRACE_INTEGER(i+=j); TRACE_INTEGER(i-=j); TRACE_INTEGER(i*=j); TRACE_INTEGER(i/=j); j = 3; TRACE_INTEGER(i%=j); }该测试的输出如下:
i: 123 +i: 123 -i: -123 i+j: 128 i-j: 118 i*j: 615 i/j: 24 i % : 3 ++i: 124 i: 124 --i: 123 i: 123 i++: 123 i: 124 i--: 124 i: 123 i>j: true i<j: false i==j: false i!=j: true i>=j: true i<=j: false i: 0000007B ~i: FFFFFF84 i<<4: 000007B0 i>>4: 00000007 i<<24: 7B000000 i & ~0xF: 00000070 i | 0xF0: 000000FB i ^ 0xF0: 0000008B i: 123 j: 5 i=j: 5 i+=j: 10 i-=j: 5 i*=j: 25 i/=j: 5 i%=j: 2
再回想一下原生的指针支持那些运算?
具体有:
*,解引用,从T*得到T
[],下标运算
++,自增
--,自减
+,加法
-,减法
有一点值得注意的是:
要实现1,Pointer类型必须实现为类模板;
Pointer实现为类模板,它的数据成员(data member)就可以是原生指针,可以很自然的支持原生指针的加减运算。
据此实现的Pointer类如下:
template <typename T> class Pointer { public: Pointer() : ptr_(0) {} Pointer(T* ptr) : ptr_(ptr) {} // operator T*() { return ptr_; } // implicit conversion. T* get() const { return ptr_; } T& operator*() const { return *ptr_; } T* operator->() const { return ptr_; printf("operator->()\n"); } T& operator[](int offset) const { return ptr_[offset]; } Pointer<T> operator++() { return Pointer(++ptr_); } // prefix Pointer<T> operator--() { return Pointer(--ptr_); } // prefix Pointer<T> operator++(int) { return Pointer(ptr_++); } // suffix Pointer<T> operator--(int) { return Pointer(ptr_--); } // suffix Pointer<T> operator+=(int off) { return Pointer(ptr_ += off); } Pointer<T> operator-=(int off) { return Pointer(ptr_ -= off); } Pointer<T> operator+(int off) const { return Pointer(ptr_ + off); } Pointer<T> operator-(int off) const { return Pointer(ptr_ - off); } // private: T* ptr_; };Pointer类仅模拟指针的一般运算,并没有考虑“通过指针进行资源管理”这一主题,所有也没有保证delete ptr的实际行为。
以下是该类的测试程序:
template<typename T> ostream& operator<<(ostream& out, const Pointer<T>& ptr) { out << ptr.ptr_; return out; } //#define TRACE(fmt, exp) printf(#exp ":\t" fmt, (exp)) #define TRACE(exp) cout << #exp << ":\t" << (exp) << endl #define ARRAY_SIZE(a) sizeof(a)/sizeof(a[0]) int ia[] = { 123, 456, 789, 111, 222, 333 }; void testPointer() { for(int i=0; i<ARRAY_SIZE(ia); ++i) { printf("%p: %d\n", &ia[i], ia[i]); } Pointer<int> ptr = &ia[0]; TRACE(ptr); TRACE(ia); TRACE(*ptr); TRACE(ptr[1]); TRACE(++ptr); TRACE(*ptr); TRACE(--ptr); TRACE(*ptr); TRACE(ptr++); TRACE(*ptr); TRACE(ptr--); TRACE(*ptr); TRACE(ptr+=2); TRACE(*ptr); TRACE(ptr-=2); TRACE(*ptr); ptr = &ia[3]; TRACE(ptr+2); TRACE(*(ptr+2)); TRACE(ptr-2); TRACE(*(ptr-2)); ptr[0] = 555; TRACE(ptr[0]); *ptr = 666; TRACE(*ptr); }
0x603090: 123 0x603094: 456 0x603098: 789 0x60309c: 111 0x6030a0: 222 0x6030a4: 333 ptr: 0x603090 ia: 0x603090 *ptr: 123 ptr[1]: 456 ++ptr: 0x603094 *ptr: 456 --ptr: 0x603090 *ptr: 123 ptr++: 0x603090 *ptr: 456 ptr--: 0x603094 *ptr: 123 ptr+=2: 0x603098 *ptr: 789 ptr-=2: 0x603090 *ptr: 123 ptr+2: 0x6030a4 *(ptr+2): 333 ptr-2: 0x603094 *(ptr-2): 456 ptr[0]: 555 *ptr: 666
struct Foo { int id; static int count; Foo() : id(++count) { printf("Foo::Foo(%p)\n", this); } ~Foo() { printf("Foo::~Foo(%p)\n", this); } void show() { printf("Foo.show(%p): %d\n", this, id); } }; int Foo::count = 0; void testAccess() { printf("\n%s():\n", __FUNCTION__); Pointer<Foo> fptr = new Foo(); // test operator-> fptr->show(); // access member function. TRACE(fptr->id); // access data member. delete fptr.get(); }该测试程序的输出为:
testAccess(): Foo::Foo(0x133a010) Foo.show(0x133a010): 1 fptr->id: 1 Foo::~Foo(0x133a010)
上面的两个类基本已经覆盖了大部分的运算符重载,下面展示几个“罕见”的运算符重载:
#define TRACE_CALL puts(__FUNCTION__) class Operand { public: void operator,(const Operand& rhs) const { TRACE_CALL; } void operator,(int a) const { TRACE_CALL; } // void operator,(int a, int b) const { TRACE_CALL; } // ERROR: operator, 有且仅有一个参数 void operator->*(int a) const { printf("%s(%d)\n", __FUNCTION__, a); } };测试程序:
void testOperand() { Operand op1, op2; op1, op2; // operator, op1->*123; // operator->* }
程序输出:
operator, operator->*(123)
关于operator new和operator delete的重载,下次另外写博客阐释。