变量¶
变量声明 var
¶
使用 var
关键字声明变量。
和绝大多数编程语言类似,直接:
var 变量名 = 初始化表达式;
var a = 1;
即可完成变量的声明与初始化,变量的类型会被自动设置为右侧表达式的类型。
也可以在声明时指定类型:
var 变量名 as 类型;
var 变量名 as 类型 = 初始化表达式;
var a as int;
var b as int = 1;
声明多个变量属于不同的类型:
var a as int = 1, b as float = 2;
声明多个变量属于同一个类型:
var a, b, c as int;
var a, b as int, c, d as float;
此时不能初始化。
声明指向不可变内存的不可变指针:
val a as *int = 0x12345678;
a = null; // error: 无法将 null 赋值给不可变指针
a[0] = 1; // error: 无法修改不可变内存
声明指向可变内存的不可变指针:
val a as *var int = 0x12345678;
a = null; // error: 无法将 null 赋值给不可变指针
a[0] = 1; // success
非空指针:
var a as &int = 0x12345678;
var b as &int = null; // error: 无法将 null 赋值给非空指针
唯一引用:
var a as !int = 0x12345678;
var b as !int = null; // error: 无法将 null 赋值给唯一引用
类型值的断言:
var a as int ($ > 0) = 1;
var b as int ($ > 0) = 0; // error: 无法将 0 赋值给 int($ > 0)
此处 $
代表当前值,每次写入时都会进行断言。
默认的规则
对于一个变量,如果没有进行任何形式的取地址,那么它绝对不会被外部意外修改。
var a = 1;
while (a > 0) {
// Do nothing
}
这应当是一个死循环。
允许的优化
对于一个变量,取到的值可以是:
- 编译时能够确定的值(非
volatile
)
warning: 对于编译期可确定的值使用lit
- 上次修改后缓存的值(非
volatile
) - 对应内存地址当前的值
不可变变量声明 val
¶
使用 val
关键字声明不可变变量,使用方法与 var
相同。
val 变量名 = 初始化表达式;
val a = 1;
常量设计上在整个生命周期中不可变,但实际上可以强行修改,但进行修改时可能会因编译器的优化策略导致不可预测的结果。
val a = 1; // 定义常量
println(a); // 输出 1
*(&a as *int) = 2; // 强行修改为 2
println(a); // 由于编译器的优化策略,可能输出 1 或 2
这么做的见一个打一个
允许的优化
对于一个常量,取到的值可以是:
- 编译时能够确定的值(非
volatile
)
warning: 对于编译期可确定的值使用lit
- 作用域内任意位置缓存的值(非
volatile
) - 对应内存地址当前的值
初始化¶
Lumos 允许的初始化方式有:
- 赋值初始化
int my_var = 1;
- 构造函数初始化
int my_var(1);
对于基本数据类型有伪构造函数 - 结构体元素赋值初始化
var my_var as MyStructure = {1, 2, .third = 3};
- 数组元素赋值初始化
var my_var[] as int = {1, 2, [2] = 3};
- 默认初始化
var my_var as int;
默认初始化会将基本数据类型变量初始化为二进制 0,对于其它数据类型则调用默认构造函数 我们推荐显式初始化
默认初始化使得所有变量都会被初始化,即使没有给定初始化表达式。
val a as int; // 初始化为 0
float b; // 初始化为 0.0
bool c; // 初始化为 false
char d; // 初始化为 '\0'
str e; // 初始化为 ""
变量类型与表达式类型不同时变量初始化被视为显式类型转换。但之后的赋值只允许隐式类型转换。
int* a = 0x123456;
延迟初始化¶
使用 lateinit
作为初始值来让变量不自动初始化。
注意访问未初始化的变量是未定义行为
var a as int = lateinit; // 此时 a 未初始化
a = 1; // 手动初始化 a
println(a); // 1
对于不可变变量,使用 lateinit
时仅可以赋值一次。
val a as int = lateinit; // 此时 a 未初始化
a = 1; // 手动初始化 a
println(a); // 1
a = 2; // error: 无法重新赋值给不可变变量
val a, b, c as int = lateinit;
if (xxx) {
a = 1;
b = 2;
c = 3;
} else {
a = 4;
b = 5;
c = 6;
}
懒初始化¶
使用 lateinit
接代码块作为初始值来让变量在第一次访问时自动初始化。
代码块将延迟到第一次访问时执行,且只会执行一次。
注意这种情况下不能连续声明多个变量
var a as int = lateinit {
return 1;
} /* 此处语句已结束,不能接着声明变量 */
println(a); // 此时 a 被初始化为 1
代码块中可以使用外部的变量,但注意变量的值为代码块执行时的值,而非声明时的值。
var a as int = 1;
var b as int = lateinit {
return a;
}
a = 2;
println(b); // 输出 2
a = 3;
println(b); // 输出 2
全局不可变变量¶
你不应该声明一个全局的不可变变量,应该用 let
(表达式) 或 lit
(常量表达式) 代替。
见 表达式。
限定符¶
restrict
¶
注意 restrict
不是属性
restrict
限定符用于指针,表示指针所指向的内存区域不会被其他指针访问。
int* restrict a = malloc(4);
int* restrict b = a; // 这是不可以的
int* c = a; // 这也是不可以的
volatile
¶
注意 volatile
不是属性
volatile
限定符用于变量,表示变量可能会被其它线程或硬件改变,编译器不会对其访问(读写)进行优化。
val a as volatile int = 1;
属性¶
register
¶
@register(寄存器名)
属性用于强制变量存储在寄存器中,而不是内存中。
这会导致相应寄存器无法被其它变量使用
无特殊需求不应该使用
@register("rax")
var a as int = 1;
注意此处的 rax
填写 eax
ax
al
都是可以的,实际宽度由类型决定。
isrestrict
运算符¶
isrestrict
运算符用于判断两个指针是否独立。
int* a = malloc(4);
int* b = malloc(4);
int* c = a;
// 当编译器可以自动推断内存块大小时
println(isrestrict(a, b)); // 输出 true
println(isrestrict(a, c)); // 输出 false
// 当编译器不能自动推断内存块大小时
println(isrestrict(a, 4, b, 4)); // 输出 true
println(isrestrict(a, 4, c, 4)); // 输出 false
getter/setter¶
你可以使用 getter 和 setter 来访问变量。
var a by int {
\get { // 只能有一个 \get 但可以有多个 \set
return 1;
}
\set { // 不写参数默认为 value
println(`set a to $value`);
}
}
a = 2; // 输出 set a to 2
println(a); // 输出 1
可以不声明类型:
var a by {
\get -> int {
return 1;
}
\set(value as int) {
println(`set a to $value`);
}
}
可以在内部声明变量来存储值:
var a by int {
var real_a as int = 1;
\get {
return real_a;
}
\set {
println(`set a to $value`);
real_a = value;
}
}
a = 2; // 输出 set a to 2
println(a); // 输出 2
外部无法访问到 real_a
变量。