运算符重载
运算符重载就是重新解释作用于特定结构或者类的一元或二元运算符的含义,被重新解释的运算符是作为结构或类的成员实现的。通过重载,我们只需使用原来的语法就可以得到我们想要的结果。一元运算符重载
可重载的一元运算符
op | opfunc |
---|---|
-e | opNeg |
+e | opPos |
~e | opCom |
e++ | opPostInc |
e-- | opPostDec |
cast(type)e | opCast |
假设可被重载的运算符为 op ,对应的类或结构成员函数名为 opfunc ,语法:
op a被解释为:
a.opfunc()其中 a 是类或结构的引用。
重载 ++e 和 --e
因为按照定义 ++ e 在语义上等价于 ( e += 1) ,表达式 ++ e 会被重写为 ( e += 1) ,然后按照这种形式应用运算符重载。-- e 的情形与之类似。示例
class A { int opNeg(); } A a; -a; // 等价于 a.opNeg();
class A { int opNeg(int i); } A a; -a; // 等价于 a.opNeg(), 这是错误
重载 cast(type)e
会调用成员函数 e. opCast() ,并且 opCast() 的返回值被隐式地转换为 type 。因为不能基于返回值重载,所以每个结构或者类只能有一个 opCast 函数。重载转型操作符并不影响隐式转型,只能用于显式转型。struct A { int opCast() { return 28; } } void test() { A a; long i = cast(long)a; // i 被设为 28L void* p = cast(void*)a; // 错误,不能隐式地转换 int 为 void* int j = a; // 错误,不能隐式地转换 A 为 int }
二元运算符重载
可重载的二元运算符
op | 可交换? | opfunc | opfunc_r |
---|---|---|---|
+ | yes | opAdd | opAdd_r |
- | no | opSub | opSub_r |
* | yes | opMul | opMul_r |
/ | no | opDiv | opDiv_r |
% | no | opMod | opMod_r |
& | yes | opAnd | opAnd_r |
| | yes | opOr | opOr_r |
^ | yes | opXor | opXor_r |
<< | no | opShl | opShl_r |
>> | no | opShr | opShr_r |
>>> | no | opUShr | opUShr_r |
~ | no | opCat | opCat_r |
== | yes | opEquals | - |
!= | yes | opEquals | - |
< | yes | opCmp | - |
<= | yes | opCmp | - |
> | yes | opCmp | - |
>= | yes | opCmp | - |
+= | no | opAddAssign | - |
-= | no | opSubAssign | - |
*= | no | opMulAssign | - |
/= | no | opDivAssign | - |
%= | no | opModAssign | - |
&= | no | opAndAssign | - |
|= | no | opOrAssign | - |
^= | no | opXorAssign | - |
<<= | no | opShlAssign | - |
>>= | no | opShrAssign | - |
>>>= | no | opUShrAssign | - |
~= | no | opCatAssign | - |
给定一个二元可重载运算符 op 和它对应的名为 opfunc 和 opfunc_r 的类或结构成员函数,则下面的语法:
a op b会按顺序应用下面的规则,来决定使用那种形式:
- 表达式被重写为下面两种形式:
a.opfunc(b) b.opfunc_r(a)
如果 a.opfunc 或 b.opfunc_r 函数两者之一存在,就应用重载解析从中选出一个最合适的形式。如果函数存在,但是参数却不匹配,则被视为错误。 - 如果运算符是可交换的,会尝试下面的形式:
a.opfunc_r(b) b.opfunc(a)
- 如果 a 或者 b 是结构或者类的引用,就是错误。(译注:即如果类或结构没有重载上述某个运算符,就不能进行相应的运算。)
示例
class A { int opAdd(int i); } A a; a + 1; // 等价于 a.opAdd(1) 1 + a; // 等价于 a.opAdd(1)
class B { int opDiv_r(int i); } B b; 1 / b; // 等价于 b.opDiv_r(1)
class A { int opAdd(int i); } class B { int opAdd_r(A a); } A a; B b; a + 1; // 等价于 a.opAdd(1) a + b; // 等价于 b.opAdd_r(a) b + a; // 等价于 b.opAdd_r(a)
class A { int opAdd(B b); int opAdd_r(B b); } class B { } A a; B b; a + b; // 等价于 a.opAdd(b) b + a; // 等价于 a.opAdd_r(b)
class A { int opAdd(B b); int opAdd_r(B b); } class B { int opAdd_r(A a); } A a; B b; a + b; // 歧义:a.opAdd(b) 还是 b.opAdd_r(a) ? b + a; // 等价于 a.opAdd_r(b)
重载 == 和 !=
这两个运算符都使用 opEquals() 函数。表达式 (a == b) 被重写为 a.opEquals(b) ,而 (a != b) 被重写为 !a.opEquals(b) 。成员函数 opEquals() 是 Object 的一部分,定义为:
int opEquals(Object o);这样所有的类对象都有 opEquals() 。
如果结构没有声明 opEquals() 函数,就会按位比较两个结构的内容以测试相等还是不相等。
重载 <, <=, > 和 >=
这些比较运算符都使用 opCmp() 函数。表达式 (a op b) 被重写为 (a.opCmp(b) op 0) 。可交换的运算被重写为 (0 op b.opCmp(a)) 。成员函数 opCmp() 是 Object 的一部分,定义为:
int opCmp(Object o);这样所有的类对象都有 opCmp() 。
如果结构没有声明 opCmp() 函数,试图比较两个结构的大小就是一个错误。
注记:对类的引用同 null 的比较应该这样做:
if (a === null)而不是:
if (a == null)后者被转换为:
if (a.opCmp(null))如果 opCmp() 是一个虚函数,调用将失败(译注:如果 a 为 null,则调用虚函数 opCmp() 会用到类似于 *a 这样的动作,类似于 C++ 中访问空指针,自然会出错。事实上,所有类的根类 Object 已经声明了 opCmp() 函数,所以对类实例调用此函数几乎一定会失败。)。
原理
同时拥有 opEquals() 和 opCmp() 两个函数的原因为:- 有时,测试相等性会比测试小于或大于要高效。
- 对于某些对象来说,测试小于或者等于根本就没有意义。对于这些对象,可以像下面这样重载 opCmp() :
class A { int opCmp(Object o) { assert(0); // 比较大小没有任何意义 return 0; } }
函数调用运算符重载 f()
函数调用运算符,(),可以通过声明名为 opCall 的函数来重载:struct F { int opCall(); int opCall(int x, int y, int z); } void test() { F f; int i; i = f(); // 等价于 i = f.opCall(); i = f(3,4,5); // 等价于 i = a.opCall(3,4,5); }采用这种方法,结构或对象可以表现得像函数一样。
数组运算符重载
重载索引运算符 a[i]
数组索引运算符,[],可以通过声明名为 opIndex 的函数来重载,该函数可以有一个或多个参数。至于对数组赋值,可以通过声明名为 opIndexAssign 的函数来重载,该函数可以有两个或多个参数。第一个参数是赋值表达式的右值。struct A { int opIndex(int i1, int i2, int i3); int opIndexAssign(int value, int i1, int i2); } void test() { A a; int i; i = a[5,6,7]; // 等价于 i = a.opIndex(5,6,7); a[i,3] = 7; // 等价于 a.opIndexAssign(7,i,3); }采用这种方法,结构或对象可以表现得像数组一样。(译注:opApply??怎么没提??)
注意:目前,数组索引重载不能用于 op=、++ 或 -- 运算符的左值。
重载切片运算符 a[] 和 a[i .. j]
重载切片运算符意味着重载例如 a[] 和 a[i .. j] 这样的表达式。class A { int opSlice(); // 重载 a[] int opSlice(int x, int y); // 重载 a[i .. j] } void test() { A a = new A(); int i; i = a[]; // 等价于 i = a.opSlice(); i = a[3..4]; // 等价于 i = a.opSlice(3,4); }