运算符重载是C++极为重要的语言特性之中的一个。本文将用代码实例回答——C++哪些运算符能够重载?怎样重载?实现运算符重载时须要注意哪些?

哪些运算符能够重载,哪些不可重载?

C++98,C++0x,C++11对“哪些运算符重载能够重载”有一致的规定。详细例如以下:

当中,非常少使用的是“,”(逗号运算符)。

标准相同规定了不可重载的运算符

当中,“::”是作用域运算符。

“?:”是条件运算符。

两个较少使用的运算符是 .* 和 ->* 各自是:

.* 对象调用成员函数指针;

->* 对象指针调用成员函数指针。

标准还指出,有的运算符能够同一时候支持“一元运算”和“二元运算”:

下文通过实现几个模拟内置类型的类来展示详细的运算符重载方法应当怎样实现。

一些注意事项

实现运算符重载时应注意:

  1. 运算符重载不改变运算符的优先级与结合性,如/的优先级比-高;
  2. 运算符重载不改变部分运算符对操作数个数的限制,如+仅仅能有两个操作数;

模拟整型Integer

回忆一下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

模拟指针Pointer

再回忆一下原生的指针支持那些运算?

详细有:

*,解引用。从T*得到T

[],下标运算

++,自增

--,自减

+,加法

-,减法

有一点值得注意的是:

  1. 每种类型都相应一种指针,如int相应int*;
  2. 解引用和下标运算的返回结果能够做“左值”(赋值运算符左边的值。即能够赋值)。
  3. 指针的加减,-是以指针当前类型的大小为单位的。即:若有指针p,则 (size_t)(p + 1) == (size_t)p + sizeof(*p)

要实现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

以上測试testPointer并没实用到operator->,以下单独測试operator->:

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

关于operator new和operator delete的重载,下次另外写博客阐释。

C++哪些运算符重载能够重载?的更多相关文章

  1. c/c++ 重载运算符 ==和!=的重载

    重载运算符 ==和!=的重载 问题:假如有一个类似于vector的类,这个类只能存放string,当有2个这个类的对象时,如何比较这2个对象. 自己重载==和!= 代码(重载==,!=) #inclu ...

  2. C++中vector::data()使用心得和对自定义类型指针运算符的默认重载

    一.C++ vector::data()函数 返回值类型:vector的基类 返回值:Returns a pointer such that [data(), data() + size()] is ...

  3. Swift开发第六篇——操作运算符也可以重载& func 的参数修饰

    本篇分为两部分: 1.Swift 中重载操作运算符的使用 2.Swfit 中 func 的参数修饰 1.Swift 中重载操作运算符的使用 与别的语言不同,Swift 支持运算符的重载,运算符指的是“ ...

  4. C++入门经典-例7.10-运算符的重载,重载加号运算符

    1:曾经介绍过string类型的数据,它是C++标准模版库提供的一个类.string类支持使用加号“+”连接两个string对象.但是使用两个string对象相减确实非法的,其中的原理就是C++所提供 ...

  5. 多关键字排序(里面有关于操作符(<<运算符 和 >>运算符 )的重载)

    一种排序 时间限制:3000 ms | 内存限制:65535 KB 难度:3   描述 现在有很多长方形,每一个长方形都有一个编号,这个编号可以重复:还知道这个长方形的宽和长,编号.长.宽都是整数:现 ...

  6. C用函数指针模拟重载 C++重载

    C中为什么不支持重载,即同一作用域内不允许出现同名函数? 我们都知道重载是c++面向对象的特性.c语言中是不存在的.所谓重载简单来说就是一个函数名可以实现不同的功能,要么输入参数不同或者参数个数不同, ...

  7. java重载和重载的区别

    重载 public class A{     public void test(){}     public void test(int num){}     public void test(Str ...

  8. 第34课.数组操作符的重载("[]"重载)

    1.问题:string类对象还具备c方式字符串的灵活性吗?还能直接访问单个字符吗? 答案:可以按照c字符串的方式使用string对象 string s = "a1b2c3d4e"; ...

  9. C#高级编程笔记2016年10月12日 运算符重载

    1.运算符重载:运算符重重载的关键是在对象上不能总是只调用方法或属性,有时还需要做一些其他工作,例如,对数值进行相加.相乘或逻辑操作等.例如,语句if(a==b).对于类,这个语句在默认状态下会比较引 ...

随机推荐

  1. 微软Xbox One无线手柄控制机器人

    ROS中的joy包提供了游戏手柄的驱动,并且包含joy_node节点,这一节点可以发布包含手柄按钮和轴信息的Joy消息.在终端中输入下面命令,安装joy包: $ sudo apt-get instal ...

  2. Ajax学习(一)

    ajax是什么? Asynchronous Javascript And XML:异步的js和xml 他能使得js访问服务器,而且是异步的. 服务器给客户端的响应一般是整个页面,一个完整的html页面 ...

  3. VS2010 lib和dll导出路径设置

    创建库文件工程时往往需要设置.lib文件和.dll文件的路径. 假设一个solution对应了多个工程,然而他们共用一些库,就可以在solution文件夹下分别添加两个文件夹lib和bin(其实放在一 ...

  4. Androidstudio安装问题

    非常多人在用Android Studio的时候会出现"'tools.jar' seems to be not in Android Studio classpath.Please ensur ...

  5. 第十九章,指针小练习(C++)

    #include <iostream> int main(int argc, char** argv) { int i = 1; int *n ; n=&i;//*n=i 这样的赋 ...

  6. ios中自定义button

    自定义button #import <UIKit/UIKit.h> #define KFont 15 @interface DIYButton : UIButton @property(n ...

  7. 获取可用的处理器(CPU)核数【转】

    linux下获取cpu核数,sysconf(_SC_NPROCESSORS_CONF),,, from:红黑联盟,https://www.2cto.com/kf/201210/164480.html ...

  8. Rust 之 cargo(项目构建和包管理工具)

    如果食用cargo来进行项目构建: 1. 执行 cargo new hello_cargo --bin ,执行完上面的操作之后,我们切换到hell_cargo目录下,可以看到一个文件(Cargo.to ...

  9. 【Android】Android解析短信操作

    目录结构: contents structure [-] 获取短信 发送短信 1.获取短信 在AndroidManifest.xml中,添加权限: <uses-permission androi ...

  10. Tomcat之如何使用Nginx进行集群部署

    目录结构: contents structure [+] 1,为什么需要集群 2,如何使用Nginx部署tomcat集群 2.1,下载Nginx 2.2,在同一台电脑上部署多个Tomcat服务器 2. ...