NOTE阅读文档版本:
语言规约 Cangjie-0.53.18-Spec
具体开发指南 Cangjie-LTS-1.0.3
在阅读 了解仓颉的语言规约时, 难免会涉及到一些仓颉的示例代码, 但 我们对仓颉并不熟悉, 所以可以用 仓颉在线体验 快速验证
有条件当然可以直接 配置 Canjie-SDK
WARNING博主在此之前, 基本只接触过 C/C++语言, 对大多现代语言都没有了解, 所以在阅读过程中遇到相似的概念, 难免会与 C/C++中的相似概念作类比, 见谅
此样式内容, 表示文档原文内容
表达式
表达式通常由一个或多个操作数(
operand)构成, 多个操作数之间由操作符(operator)连接, 每个表达式都有一个类型, 计算表达式值的过程称为对表达式的求值(evaluation)在仓颉编程语言中, 表达式几乎无处不在, 有表示各种计算的表达式(如算术表达式、逻辑表达式等), 也有表示分支和循环的表达式(如
if表达式、循环表达式等)对于包含多个操作符的表达式, 必须明确每个操作符的优先级、结合性以及操作数的求值顺序
优先级和结合性规定了操作数与操作符的结合方式, 操作数的求值顺序规定了二元和三元操作符的操作数求值顺序, 它们都会对表达式的值产生影响
注: 本章中对于各操作符的操作数类型的规定, 均建立在操作符没有被重载的前提下
自增自减表达式
自增自减表达式是包含自增操作符(
++)或自减操作符(--)的表达式
a++与a--分别是a+=1与a-=1的语法糖自增和自减操作符实现对值的加
1和减1操作, 且只能作为后缀操作符使用
++和--是非结合的, 所以类似于a++--这样的在同一个表达式中同时包含两个及以上++/--但未使用圆括号规定计算顺序的表达式是被(从语法上)禁止的自增(自减)表达式的语法定义为:
incAndDecExpression: postfixExpression ('++' | '--' );对于表达式
expr++(或expr--), 规定如下:
expr的类型必须是整数类型- 因为
expr++(或expr--)是expr += 1(或expr -= 1)的语法糖, 所以此expr同时必须也是可被赋值的(见赋值表达式)expr++(或expr--)的类型为Unit自增(自减)表达式举例:
var i: Int32 = 5i++ // i = 6i-- // i = 5i--++ // syntax errorvar j = 0j = i-- // semantics error
仓颉规定++和--只能后置, 且只能用于整型, 且变量必须可变
这点和 C/C++很不一样, C++是可以重载++和--运算符的, 且区分前置和后置
算术表达式
算术表达式是包含算术操作符的表达式
仓颉编程语言支持的算术操作符包括: 一元负号(
-)、加(+)、减(-)、乘(*)、除(/)、取余(%)、求幂(**)除了一元负号是一元前缀操作符, 其他操作符均是二元中缀操作符
它们的优先级和结合性参见下文
算术表达式的语法定义为:
prefixUnaryExpression: prefixUnaryOperator* incAndDecExpression;prefixUnaryOperator: '-'| ...;additiveExpression: multiplicativeExpression (additiveOperator multiplicativeExpression)*;multiplicativeExpression: exponentExpression (multiplicativeOperator exponentExpression)*;exponentExpression: prefixUnaryExpression (exponentOperator prefixUnaryExpression)*;additiveOperator: '+' | '-';multiplicativeOperator: '*' | '/' | '%';exponentOperator: '**';
一元负号(
-)的操作数只能是数值类型的表达式一元前缀负号表达式的值等于操作数取负的值, 类型和操作数的类型相同:
let num1: Int64 = 8let num2 = -num1 // num2 = -8, with 'Int64' typelet num3 = -(-num1) // num3 = 8, with 'Int64' type对于二元操作符
*,/,%,+和-, 要求两个操作数的类型相同其中
%的操作数只支持整数类型,*,/,+和-的操作数可以是任意数值类型let a = 2 + 3 // add: 5let b = 3 - 1 // sub: 2let c = 3 * 4 // multi: 12let d = 6.6 / 1.1 // division: 6let e = 4 % 3 // mod: 1特别地, 除法(
/)的操作数为整数时, 将非整数值向 0 的方向舍入为整数整数取余运算
a % b的值定义为a - b * (a / b)let q1 = 7 / 3 // integer division: 2let q2 = -7 / 3 // integer division: -2let q3 = 7 / -3 // integer division: -2let q4 = -7 / -3 // integer division: 2let r1 = 7 % 3 // integer remainder: 1let r2 = -7 % 3 // integer remainder: -1let r3 = 7 % -3 // integer remainder: 1let r4 = -7 % -3 // integer remainder: -1
如果不确定负数之间的取余结果, 仓颉给定了取余运算的定义, 可以按照此进行计算
**表示求幂运算(如x**y表示计算底数 x 的 y 次幂)
**的左操作数只能为Int64类型或Float64类型, 并且:
- 当左操作类型为
Int64时, 右操作数只能为UInt64类型, 表达式的类型为Int64- 当左操作类型为
Float64时, 右操作数只能为Int64类型或Float64类型, 表达式的类型为Float64let p1 = 2 ** 3 // p1 = 8let p2 = 2 ** UInt64(3 ** 2) // p2 = 512let p3 = 2.0 ** 3.0 // p3 = 8.0let p4 = 2.0 ** 3 ** 2 // p4 = 512.0let p5 = 2.0 ** 3.0 // p5 = 8.0let p6 = 2.0 ** 3.0 ** 2.0 // p6 = 512.0当左操作类型为
Float64, 右操作数类型为Int64时, 存在一些特殊情况需要明确求幂表达式的值具体罗列为:
x ** 0 = 1.0 // 对 任意 x0.0 ** n = POSITIVE_INFINITY // 对 n < 0 的奇数-0.0 ** n = NEGATIVE_INFINITY // 对 n < 0 的奇数0.0 ** n = POSITIVE_INFINITY // 对 n < 0 的偶数-0.0 ** n = POSITIVE_INFINITY // 对 n < 0 的偶数0.0 ** n = 0.0 // 对 n > 0 的偶数-0.0 ** n = 0.0 // 对 n > 0 的偶数0.0 ** n = 0.0 // 对 n > 0 的奇数-0.0 ** n = -0.0 // 对 n > 0 的奇数POSITIVE_INFINITY ** n = POSITIVE_INFINITY // 对 n > 0NEGATIVE_INFINITY ** n = NEGATIVE_INFINITY // 对 n > 0 的奇数NEGATIVE_INFINITY ** n = POSITIVE_INFINITY // 对 n > 0 的偶数POSITIVE_INFINITY ** n = 0.0 // 对 n < 0NEGATIVE_INFINITY ** n = -0.0 // 对 n < 0 的奇数NEGATIVE_INFINITY ** n = 0.0 // 对 n < 0 的偶数注: 在上述所列特殊情况之外, 当左操作数的值为
NaN时, 无论右操作数取何值, 求幂表达式的值均等于NaN当左操作类型为
Float64, 右操作数类型为Float64时, 同样存在一些特殊情况需要明确求幂表达式的值具体罗列为:
x ** 0.0 = 1.0 // 对 任意 xx ** -0.0 = 1.0 // 对 任意 x0.0 ** y = POSITIVE_INFINITY // 对于 y 的值等于 < 0 的奇数整数-0.0 ** y = NEGATIVE_INFINITY // 对于 y 的值等于 < 0 的奇数整数0.0 ** NEGATIVE_INFINITY = POSITIVE_INFINITY-0.0 ** NEGATIVE_INFINITY = POSITIVE_INFINITY0.0 ** POSITIVE_INFINITY = 0.0-0.0 ** POSITIVE_INFINITY = 0.00.0 ** y = 0.0 // 对 有限的 y > 0.0 且它的值等于奇数整数时-0.0 ** y = -0.0 // 对 有限的 y > 0.0 且它的值等于奇数整数时-1.0 ** POSITIVE_INFINITY = 1.0-1.0 ** NEGATIVE_INFINITY = 1.01.0 ** y = 1.0 // 对 任意 yx ** POSITIVE_INFINITY = 0.0 // 对 -1.0 < x < 1.0x ** POSITIVE_INFINITY = POSITIVE_INFINITY // 对 任意 < -1.0 的 x 或 任意 > 1.0 的 xx ** NEGATIVE_INFINITY = POSITIVE_INFINITY // 对 -1.0 < x < 1.0x ** NEGATIVE_INFINITY = 0.0 // 对 任意 x < -1.0 或 任意 > 1.0 的 xPOSITIVE_INFINITY ** y = 0.0 // 对 y < 0.0POSITIVE_INFINITY ** y = POSITIVE_INFINITY // 对 y > 0.0NEGATIVE_INFINITY ** y = -0.0 // 对 有限的 y < 0.0 且它的值等于奇数整数时NEGATIVE_INFINITY ** y = NEGATIVE_INFINITY // 对 有限的 y > 0.0 且它的值等于奇数整数时NEGATIVE_INFINITY ** y = 0.0 // 对 有限的 y < 0.0 且它的值不为奇数整数时NEGATIVE_INFINITY ** y = POSITIVE_INFINITY // 对 有限的 y > 0.0 且它的值不为奇数整数时0.0 ** y = POSITIVE_INFINITY // 对 有限的 y < 0.0 且它的值不为奇数整数时-0.0 ** y = POSITIVE_INFINITY // 对 有限的 y < 0.0 且它的值不为奇数整数时0.0 ** y = 0.0 // 对 有限的 y > 0.0 且它的值不为奇数整数时-0.0 ** y = 0.0 // 对 有限的 y > 0.0 且它的值不为奇数整数时x ** y = NaN // 对 有限的 x < 0.0 且 有限的 y 的值不为整数时注: 在上述所列特殊情况之外, 一旦有操作数的值为
NaN, 则求幂表达式的值等于NaN
**幂运算符, 是 C/C++中不存在的一个东西
仓颉中**运算符的左、右操作数都是只能为Int64和Float64类型
整型还好, 浮点型有些特殊结果的运算, 比较繁琐, 需要记
算术表达式结果为整数类型时存在整数溢出的可能
仓颉提供了三种属性宏及编译选项来控制整数溢出的处理行为(以下简称为行为)
如下表格所示:
attributes Options behavior explaining for behavior @OverflowThrowing--int-overflow throwingthrowing抛出异常 (默认行为) @OverflowWrapping--int-overflow wrappingwrapping数值回绕 (达到最大值后从最小值重新开始) @OverflowSaturating--int-overflow saturatingsaturating饱和处理 (达到最大值后保持最大值不变) 注:
- 默认的行为为 throwing
- 假设属性宏与编译选项同时作用在某一范围, 且代表的行为不相同, 则该范围以属性宏代表的行为为准
行为示例:
@OverflowThrowingfunc test1(x: Int8, y: Int8) { // if x equals to 127 and y equals to 3let z = x + y // throwing OverflowException}@OverflowWrappingfunc test2(x: Int8, y: Int8) { // if x equals to 127 and y equals to 3let z = x + y // z equals to -126}@OverflowSaturatingfunc test3(x: Int8, y: Int8) { // if x equals to 127 and y equals to 3let z = x + y // z equals to 127}
仓颉对整型计算时可能出现的溢出, 定义了三种处理方法: 抛异常、数值回绕以及饱和处理
属性宏与编译选项作用范围冲突示例:
// 编译: cjc --int-overflow saturating test.cj@OverflowWrappingfunc test2(x: Int8, y: Int8) {let z = x + y // the behavior is wrapping}func test3(x: Int8, y: Int8) {let z = x + y // the behavior is saturating}特别地, 对于
INT_MIN * -1,INT_MIN / -1和INT_MIN % -1, 规定的行为如下:
Expression Throwing Wrapping Saturating INT_MIN * -1 or -1 * INT_MINthrowing OverflowExceptionINT_MININT_MAXINT_MIN / -1throwing OverflowExceptionINT_MININT_MAXINT_MIN % -1000需要注意的是, 对于整数溢出行为是 throwing 的场景, 若整数溢出可提前在编译期检测出来, 则编译器会直接给出报错
如果代码中存在属性宏, 但编译时制定了编译选项
那么, 属性宏优先, 编译选项其次
关系表达式
关系表达式是包含关系操作符的表达式
关系操作符包括
6种: 相等(==)、不等(!=)、小于(<)、小于等于(<=)、大于(>)、大于等于(>=)关系操作符都是二元操作符, 并且要求两个操作数的类型是一样的
关系表达式的类型是
Bool类型, 即值只可能是true或false关系操作符的优先级和结合性见下文
关系表达式的语法定义为:
equalityComparisonExpression: comparisonOrTypeExpression (equalityOperator comparisonOrTypeExpression)?;comparisonOrTypeExpression: shiftingExpression (comparisonOperator shiftingExpression)?| ...;equalityOperator: '!=' | '==';comparisonOperator: '<' | '>' | '<=' | '>=';关系表达式举例:
main(): Int64 {3 < 4 // return true3 <= 3 // return true3 > 4 // return false3 >= 3 // return true3.14 == 3.15 // return false3.14 != 3.15 // return truereturn 0}需要注意的是, 关系运算符是非结合(non-associative)运算符, 即无法写出类似于
a < b < c这样的表达式main(): Int64 {3 < 4 < 5 // error:`<`is non-associative3 == 3 != 4 // error:`==`and`!=`are non-associativereturn 0}
关系运算符是非结合运算符, 这一点与 C/C++一致, 但仓颉中强制关系表达式为Bool类型且不可类型转换
type test和type cast表达式
type test表达式是包含操作符is的表达式,type cast表达式是包含操作符as的表达式
is和as的优先级和结合性参见下文
type test和type cast表达式的语法定义为:comparisonOrTypeExpression: ...| shiftingExpression ('is' type)?| shiftingExpression ('as' userType)?;
is操作符
e is T是一个用于类型检查的表达式,e is T的类型是Bool其中
e可以是任何类型的表达式,T可以是任何类型当
e的运行时类型R是T的子类型时,e is T的值为true, 否则值为false
is操作符举例:open class Base {var name: String = "Alice"}class Derived1 <: Base {var age: UInt8 = 18}class Derived2 <: Base {var gender: String = "female"}main(): Int64 {var testVT = 1 is Int64 // testVT = truetestVT = 1 is String // testVT = falsetestVT = true is Int64 // testVT = falsetestVT = [1, 2, 3] is Array<Int64> // testVT = truelet base1: Base = Base()let base2: Base = Derived1()let base3: Base = Derived2()let derived1: Derived1 = Derived1()let derived2: Derived2 = Derived2()var test = base1 is Base // test = truetest = base1 is Derived1 // test = falsetest = base1 is Derived2 // test = falsetest = base2 is Base // test = truetest = base2 is Derived1 // test = truetest = base2 is Derived2 // test = falsetest = base3 is Base // test = truetest = base3 is Derived1 // test = falsetest = base3 is Derived2 // test = truetest = derived1 is Base // test = truetest = derived1 is Derived1 // test = truetest = derived1 is Derived2 // test = falsetest = derived2 is Base // test = truetest = derived2 is Derived1 // test = falsetest = derived2 is Derived2 // test = truereturn 0}
仓颉提供了is操作符, 可以用于判断变量是否是某个类型:value is Type, 如果value是Type的子类型, 表达式值也会为true
此为type test表达式
as操作符
e as T是一个用于类型转换的表达式,e as T的类型是Option<T>其中
e可以是任何类型的表达式,T可以是任何具体类型当
e的运行时类型R是T的子类型时,e as T的值为Some(e), 否则值为None
as操作符举例:open class Base {var name: String = "Alice"}class Derived1 <: Base {var age: UInt8 = 18}class Derived2 <: Base {var gender: String = "female"}main(): Int64 {let base1: Base = Base()let base2: Base = Derived1()let base3: Base = Derived2()let derived1: Derived1 = Derived1()let derived2: Derived2 = Derived2()let castOP1 = base1 as Base // castOP = Option<Base>.Some(base1)let castOP2 = base1 as Derived1 // castOP = Option<Derived1>.Nonelet castOP3 = base1 as Derived2 // castOP = Option<Derived2>.Nonelet castOP4 = base2 as Base // castOP = Option<Base>.Some(base2)let castOP5 = base2 as Derived1 // castOP = Option<Derived1>.Some(base2)let castOP6 = base2 as Derived2 // castOP = Option<Derived2>.Nonelet castOP7 = base3 as Base // castOP = Option<Base>.Some(base3)let castOP8 = base3 as Derived1 // castOP = Option<Derived1>.Nonelet castOP9 = base3 as Derived2 // castOP = Option<Derived2>.Some(base3)let castOP10 = derived1 as Base // castOP = Option<Base>.Some(derived1)let castOP11 = derived1 as Derived1 // castOP = Option<Derived1>.Some(derived1)let castOP12 = derived1 as Derived2 // castOP = Option<Derived2>.Nonelet castOP13 = derived2 as Base // castOP = Option<Base>.Some(derived2)let castOP14 = derived2 as Derived1 // castOP = Option<Derived1>.Nonelet castOP15 = derived2 as Derived2 // castOP = Option<Derived2>.Some(derived2)return 0}
as是用于类型转换的, 不过转换的结果的类型不是原始类型, 而是Option<T>类型
value as Type, 如果value的类型是Type的子类型, 则表达式结果为Option<Type>.Some(value)
此为type cast
位运算表达式
位运算表达式是包含位运算操作符的表达式
仓颉编程语言支持 1 种一元前缀位运算操作符: 按位求反(
!)以及 5 种二元中缀位运算操作符: 左移(
<<)、右移(>>)、按位与(&)、按位异或(^)和按位或(|)位运算操作符的操作数只能为整数类型, 通过将操作数视为二进制序列, 然后在每一位上进行逻辑运算(
0视为false,1视为true)或移位操作来实现位运算
&、^和|的操作中, 位与位之间执行的是逻辑操作(参见 [逻辑表达式])位运算操作符的优先级和结合性参见下文
位运算表达式的语法定义为:
prefixUnaryExpression: prefixUnaryOperator* incAndDecExpression;prefixUnaryOperator: '!'| ...;bitwiseDisjunctionExpression: bitwiseXorExpression ( '|' bitwiseXorExpression)*;bitwiseXorExpression: bitwiseConjunctionExpression ( '^' bitwiseConjunctionExpression)*;bitwiseConjunctionExpression: equalityComparisonExpression ( '&' equalityComparisonExpression)*;shiftingExpression: additiveExpression (shiftingOperator additiveExpression)*;shiftingOperator: '<<' | '>>';位运算表达式举例:
func foo(): Unit {!10 // The result is -11!20 // The result is -2110 << 1 // The result is 2010 << 1 << 1 // The result is 4010 >> 1 // The result is 510 & 15 // The result is 1010 ^ 15 // The result is 510 | 15 // The result is 151 ^ 8 & 15 | 24 // The result is 25}
如果已经熟悉 C/C++中的位运算, 仓颉的位运算应该也不会陌生
对于移位操作符, 要求其操作数必须是整数类型(但两个操作数的类型可以不一样), 并且无论左移还是右移, 右操作数都不允许为负数(对于编译时可检查出的此类错误, 编译报错, 如果运行时发生此错误, 则抛出异常)
对于无符号数的移位操作, 移位和补齐规则是: 左移低位补 0 高位丢弃, 右移高位补 0 低位丢弃
对于有符号数的移位操作, 移位和补齐规则是:
- 正数和无符号数的移位补齐规则一致
- 负数左移低位补 0 高位丢弃
- 负数右移高位补 1 低位丢弃
let p: Int8 = -30let q = p << 2 // q = -120let r = p >> 2 // r = -8let r = p >> -2 // errorlet x: UInt8 = 30let b = x << 3 // b = 240let b = x >> 1 // b = 15另外, 如果右移或左移的位数(右操作数)等于或者大于操作数的宽度, 则为
overshift, 如果编译时可以检测到则报错, 否则运行时抛出异常let x1 : UInt8 = 30 // 0b00011110let y1 = x1 >> 11 // compilation error
仓颉的位运算, 除了移位时超出位数的强制安全措施, 其他运算方式基本与与 C/C++中的一致
区间表达式
区间表达式是包含区间操作符的表达式
区间表达式用于创建
Range实例区间表达式的语法定义为:
rangeExpression: bitwiseDisjunctionExpression ('..=' | '..') bitwiseDisjunctionExpression (':' bitwiseDisjunctionExpression)?| bitwiseDisjunctionExpression;区间操作符有两种:
..和..=, 分别用于创建”左闭右开”和”左闭右闭”的Range实例关于它们的介绍, 请参见 Range 类型
逻辑表达式
逻辑表达式是包含逻辑操作符的表达式
逻辑操作符的操作数只能为
Bool类型的表达式仓颉编程语言支持 3 种逻辑操作符: 逻辑非(
!)、逻辑与(&&)、逻辑或(||)它们的优先级和结合性参见下文
逻辑表达式的语法定义为:
prefixUnaryExpression: prefixUnaryOperator* incAndDecExpression;prefixUnaryOperator: '!'| ...;logicDisjunctionExpression: logicConjunctionExpression ( '||' logicConjunctionExpression)*;logicConjunctionExpression: rangeExpression ( '&&' rangeExpression)*;**逻辑非(
!)**是一元操作符, 它的作用是对其操作数的布尔值取反:!false的值等于true,!true的值等于false**逻辑与(
&&)和逻辑或(||)**均是二元操作符对于表达式
expr1 && expr2, 只有当expr1和expr2的值均等于true时, 它的值才等于true对于表达式
expr1 || expr2, 只有当expr1和expr2的值均等于false时, 它的值才等于false
&&和||采用短路求值策略:计算
expr1 && expr2时, 当expr1=false则无需对expr2求值, 整个表达式的值为false计算
expr1 || expr2时, 当expr1=true则无需对expr2求值, 整个表达式的值为truemain(): Int64 {let expr1 = falselet expr2 = true!true // Logical NOT, return false.1 > 2 && expr1 // Logical AND, return false without computing the value of expr1.1 < 2 || expr2 // Logical OR, return true without computing the value of expr2.return 0}
仓颉中的逻辑操作符, 与 C/C++中的也一致, 包括短路求值策略
coalescing表达式 **
coalescing表达式是包含coalescing操作符的表达式
coalescing操作符使用??表示,??是二元中缀操作符其优先级和结合性参见下文
coalescing表达式的语法定义为:coalescingExpression: logicDisjunctionExpression ('??' logicDisjunctionExpression)*;
coalescing操作符用于Option类型的解构假设表达式
e1的类型是Option<T>, 对于表达式e1 ?? e2, 规定:
表达式
e2具有类型T表达式
e1 ?? e2具有类型T当
e1的值等于Option<T>.Some(v)时,e1 ?? e2的值等于v的值(此时, 不会再去对e2求值, 即满足”短路求值”)当
e1的值等于Option<T>.None时,e1 ?? e2的值等于e2的值表达式
e1 ?? e2是如下match表达式的语法糖:// when e1 is Option<T>match (e1) {case Some(v) => vcase None => e2}
coalescing表达式使用举例:main(): Int64 {let v1 = Option<Int64>.Some(100)let v2 = Option<Int64>.Nonelet r1 = v1 ?? 0let r2 = v2 ?? 0print("${r1}") // output: 100print("${r2}") // output: 0return 0}
coalescing也就是??是Option类型模式匹配的语法糖
是用来解构Option类型数据的
如果存在e1为Option<T>, 则e1 ?? e2中, e2类型需要为T, 此时:
- 如果
e1是Some(value), 则此表达式值为value, 且不再计算e2 - 如果
e1是None, 则此表达式值为e2
也就是说, 对于Option<T>类型的变量, 如果比较简单就不用再写match匹配模式, 可以直接??进行解构