基本操作符
操作符是执行特定的数学运算或逻辑操作的符号。例如,加号(+
)将两个数相加,如:let i = 1 + 2
。更复杂的运算例子包括逻辑与操作符(&&
),如:if (i > 0 && i < 10)
。
仓颉编程语言不仅支持各种常用的操作符,同时为了减少常见编码错误对它们做了部分改进。如:赋值表达式(包含赋值操作符的表达式)的类型是 Unit,值是 (),如果将 if(a == 3)
写成 if(a = 3)
,赋值表达式的返回值不是布尔类型,因此会编译报错,这样可以避免将判等操作符(==
)误写成赋值操作符(=
)的问题。算术操作符(+
,-
,*
,/
,%
等)的结果会被检测并禁止值溢出,以此来避免保存变量时由于变量大于或小于其类型所能承载的范围时导致的异常结果。
仓颉编程语言还提供了区间操作符,例如 a..b
或 a..=b
,这方便表达一个区间内的数值。
本章节只描述了仓颉编程语言中的基本操作符,其他操作符参见附录中的操作符。如何进行自定义类型的操作符重载参见操作符重载章节。
赋值操作符
用于将左操作数的值修改为右操作数的值,要求右操作数的类型是左操作数类型的子类型。对赋值表达式求值时,总是先计算 =
右边的表达式,再计算 =
左边的表达式,最后进行赋值。
main(): Int64 {
var a = 1
var b = 1
a = (b = 0) // 编译错误,赋值表达式的类型是 Unit,值是 ()
if (a = 5) { // 编译错误,赋值表达式的类型是 Unit,值是 ()
}
a = b = 0 // 语法错误,不支持链式使用赋值
return 0
}
多赋值表达式是一种特殊的赋值表达式,多赋值表达式等号左边必须是一个 tuple ,这个 tuple 里面的元素必须都是左值,等号右边的表达式也必须是 tuple 类型,右边 tuple 每个元素的类型必须是对应位置左值类型的子类型。值得注意的是当左侧 tuple 中出现 _ 时,表示忽略等号右侧 tuple 对应位置处的求值结果(意味着这个位置处的类型检查总是可以通过的)。多赋值表达式可以将右边的 tuple 类型的值,一次性赋值给左边 tuple 内的对应左值,省去逐个赋值的代码。
main(): Int64 {
var a: Int64
var b: Int64
(a, b) = (1, 2) // a = 1, b = 2
(a, b) = (b, a) // 交换, a = 2, b = 1
(a, _) = (3, 4) // a = 3
(_, _) = (5, 6) // 无赋值
return 0
}
算术操作符
仓颉编程语言支持的算术操作符包括:一元负号(-
)、加(+
)、减(-
)、乘(*
)、除(/
)、取余(%
)、求幂(**
)。除了一元负号是一元前缀操作符,其他操作符均是二元中缀操作符。
一元负号(-
)的操作数只能是数值类型的表达式。一元前缀负号表达式的值等于操作数取负的值,类型和操作数的类型相同:
let num1: Int64 = 8
let num2 = -num1 // num2 = -8, with 'Int64' type
let num3 = -(-num1) // num3 = 8, with 'Int64' type
对于二元操作符 *
,/
,%
,+
和 -
,要求两个操作数的类型相同。其中 %
的操作数只支持整数类型,*
,/
,+
和 -
的操作数可以是任意数值类型。
注意:
- 除法(
/
)的操作数为整数时,将非整数值向 0 的方向舍入为整数。- 整数取余运算
a % b
的值定义为a - b * (a / b)
。- 加法操作符也可用于字符串的拼接。
let a = 2 + 3 // add: 5
let b = 3 - 1 // sub: 2
let c = 3 * 4 // multi: 12
let d = 7 / 3 // division: 2
let e = 4 % 3 // mod: 1
let s1 = "abc"
var s2 = "ABC"
let r1 = s1 + s2 // r1 = "abcABC"
**
表示求幂运算(如 x**y
表示计算底数 x 的 y 次幂)。**
的左操作数只能为 Int64 类型或 Float64 类型。
注意:
当左操作类型为 Int64 时,右操作数只能为 UInt64 类型,表达式的类型为 Int64。
当左操作类型为 Float64 时,右操作数只能为 Int64 类型或 Float64 类型,表达式的类型为 Float64。
let p1 = 2 ** 3 // p1 = 8
let p2 = 2 ** UInt64(3 ** 2) // p2 = 512
let p3 = 2.0 ** 3 // p3 = 8.0
let p4 = 2.0 ** 3 ** 2 // p4 = 512.0
let p5 = 2.0 ** 3.0 // p5 = 8.0
let p6 = 2.0 ** 3.0 ** 2.0 // p6 = 512.0
复合赋值操作符
仓颉编程语言也提供 **=
、*=
、/=
、%=
、+=
、-=
、<<=
、>>=
、&=
、^=
、|=
、&&=
和 ||=
复合赋值操作符。对于复合赋值表达式求值时,总是先计算 =
左边的表达式的左值,然后根据这个左值取右值,然后将该右值与 =
右边的表达式做计算(若有短路规则会继续遵循短路规则),最后赋值。因为复合赋值表达式也是一个赋值表达式,所以复合赋值操作符也是非结合的。复合赋值表达式同样要求两个操作数的类型相同。
var a: Int64 = 10
a += 2 // a = 12
a -= 2 // a = 10
a **= 2 // a = 100
a *= 2 // a = 200
a /= 10 // a = 20
a %= 6 // a = 2
a <<= 2 // a = 8
关系操作符
关系操作符包括六种:相等(==
)、不等(!=
)、小于(<
)、小于等于(<=
)、大于(>
)、大于等于(>=
)。关系操作符都是二元操作符,并且要求两个操作数的类型是一样的。关系表达式的类型是 Bool 类型,即值只可能是 true 或 false。
关系表达式举例:
main(): Int64 {
3 < 4 // return true
3 <= 3 // return true
3 > 4 // return false
3 >= 3 // return true
3.14 == 3.15 // return false
3.14 != 3.15 // return true
return 0
}
对于元组类型,当且仅当所有元素均支持使用 ==
进行值判等(使用 !=
进行值判不等)时,此元组类型才支持使用 ==
进行值判等(使用 !=
进行值判不等);否则,此元组类型不支持 ==
和 !=
(如果使用 ==
和 !=
,编译报错)。两个同类型的元组实例相等,当且仅当相同位置(index)的元素全部相等(意味着它们的长度相等)。
var isTrue: Bool = (1, 3) == (0 , 2) // false
isTrue = (1, "123") == (1.0, 2) // 编译错误,两个操作数的类型不一致
coalescing 操作符
coalescing 操作符使用 ??
表示,??
是二元中缀操作符。coalescing 操作符用于 Option 类型的解构。
e1 ?? e2
表达式,在 e1 的值等于 Option<T>.Some(v)
时,e1 ?? e2
的值等于 v 的值(此时,不会再去对 e2 求值,即满足 “短路求值”);在 e1 的值等于 Option<T>.None
时,e1 ?? e2
的值等于 e2 的值。
coalescing 表达式使用举例:
main(): Int64 {
let v1 = Option<Int64>.Some(100)
let v2 = Option<Int64>.None
let r1 = v1 ?? 0
let r2 = v2 ?? 0
print("${r1}") // output: 100
print("${r2}") // output: 0
return 0
}
区间操作符
区间操作符有两种:..
和 ..=
,分别用于创建 “左闭右开” 和 “左闭右闭” 的区间实例。关于它们的介绍,请参见 区间类型。
逻辑操作符
仓颉编程语言支持三种逻辑操作符:逻辑非(!
)、逻辑与(&&
)、逻辑或(||
)。
逻辑非(!
)是一元操作符,它的作用是对其操作数的布尔值取反:!false
的值等于 true
,!true
的值等于 false
。
var a: Bool = true // a = true
var b: Bool = !a // b = false
var c: Bool = !false // c = true
逻辑与(&&
)和逻辑或(||
)均是二元操作符。对于表达式 expr1 && expr2
,只有当 expr1
和 expr2
的值均等于 true
时,它的值才等于 true
;对于表达式 expr1 || expr2
,只有当 expr1
和 expr2
的值均等于 false
时,它的值才等于 false
。
var a: Bool = true && true // a = true
var b: Bool = true && false // b = false
var c: Bool = false && false // c = false
var d: Bool = false && true // d = false
a = true || true // a = true
b = true || false // b = true
c = false || false // c = false
d = false || true // d = true
逻辑与(&&
)和逻辑或(||
)采用短路求值策略:计算 expr1 && expr2
时,当 expr1=false
则无需对 expr2
求值,整个表达式的值为 false
;计算 expr1 || expr2
时,当 expr1=true
则无需对 expr2
求值,整个表达式的值为 true
。
func isEven(a: Int64): Bool {
if((a % 2) == 0) {
println("${a} is an even number")
true
} else {
println("${a} is not an even number")
false
}
}
main() {
var a: Bool = isEven(2) && isEven(20)
var b: Bool = isEven(3) && isEven(30) // isEven(3)返回值是false, b 值为false,无需对isEven(30)求值
a = isEven(4) || isEven(40) // isEven(4)返回值是true, a 值为true,无需对isEven(40)求值
b = isEven(5) || isEven(50)
}
位运算操作符
仓颉编程语言支持一种一元前缀位运算操作符:按位求反(!
),以及五种二元中缀位运算操作符:左移(<<
)、右移(>>
)、按位与(&
)、按位异或(^
)和按位或(|
)。位运算操作符的操作数只能为整数类型,通过将操作数视为二进制序列,然后在每一位上进行逻辑运算(0 视为 false,1 视为 true )或移位操作来实现位运算。
对于移位操作符,要求其操作数必须是整数类型(但两个操作数可以是不同的整数类型,例如:左操作数是 Int8,右操作数是 Int16),并且无论左移还是右移,右操作数都不允许为负数(对于编译时可检查出的此类错误,编译报错,如果运行时发生此错误,则抛出异常)。对于无符号数的移位操作,移位和补齐规则是:左移低位补 0 高位丢弃,右移高位补 0 低位丢弃。对于有符号数的移位操作,移位和补齐规则是:
- 正数和无符号数的移位补齐规则一致;
- 负数左移低位补 0 高位丢弃;
- 负数右移高位补 1 低位丢弃。
另外,如果右移或左移的位数(右操作数)等于或者大于操作数的宽度,则为移位越界,如果编译时可以检测到则报错,否则运行时抛出异常。
var a = !10 // The result is -11
a = !20 // The result is -21
a = 10 << 1 // The result is 20
a = 10 << 1 << 1 // The result is 40
a = 10 >> 1 // The result is 5
a = 10 & 15 // The result is 10
a = 10 ^ 15 // The result is 5
a = 10 | 15 // The result is 15
a = (1 ^ (8 & 15)) | 24 // The result is 25
自增自减操作符
自增(++
)和自减(--
)操作符实现对值的加 1 和减 1 操作,且只能作为后缀操作符使用。自增(++
)和自减(--
)操作符是非结合的。
对于表达式 expr++
(或 expr--
),规定如下:
expr
的类型必须是整数类型;- 因为
expr++
(或expr--
)是expr += 1
(或expr -= 1
)的语法糖,所以此 expr 同时必须也是可被赋值的; expr++
(或expr--
)的类型为 Unit。
自增(自减)表达式举例:
var i: Int32 = 5
i++ // i = 6
i-- // i = 5
i--++ // syntax error
var j = 0
j = i-- // semantics error