学习一门编程语言,离不开了解基本数据类型。数据的数学运算和逻辑操作又会用到操作符。因此这篇来整理学习一下,仓颉编程语言的操作符和基础数据类型。
基本操作符
操作符是执行特定的数学运算或逻辑操作的符号。仓颉编程语言不仅支持各种常用的操作符,同时为了减少常见编码错误对它们做了部分改进。
先看一下操作符的分类和每个分类的操作符,然后再单独介绍一下。
| 类别 | 操作符 |
|---|---|
| 赋值操作符 | = |
| 算术操作符 | 一元负号(-)、加(+)、减(-)、乘(*)、除(/)、取余(%)、求幂(**) |
| 复合赋值操作符 | *=、=、/=、%=、+=、-=、<<=、>>=、&=、^=、|=、&&= 和 ||= |
| 关系操作符 | 相等(==)、不等(!=)、小于(<)、小于等于(<=)、大于(>)、大于等于(>=) |
| coalescing 操作符 | ?? |
| 区间操作符 | .. 和 ..= |
| 逻辑操作符 | 逻辑非(!)、逻辑与(&&)、逻辑或(||) |
| 位运算操作符 | 左移(<<)、右移(>>)、按位与(&)、按位异或(^)和按位或(|) |
| 自增自减操作符 | 自增(++)和自减(--) |
赋值操作符
用于将左操作数的值修改为右操作数的值,要求右操作数的类型是左操作数类型的子类型。对赋值表达式求值时,总是先计算 = 右边的表达式,再计算 = 左边的表达式,最后进行赋值。
main() {
var a = 1
var b = 1
var c = false
a = (b = 0) // 编译错误,赋值表达式的类型是 Unit,值是 ()
if (a = 5) { // 编译错误,赋值表达式的类型是 Unit,值是 ()
}
a = b = 0 // 语法错误,不支持链式使用赋值
a = a++ // 编译错误,赋值表达式的类型是 Unit,值是 ()
c = a==b //编译正确,输出 true
b = b+1 //编译正确,输出 2
println(a)
}
多赋值表达式是一种特殊的赋值表达式,多赋值表达式等号左边必须是一个元组tuple。值得注意的是当左侧 tuple 中出现 _ 时,表示忽略等号右侧 tuple 对应位置处的求值结果
main() {
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) // 无赋值
println('${a}'+','+'${b}') //输出3,1
}
算术操作符
除了一元负号是一元前缀操作符,其他操作符均是二元中缀操作符。一元负号(-)的操作数只能是数值类型的表达式。
*、/、%、+ 和 -,要求两个操作数的类型相同
% 的操作数只支持整数类型
*、/、+ 和 - 的操作数可以是任意数值类型。
main() {
let a = 2 + 3 // a = 5
let b = 3 - 1 // b = 2
let c = 3 * 4 // c = 12
let d = 7 / 3 // d = 2
let e = 7 / -3 // e = -2, 当遇到“-”时,它具有更高的优先级。
let f = -7 / 3 // f = -2
let g = -7 / -3 // g = 2, 当遇到“-”时,它具有更高的优先级。
let h = 14 % 3 // h = 2
let i = 14 % -3 // i = 2, 当遇到“-”时,它具有更高的优先级。
let j = -14 % 3 // j = -2
let k = -4 % -13 // k = -4, 当遇到“-”时,它具有更高的优先级。
let s1 = "abc"
var s2 = "ABC"
let r1 = s1 + s2 // r1 = "abcABC"
println(k)
}
注意:
除法(/)的操作数为整数时,将非整数值向 0 的方向舍入为整数。
整数取余运算 a % b 的值定义为 a - b * (a / b)。
加法操作符也可用于字符串的拼接。
复合赋值操作符
对于复合赋值表达式求值时,总是先计算 = 左边的表达式的左值,再根据这个左值取右值,然后将该右值与 = 左边的表达式做计算,最后赋值。复合赋值表达式同样要求两个操作数的类型相同。
main() {
var a: Int64 = 10
a += 2 // a = a+2 12
a -= 2 // a = a-2 10
a **= 2 // a = a*a 100
a *= 2 // 200
a /= 10 // 20
a %= 6 // 2
a <<= 2 // 二进制 0B10 左移2位变为 0B1000 十进制8
}
关系操作符
关系操作符都是二元操作符,并且要求两个操作数的类型是一样的。关系表达式的类型是 Bool 类型,即值只可能是 true 或 false。
main() {
3 < 4 // true
3 <= 3 // true
3 > 4 // false
3 >= 3 // true
3.14 == 3.15 // false
3.14 != 3.15 // true
println()
}
coalescing 操作符
e1 ?? e2 表达式,在 e1 的值等于 Option<T>.Some(v) 时,e1 ?? e2 的值等于 v 的值;在 e1 的值等于 Option<T>.None 时,e1 ?? e2 的值等于 e2 的值。
main(): Int64 {
let v1 = Option<Int64>.Some(100)
let v2 = Option<Int64>.None
let r1 = v1 ?? 0
let r2 = v2 ?? 0
print("${r1}") // 100
print("${r2}") // 0
return 0
}
区间操作符
区间操作符有两种:.. 和 ..=,分别用于创建 “左闭右开” 和 “左闭右闭” 的区间实例。具体在下面的区间类型介绍。
逻辑操作符
逻辑非(!)是一元操作符,它的作用是对其操作数的布尔值取反。
逻辑与(&&)和逻辑或(||)均是二元操作符,采用短路求值策略。
位运算操作符
仓颉编程语言支持一种一元前缀位运算操作符:按位求反(!),以及五种二元中缀位运算操作符:左移(<<)、右移(>>)、按位与(&)、按位异或(^)和按位或(|)。位运算操作符的操作数只能为整数类型,通过将操作数视为二进制序列,然后在每一位上进行逻辑运算(0 视为 false,1 视为 true )或移位操作来实现位运算。
对于有符号数的移位操作,移位和补齐规则是:
1.正数和无符号数的移位补齐规则一致;
2.负数左移低位补 0 高位丢弃;
3.负数右移高位补 1 低位丢弃。
main() {
var a = !1 // -2
a <<= 1 // -4
a = 10 & 5 // 0B1010 & 0B101 0
a = 10 ^ 0 //二进制位同0非1 10
a = 10 | 15 // 0B1010 | 0B1111 15
a = -1 >> 1 // -1
a = -1 << 1 // -2
println(a)
}
自增自减操作符
自增(++)和自减(--)操作符实现对值的加 1 和减 1 操作,且只能作为后缀操作符使用。
对于表达式 expr++ (或 expr-- ),规定如下:
1.expr 的类型必须是整数类型;
2.因为 expr++ (或 expr-- )是 expr += 1(或 expr -= 1 )的语法糖,所以此 expr 同时必须也是可被赋值的;
3.expr++(或 expr-- )的类型为 Unit。
var i: Int32 = 5
i++ // i = 6
i-- // i = 5
i--++ // 语法错误
var j = 0
j = i-- // 语义错误
基础数据类型
Java基础数据类型整数类型(byte、short、int、long)浮点类型(float、double)字符类型(char)布尔类型(boolean)
仓颉编程语言中的基础数据类型包括:整数类型、浮点类型、布尔类型、字符类型、字符串类型、元组类型、数组类型、区间类型、Unit类型、Nothing类型
整数类型
整数类型分为有符号整数类型和无符号整数类型。
有符号整数类型包括 Int8、Int16、Int32、Int64 和 IntNative。
无符号整数类型包括 UInt8、UInt16、UInt32、UInt64 和 UIntNative
对于编码长度为N 的整数类型,其表示范围为:
对比可以发现,这里仓颉的Int8、Int16、Int32、Int64分别对应的Java中的byte、short、int、long。
程序具体使用哪种整数类型,取决于该程序中需要处理的整数的性质和范围。在没有类型上下文的情况下默认推断为Int64类型,可以避免不必要的类型转换。
整数类型字面量
整数类型字面量有 4 种进制表示形式:二进制(使用 0b 或 0B 前缀)、八进制(使用 0o 或 0O 前缀)、十进制(没有前缀)、十六进制(使用 0x 或 0X 前缀)。
main() {
var x = 100
var y = 0x10
var z = 0o432
var a = 100i8
var b = 0x10u16
var c = 0o432i32
println('${x},${y},${z}') //输出100,16,282
println('${a},${b},${c}') //输出100,16,282
}
字符字节字面量
使用 ASCII 码表示 UInt8 类型的值。字符字节字面量由字符 b、一对标识首尾的单引号、以及一个 ASCII 字符组成。
main() {
var a = b'\n' //换行符 10
var b = b'z' //ASCII 122
var c = b'\u{7A}' // ox7A = 122
println('${a},${b},${c}') //输出10,122,122
}
在各进制表示中,可以使用下划线 _ 充当分隔符的作用,方便识别数值的位数,如 0b0001_1000。
整数类型支持的操作符
整数类型默认支持的操作符包括:算术操作符、位操作符、关系操作符、自增和自减操作符、复合赋值操作符。
浮点类型
浮点类型包括 Float16、 Float32 和 Float64,分别用于表示编码长度为 16-bit、 32-bit 和 64-bit 的浮点数的类型。
仓颉的Float32和Float64相当于Java的float和double
| 格式 | 位数 | 符号位 | 指数位 | 尾数位 | 数值范围(约) | 有效数字(十进制) |
|---|---|---|---|---|---|---|
| Float16 | 16 | 1 | 5 | 10 | ±6.1e-5 ~ ±6.5e4 | 3~4 位 |
| Float32 | 32 | 1 | 8 | 23 | ±1.4e-45 ~ ±3.4e38 | 6~7 位 |
| Float64 | 64 | 1 | 11 | 52 | ±4.9e-324 ~ ±1.8e308 | 15~17 位 |
浮点类型字面量
浮点类型字面量有两种进制表示形式:十进制、十六进制。在十进制表示中,一个浮点字面量至少要包含一个整数部分或一个小数部分,没有小数部分时必须包含指数部分(以 e 或 E 为前缀,底数为 10)。
在十六进制表示中,一个浮点字面量除了至少要包含一个整数部分或小数部分(以 0x 或 0X 为前缀),同时必须包含指数部分(以 p 或 P 为前缀,底数为 2)。
main() {
let a = 3.14f32
let b: Float32 = 2e3
let c = 2.4e-1f64
println('${a},${b},${c}') //输出 3.140000,2000.000000,0.240000
}
浮点类型支持的操作
浮点类型默认支持的操作符包括:算术操作符、关系操作符、复合赋值操作符。浮点类型不支持自增和自减操作符。
布尔类型
布尔类型使用 Bool 表示,用来表示逻辑中的真和假。
布尔类型字面量
true 和 false
布尔类型支持的操作
逻辑操作符(逻辑非 !,逻辑与 &&,逻辑或 ||)、部分关系操作符(== 和 !=)、部分复合赋值操作符(&&= 和 ||=)
字符类型
字符类型使用 Rune 表示,可以表示 Unicode 字符集中的所有字符。
字符类型字面量
单个字符
转义字符使用转义符号 \ 开头,后面加需要转义的字符
通用字符以 \u 开头,后面加上定义在一对花括号中的 1~8 个十六进制数,即可表示对应的 Unicode 值代表的字符。
main() {
let a: Rune = r'a' //单个字符 a
let b: Rune = r'\\' //转移字符 \
let c:Rune = r'\u{2764}' //可以表示一些键盘不能输入的字符
println('${a},${b},${c}') //输出 a,\,❤
}
字符类型支持的操作
字符类型支持的操作符包括:关系操作符,即小于(<)、大于(>)、小于等于(<=)、大于等于(>=)、相等(==)、不等(!=)。比较的是字符的 Unicode 值。
字符串类型
字符串类型使用 String 表示,用于表达文本数据,由一串 Unicode 字符组合而成。
字符串字面量
单行字符串字面量的内容定义在一对单引号或一对双引号之内。单行字符串字面量只能写在同一行,不能跨越多行
多行字符串字面量开头结尾需各存在三个双引号(""")或三个单引号(''')。
多行原始字符串字面量以一个或多个井号(#)和一个单引号(')或双引号(")开头,后跟任意数量的合法字符,直到出现与字符串开头相同的引号和与字符串开头相同数量的井号为止。

元组类型
元组(Tuple)可以将多个不同的类型组合在一起,成为一个新的类型。元组类型使用 (T1, T2, ..., TN) 表示,其中 T1 到 TN 可以是任意类型,不同类型间使用逗号(,)连接。元组至少是二元,例如,(Int64, Float64) 表示一个二元组类型,(Int64, Float64, String) 表示一个三元组类型。
元组的长度是固定的,即一旦定义了一个元组类型的实例,它的长度不能再被更改。
元组类型的字面量
元组类型的字面量使用 (e1, e2, ..., eN) 表示,其中 e1 到 eN 是表达式,多个表达式之间使用逗号分隔。
main() {
let a = 2025
let b = '10 24'
var c =(a,b)
println(c[0]) // 输出 2025
println(c[1]) // 输出 10 24
c = (10,'24')
println(c[0]) // 输出 10
println(c[1]) // 输出 24
// c = ('2025','1024') //error: mismatched types
}
元组类型的类型参数
对于一个元组类型,只允许统一写类型参数名,或者统一不写类型参数名
func getSystem():(name:String,code:Int8){
return ('HarmonyOS',6)
}
main() {
let a:(name:String,code:Int8) = getSystem()
let b:(String,String) =('仓颉','cangjie')
println(a[0]) //HarmonyOS
println(b[0]) //仓颉
}
数组类型
Array
用 Array 类型来构造单一元素类型,有序序列的数据。仓颉使用 Array<T> 来表示 Array 类型。T 表示 Array 的元素类型,T 可以是任意类型。
main() {
var a: Array<Int64> = [0, 0, 0, 0]
var b: Array<String> = ["a1", "a2", "a3"]
let c: Array<String> = []
let d = [1, 2, 3, 3, 2, 1]
let e = Array<Int64>(3, repeat: 1) // [1, 1, 1]
let f = Array<Int64>(4, {i => i * i}) //[0, 1, 4, 9]
println(f)
}
访问Array
当需要对 Array 的所有元素进行访问时,可以使用 for-in 循环遍历 Array 的所有元素。
当需要知道某个 Array 包含的元素个数时,可以使用 size 属性获得对应信息。
当想访问单个指定位置的元素时,可以使用下标语法访问
如果想获取某一段 Array 的元素,可以在下标中传入 Range 类型的值
main() {
let arr = [0,1,2,3,4]
for (i in arr) {
print("${i} ") // 0 1 2 3 4
}
println('arr size 为 ${arr.size}') //5
println('arr 第二个元素 为 ${arr[1]}') //1
println('获取 第二个到第四个元素 ${arr[1..4]}') //[1,2,3]
println('获取 第一个到第三个元素 ${arr[..3]}') //[0,1,2]
arr[0] = -1
println(arr[0]) //-1
}
VArray
仓颉还引入了值类型数组 VArray<T, $N> ,其中 T 表示该值类型数组的元素类型,$N 是一个固定的语法。通过 $ 加上一个 Int64 类型的数值字面量表示这个值类型数组的长度。
main() {
var a: VArray<Int64, $3> = [1, 2, 3]
let b = VArray<Int64, $5>({ i => i }) // [0, 1, 2, 3, 4]
let c = VArray<Int64, $5>(repeat: 0) // [0, 0, 0, 0, 0]
for (i in a) { //for - in 表达式中表达式的 VArray 类型未实现迭代器
print("${i}")
}
}
区间类型
区间类型用于表示拥有固定步长的序列,区间类型是一个泛型,使用 Range<T> 表示。
每个区间类型的实例都会包含 start、end 和 step 三个值。其中,start 和 end 分别表示序列的起始值和终止值,step 表示序列中前后两个元素之间的差值(即步长)
区间字面量
区间字面量有两种形式:“左闭右开”区间和“左闭右闭”区间。
左闭右开区间的格式是 start..end : step,它表示一个从 start 开始,以 step 为步长,到 end(不包含 end)为止的区间。
左闭右闭区间的格式是 start..=end : step,它表示一个从 start 开始,以 step 为步长,到 end(包含 end)为止的区间。
main() {
let r1 = Range<Int64>(0, 5, 1, true, true, true) //012345
let r2 = 0..5 //01234
let r3 = 0..5 : 2 //024
let r4 = 5..=0 : -1 //543210
for(i in r4){
print(i)
}
}
Unit类型
Unit 类型只有一个值,也是它的字面量:()。除了赋值、判等和判不等外,Unit 类型不支持其他操作。
Nothing类型
Nothing 是一种特殊的类型,它不包含任何值,并且 Nothing 类型是所有类型的子类型
目前编译器还不允许在使用类型的地方显式地使用 Nothing 类型。