从微机原理中我们就知道了精简指令集计算机RISC,Reduced Instruction Set Computer中的指令分为操作符和操作数。其中操作按照参数个数分为:一元操作符和二元操作符。发展到C++等高级语言时,操作符又可以根据功能分为:算术、关系、位操作、逻辑运算、成员运算、函数调用、索引(下标)、递增、递减操作符。为了理解操作符重载,简单说一些重要的操作符。
² 一元操作符(Unary Operators)
! |
逻辑反 |
& |
取地址 |
~ |
异或 |
* |
指针去引用(dereference) |
+ |
一元加 |
++ |
递增 |
- |
一元负 |
-- |
递减 |
² 二元操作符(Binary Operator)
二元操作符的数目远大于一元操作符,如+、-、*、/、<>、!=、==、!=等,不再详细列出。
² 赋值操作符(Assignment Operators)
赋值操作符,即=,是编译器提供的默认操作符,如果操作符一个类没有赋值操作符,将使用编译器的默认赋值操作符。另外,默认操作符不能被继承,即父类虽然重载了赋值操作符,但是如果派生类没有重载此操作符,派生类将还是使用默认赋值操作符,而不是父类的的赋值操作符。最后,赋值操作符不能作为静态成员函数存在,即只能对对象实例使用此操作符。
² 函数调用操作符(Function Call Operators)
函数调用操作符,即(),常见于标准模板库STL,Standard Template Library的仿函数定义。STL的大多数算法都有带仿函数版本;所谓仿函数即是重载了函数调用操作符的类,此操作符的功能就是调用函数。
² 下标操作符(subscripting)
下标操作符,即[],用于快速引用数组元素。所有的STL容器都提供下标操作符,如vector,如果定义了vector<int> vInts,就能使用vInts[i]的形式来访问其元素。
² 成员访问操作符(Member Access Operators)
成员访问操作符有2个:->和.,后者不能重载,主要用于访问成员变量。->的重载也比较少见,大多数情况下都不需要重载成员访问操作符。
² 递增和递减操作符(Increment and Decrement Operators)
递增操作符,即++;递减操作符,即--。需要注意的时,对于复合类型(非int等简单类型),操作符在前与后有着效率上的区分,如++a和a++,后者由于构造临时对象,所以效率更低。
² 不能重载的操作符
对于大多数编译器而言,有一些操作符都是不能重载的。
. |
成员访问 |
.* |
Pointer-to-member selection |
:: |
作用域 |
?: |
条件赋值 |
# |
转换位字符串的预处理 |
## |
预处理连接 |
对不同编译器而言,还有别的操作符不能重载。
² 为何进行操作符重载
原因主要有这样一些:
Ø 数学计算的需要,如为复数重载+、-、*、/;
Ø 使程序变得精练,如重载<<、>>操作符使调试类变得容易;
Ø 标准库函数的需要,如仿函数需要重载(),map需要重载<;
Ø 对象间操作的需要。
Ø 操作符重载的通用原则
从编程实践中,人们总结了以下一些原则,来指导对操作符的重载:
Ø 不能定义新的操作符,只能重载;
Ø 不能重载针对简单类型的操作符;
Ø 操作符重载,只能在成员函数和友元(全局)两种方法中选择一种;
Ø 操作符的优先级等熟悉不会发生变化;
Ø 重载的操作符不能有默认参数;
Ø 除了赋值操作符和成员访问操作符.,其它操作符都能继承;
Ø 基于成员函数方式的操作符重载的第一个残杀都是调用者类型(或者派生类),不能对第一个参数进行转换。
² 流操作符<<和>>
流操作符<<和>>分别对应C++标准库中的istream和ostream,一般而言,应该为大多数类重载此操作符;因为输出操作符可用于测试和调试,并方便调用库函数的上层应用者。另外,输出输入操作符函数一种对象持久化的低成本实现方式。
要重载流操作符,通常将此操作符定义为类的友元函数,否则类本身必须使istream和ostream的派生类;另外在重载操作符时,还通常返回istream&和ostream&,这样返回值可以作为下一个操作符的输入,从而实现操作符的链接,如cout << a1 << a2 << endl,其中a1,a2分别为实现了流操作符重载的类A的实例。
流操作符的第一个参数时istream和ostream的引用,这是因为需要更新流的内部状态;而第二个参数为类的引用,这对<<而言是为了效率(可以定义为常量引用),对>>而言是需要接受修改的内容。
² 赋值操作符=
赋值操作符的最重要问题就是深Copy的问题,即对指针成员,需要重新申请空间,在copy内容,以免两个实例的成员指向同一块地址;默认赋值操作符是浅Copy的,这时指针成员会指向同一块地址。
其次一个小技巧:需要在重载时返回*this的引用,以实现连续赋值,即a1=a2=1。
² 转换操作符()
实现类型间转换通常有2种转换方式(除去强制类型转换,如static_cast):通过构造函数和转换操作符。通过构造函数比较容易,如:
Class MyClass
{
public:
MyClass(const string&);
};
即实现了把类型string转换为类型MyClass。
通过转换操作符,需要在类中重载(),如:
Class MyClass
{
public:
MyClass(const string&);
operator string() const;
};
这样就能在需要的时候调用转换操作符实现转换,如string str = (string)my_class_instance。
² 逻辑操作符&&和||
这类操作符绝不能够重载,虽然编译器允许此类重载;否则,程序会变得晦涩难懂。如if((p!=0) && (strlen(p)>10)的语句中,第一个条件为true,才会检查第2个参数。如果对全局操作符&&重载,以使在任何条件下都要检查第2个参数,如if(operator&&(p!=0, strlen(p)>10)),虽然可以做,但是理解混乱了。
一般而言,其它操作符(除了算术运算符、赋值操作符、下标操作符、流操作符、函数调用操作符等)最好不要去重载,因为这会造成程序可读性的降低。另外,反过来讲,用函数实现该功能却是一个更好的选择。
² Effective C++,Scott Meyers.
² More Effective C++, Scott Meyers.
² STL源码剖析,侯捷。