2681 字
13 分钟
仓颉文档阅读-语言规约XVI: 常量求值
NOTE

阅读文档版本:

语言规约 Cangjie-0.53.18-Spec

具体开发指南 Cangjie-LTS-1.0.3

在阅读 了解仓颉的语言规约时, 难免会涉及到一些仓颉的示例代码, 但 我们对仓颉并不熟悉, 所以可以用 仓颉在线体验 快速验证

有条件当然可以直接 配置Canjie-SDK

WARNING

博主在此之前, 基本只接触过C/C++语言, 对大多现代语言都没有了解, 所以在阅读过程中遇到相似的概念, 难免会与C/C++中的相似概念作类比, 见谅

此样式内容, 表示文档原文内容

常量求值#

const变量#

const变量是一种特殊的变量, 它可以定义在编译时完成求值, 并且在运行时不可改变的变量

const变量与let/var声明的变量的区别是必须在定义时就初始化, 且必须用const表达式初始化

因此const变量的类型只能是const表达式支持的类型

let/var一样, const变量也支持省略类型

const表达式见下文定义

const a: Int64 = 0 // ok
const b: Int64 // error, b 为初始化
const c = f() // error, f() 不是 const 表达式
const d = 0 // ok, d 的类型是 Int64
func f(): Unit {}

const变量定义后可以像let/var声明的变量一样使用

let/var声明的变量不同, const由于在编译时就可以得到结果, 可以大幅减少程序运行时需要的计算

const a = 0
main(): Int64 {
let b: Int64 = a // ok
print(a) // ok
let c: VArray<Int64, $0> = [] // ok
return 0
}

const变量可以是全局变量, 局部变量, 静态成员变量

const变量不能在扩展中定义

const a = 0 // ok
class C {
const b = 0 // error, const 成员字段必须由 static 修改
static const c = 0 // ok
var v: Int64
init() {
const d = 0 // ok
v = b + c + d
}
}
extend C {
const e = 0 // error, const 不能在 extend 中定义
}

const变量可以访问对应类型的所有实例成员, 也可以调用对应类型的所有**非mut**实例成员函数

struct Foo {
let a = 0
var b = 0
const init() {}
func f1() {}
const func f2() {}
mut func f3() {
b = 123
}
}
main(): Int64 {
const v = Foo()
print(v.a) // ok
print(v.b) // ok
v.f1() // ok
v.f2() // ok
v.f3() // error, f3 是 mut 函数
return 0
}

const变量初始化后该类型实例的所有成员都是const的(深度const, 包含成员的成员), 因此不能被用于左值

struct Foo {
let a = 0
var b = 0
const init() {}
}
func f() {
const v = Foo()
v.a = 1 // error
v.b = 1 // error
}

const修饰变量, 定义常量, 整个程序不允许修改次变量的值

const变量, 必须在定义时进行初始化

const可以修饰成员变量, 但必须是静态成员变量

const修饰一个实例, 此实例可以访问所有成员变量, 但只能访问非mut成员函数

const修饰一个实例, 此实例的所有成员变量默认会被const修饰, 禁止赋值(当左值)

const表达式#

某些特定形式的表达式, 被称为const表达式, 这些表达式具备了可以在编译时求值的能力

const上下文中, 这些是唯一允许的表达式, 并且始终会在编译时进行求值

而在其它非const上下文, const表达式不保证在编译时求值

以下表达式都是const表达式

  1. 数值类型、BoolUnitRuneString类型的字面量(不包含插值字符串)

  2. 所有元素都是const表达式的array字面量(不能是 Array 类型), tuple字面量

  3. const变量, const函数形参, const函数中的局部变量

  4. const函数, 包含使用const声明的函数名、符合const函数要求的lambda、以及这些函数返回的函数表达式

  5. const函数调用(包含const构造函数), 该函数的表达式必须是const表达式, 所有实参必须都是const表达式

  6. 所有参数都是const表达式的enum构造器调用, 和无参数的enum构造器

  7. 数值类型、BoolUnitRuneString类型的算数表达式、关系表达式、位运算表达式, 所有操作数都必须是const表达式

  8. ifmatchtry、控制转移表达式(包含returnbreakcontinuethrow)、isas

    这些表达式内的表达式必须都是const表达式

  9. const表达式的成员访问(不包含属性的访问), tuple的索引访问

  10. const initconst函数中的thissuper表达式

  11. const表达式的const实例成员函数调用, 且所有实参必须都是const表达式

首先, 各类型的字面量都是const表达式

const修饰的各种函数、参数、变量等都是const表达式

各种元素是const表达式的运算表达式 等

const表达式应该不是很难区分, 除了let之外, 不能被修改的, 不能被当作左值的等

const上下文#

