25-操作符重载
前言
操作符重载的意思是允许开发者根据自己的需求对常见的运算符赋予不同的能力。比如
- + 赋予 * 的能力
- - 赋予 平方 的能力
定义操作符函数的方式
定义操作符函数有两种方式:
- 对于可以直接包含函数定义的类型 (包括
struct
、enum
、class
和interface
),可以直接在其内部定义操作符函数的方式实现操作符的重载。 - 使用
extend
的方式为其添加操作符函数,从而实现操作符在这些类型上的重载。
操作符函数对参数类型的约定如下:
- 一元操作符
- 二元操作符
- 索引操作符(
[]
) - 操作符函数只有一个参数,对返回值的类型没有要求。
一元操作符 和 二元操作符
-
实现对一个 Point
实例中两个成员变量 x
和 y
取负值,然后返回一个新的 Point
对象,+
实现对两个 Point
实例中两
个成员变量 x
和 y
分别求和,然后返回一个新的 Point
对象。
open class Point {
var x: Int64 = 0
var y: Int64 = 0
public init (a: Int64, b: Int64) {
x = a
y = b
}
// 重载一元运算符 -
public operator func -(): Point {
Point(-x, -y)
}
// 重载二元运算符 +
public operator func +(right: Point): Point {
Point(this.x + right.x, this.y + right.y)
}
}
直接使用一元 -
操作符和二元 +
操作符:
main() {
let p1 = Point(8, 24)
let p2 = -p1 // p2 = Point(-8, -24)
let p3 = p1 + p2 // p3 = Point(0, 0)
}
索引操作符
索引操作符是指[]
,它分成了两种形式,一种是取值,一种是赋值,比如
let a = arr[1]; // 取值
arr[2] = 300; // 赋值
所以重载的写法也有变化
取值时候的重载
class A {
operator func [](arg1: Int64, arg2: String): Int64 {
return 0
}
}
func f() {
let a = A()
let b: Int64 = a[1, "2"] // 取值的重载
// b == 0
}
赋值时候的重载,必须在结尾加上一个固定名称的参数 value
, 然后重载的函数返回值必须是unit
类型
class A {
var num: Int64 = 100
operator func [](arg1: Int64, arg2: Int64, value!: Int64): Unit {
this.num = arg1 + arg2 + value
}
}
main() {
let a = A()
// arg1 = 1 , arg2 = 2 , value = 30
a[1, 2] = 30
println(a.num) // 33
}
函数调用操作符
函数调用操作符(()
)重载函数,输入参数和返回值类型可以是任意类型
open class A {
public init() {}
public operator func ()(): Unit {}
}
func test1() {
let a = A()
a()
}
需要注意的是,不能在非构造函数中使用 this 和 super 进行重载。
open class A {
public init() {}
public init(x: Int64) {
this() // Ok, this() calls the constructor of A.
}
public operator func ()(): Unit {}
// 非构造函数 使用了this() 重载
public func foo() {
this() // Error, this() calls the constructor of A.
super() // Error
}
}
class B <: A {
public init() {
super() // Ok, super() calls the constuctor of the super class.
}
public func goo() {
super() // Error
}
}
对于枚举类型,当构造器形式和 ()
操作符重载函数形式都满足时,优先匹配构造器形式。示例如下:
enum E {
Y | X | X(Int64)
public operator func ()(p: Int64) {}
public operator func ()(p: Float64) {}
}
main() {
let e = X(1) // Ok, X(1) is to call the constructor X(Int64).
X(1.0) // Ok, X(1.0) is to call the operator () overloading function.
let e1 = X
e1(1) // Ok, e1(1) is to call the operator () overloading function.
Y(1) // oK, Y(1) is to call the operator () overloading function.
}
操作符重载
如果希望在某个类型上支持此类型默认不支持的操作符,可以使用操作符重载实现。
如果需要在某个类型上重载某个操作符,可以通过为类型定义一个函数名为此操作符的函数的方式实现,这样,在该类型的实例使用该操作符时,就会自动调用此操作符函数。
操作符函数定义与普通函数定义相似,区别如下:
- 定义操作符函数时需要在
func
关键字前面添加operator
修饰符; - 操作符函数的参数个数需要匹配对应操作符的要求(详见附录操作符);
- 操作符函数只能定义在 class、interface、struct、enum 和 extend 中;
- 操作符函数具有实例成员函数的语义,所以禁止使用
static
修饰符; - 操作符函数不能为泛型函数。
另外,需要注意的是,被重载后的操作符不改变它们固有的优先级和结合性(详见附录操作符)。
操作符重载函数定义和使用
定义操作符函数有两种方式:
- 对于可以直接包含函数定义的类型 (包括
struct
、enum
、class
和interface
),可以直接在其内部定义操作符函数的方式实现操作符的重载。 - 使用
extend
的方式为其添加操作符函数,从而实现操作符在这些类型上的重载。对于无法直接包含函数定义的类型(是指除struct
、class
、enum
和interface
之外其他的类型)或无法改变其实现的类型,比如第三方定义的struct
、class
、enum
和interface
,只能采用这种方式(参见扩展);
操作符函数对参数类型的约定如下:
对于一元操作符,操作符函数没有参数,对返回值的类型没有要求。
对于二元操作符,操作符函数只有一个参数,对返回值的类型没有要求。
如下示例中介绍了一元操作符和二元操作符的定义和使用:
-
实现对一个Point
实例中两个成员变量x
和y
取负值,然后返回一个新的Point
对象,+
实现对两个Point
实例中两个成员变量x
和y
分别求和,然后返回一个新的Point
对象。javascriptopen class Point { var x: Int64 = 0 var y: Int64 = 0 public init (a: Int64, b: Int64) { x = a y = b } public operator func -(): Point { Point(-x, -y) } public operator func +(right: Point): Point { Point(this.x + right.x, this.y + right.y) } }
接下来,就可以在
Point
的实例上直接使用一元-
操作符和二元+
操作符:javascriptmain() { let p1 = Point(8, 24) let p2 = -p1 // p2 = Point(-8, -24) let p3 = p1 + p2 // p3 = Point(0, 0) }
索引操作符(
[]
)分为取值let a = arr[i]
和赋值arr[i] = a
两种形式,它们通过是否存在特殊的命名参数 value 来区分不同的重载。索引操作符重载不要求同时重载两种形式,可以只重载赋值不重载取值,反之亦可。索引操作符取值形式
[]
内的参数序列对应操作符重载的非命名参数,可以是 1 个或多个,可以是任意类型。不可以有其它命名参数。返回类型可以是任意类型。javascriptclass A { operator func [](arg1: Int64, arg2: String): Int64 { return 0 } } func f() { let a = A() let b: Int64 = a[1, "2"] // b == 0 }
索引操作符赋值形式
[]
内的参数序列对应操作符重载的非命名参数,可以是 1 个或多个,可以是任意类型。=
右侧的表达式对应操作符重载的命名参数,有且只能有一个命名参数,该命名参数的名称必须是 value, 不能有默认值,value 可以是任意类型。返回类型必须是 Unit 类型。需要注意的是,value 只是一种特殊的标记,在索引操作符赋值时并不需要使用命名参数的形式调用。
javascriptclass A { operator func [](arg1: Int64, arg2: String, value!: Int64): Unit { return } } func f() { let a = A() a[1, "2"] = 0 }
特别的,除
enum
外的不可变类型不支持重载索引操作符赋值形式。函数调用操作符(
()
)重载函数,输入参数和返回值类型可以是任意类型。示例如下:javascriptopen class A { public init() {} public operator func ()(): Unit {} } func test1() { let a = A() // Ok, A() is call the constructor of A. a() // Ok, a() is to call the operator () overloading function. }
不能使用
this
或super
调用()
操作符重载函数。示例如下:javascriptopen class A { public init() {} public init(x: Int64) { this() // Ok, this() calls the constructor of A. } public operator func ()(): Unit {} public func foo() { this() // Error, this() calls the constructor of A. super() // Error } } class B <: A { public init() { super() // Ok, super() calls the constuctor of the super class. } public func goo() { super() // Error } }
对于枚举类型,当构造器形式和
()
操作符重载函数形式都满足时,优先匹配构造器形式。示例如下:javascriptenum E { Y | X | X(Int64) public operator func ()(p: Int64) {} public operator func ()(p: Float64) {} } main() { let e = X(1) // Ok, X(1) is to call the constructor X(Int64). X(1.0) // Ok, X(1.0) is to call the operator () overloading function. let e1 = X e1(1) // Ok, e1(1) is to call the operator () overloading function. Y(1) // oK, Y(1) is to call the operator () overloading function. }
可以被重载的操作符
下表列出了所有可以被重载的操作符(优先级从高到低):
| Operator | Description | | :------- | :-------------------- | ---------- | | ()
| Function call | | []
| Indexing | | !
| NOT | | -
| Negative | | **
| Power | | *
| Multiply | | /
| Divide | | %
| Remainder | | +
| Add | | -
| Subtract | | <<
| Bitwise left shift | | >>
| Bitwise right shift | | <
| Less than | | <=
| Less than or equal | | >
| Greater than | | >=
| Greater than or equal | | ==
| Equal | | !=
| Not equal | | &
| Bitwise AND | | ^
| Bitwise XOR | | |
| Bitwise OR |
需要注意的是:
- 一旦在某个类型上重载了除关系操作符(
<
、<=
、>
、>=
、==
和!=
)之外的其他二元操作符,并且操作符函数的返回类型与左操作数的类型一致或是其子类型,那么此类型支持对应的复合赋值操作符。当操作符函数的返回类型与左操作数的类型不一致且不是其子类型时,在使用对应的复合赋值符号时将报类型不匹配错误。 - 仓颉编程语言不支持自定义操作符,即不允许定义除上表中所列
operator
之外的其他操作符函数。 - 对于类型
T
, 如果T
已经默认支持了上述若干可重载操作符,那么通过扩展的方式再次为其实现同签名的操作符函数时将报重定义错误。例如,为数值类型重载其已支持的同签名算术操作符、位操作符或关系操作符等操作符时,为Rune
重载同签名的关系操作符时,为Bool
类型重载同签名的逻辑操作符、判等或不等操作符时,等等这些情况,均会报重定义错误。