const上下文是一类特定上下文, 在这些上下文内的表达式都必须是const表达式, 并且这些表达式始终在编译时求值

const上下文是指 const变量初始化表达式

常量上下文, 始终再编译时求值, 其实是特指const变量初始化表达式

const函数#

const函数是一类特殊的函数, 这些函数具备了可以在编译时求值的能力

const上下文中调用这种函数时, 这些函数会在编译时执行计算

而在其它非const上下文, const函数会和普通函数一样在运行时执行

const函数与普通函数的区别是 限制了部分影响编译时求值的功能, const函数中只能出现声明、const表达式、受限的部分赋值表达式

  1. const函数声明必须使用const修饰

  2. 全局const函数和static const函数中只能访问const声明的外部变量, 包含const全局变量、const静态成员变量, 其它外部变量都不可访问

    const init函数和const实例成员函数, 除了能访问const声明的外部变量, 还可以访问当前类型的实例成员变量

  3. const函数中的表达式都必须是const表达式, const init函数除外

  4. const函数中可以使用letconst声明新的局部变量, 但不支持var

  5. const函数中的参数类型和返回类型没有特殊规定

    如果该函数调用的实参不符合const表达式要求, 那这个函数调用不能作为const表达式使用, 但仍然可以作为普通表达式使用

  6. const函数不一定都会在编译时执行, 例如可以在非const函数中运行时调用

  7. const函数与非const函数重载规则一致

  8. 数值类型、BoolUnitRuneString类型 和enum支持定义const实例成员函数

  9. 对于structclass, 只有定义了const init才能定义const实例成员函数

    class中的const实例成员函数不能是open

    struct中的const实例成员函数不能是mut

const func f(a: Int64): Int64 { // ok
let b = 6
if (a > 5 && (b + a) < 15 ) {
return a * a
}
return a
}
class A {
var a = 0
}
const func f1(a: A): Unit { // ok
return
}
const func f2(): A {
return A() // error, A 没有包含 const init
}

const函数使用const修饰, 其内的表达式全部为const表达式(const init除外)

const函数在const上下文中调用时, 会在编译时求值

接口中的const函数#

接口中也可以定义const函数, 但会受到以下规则限制

  • 接口中的const函数, 实现类型必须也用const函数才算实现接口

  • 接口中的非const函数, 实现类型使用const或非const函数都算实现接口

  • 接口中的const函数与接口的static函数一样, 只有在该接口作为泛型约束的时候, 受约束的泛型变元或变量才能使用这些const函数

interface I {
const func f(): Int64
const static func f2(): Int64
}
const func g<T>(i: T) where T <: I {
return i.f() + T.f2()
}

接口中的const函数, 只有实际实现const函数才算实现接口函数

接口中的普通函数, 实际实现const或非const函数, 都算实现接口函数

const init#

有无const init决定了哪些自定义struct/class可以用在const表达式上

一个类型能否定义const init取决于以下条件

  • 如果当前类型是class, 则不能具有var声明的实例成员变量, 否则不允许定义const init

    如果当前类型具有父类, 当前的const init必须调用父类的const init(可以显式调用或者隐式调用无参const init), 如果父类没有const init则报错

  • Object类型的无参init也是const init, 因此Object的子类可以使用该const init

  • 当前类型的实例成员变量如果有初始值, 初始值必须要是const表达式, 否则不允许定义const init

  • const init内可以使用赋值表达式对实例成员变量赋值, 除此以外不能有其它赋值表达式

const initconst函数的区别是const init内允许对实例成员变量进行赋值(需要使用赋值表达式)

struct R1 {
var a: Int64
let b: Int64
const init() { // ok
a = 0
b = 0
}
}
struct R2 {
var a = 0
let b = 0
const init() {} // ok
}
func zero(): Int64 {
return 0
}
struct R3 {
let a = zero()
const init() {} // error, a 的初始化不是 const 表达式
}
class C1 {
var a = 0
const init() {} // error, a 不能是 var 绑定
}
struct R4 {
var a = C1()
const init() {} // error, a 的初始化不是 const 表达式
}
open class C2 {
let a = 0
let b = R2()
const init() {} // ok
}
class C3 <: C2 {
let c = 0
const init() {} // ok
}

如果, class想要实现const init, 那么成员变量不允许存在var变量

struct/class成员如果存在定义直接初始化, 需要是**const表达式**, 否则不能实现const init

const init内出现的赋值表达式, 只能是对成员变量进行赋值的表达式, 不允许出现其他赋值表达式

const init的作用, 感觉更大是优化性能, 因为在编译时会实例化对象

要实例化一个const变量, 那么此类型就必须实现const init

作者
Humid1ch
发布于
2025-10-21
许可协议
CC BY-NC-SA 4.0