类型¶
基本类型¶
| 简写 | 全称 | 解释 |
|---|---|---|
| unit | 空类型,不占空间 | |
| bool | 布尔值 | |
| flag | 标志位 | |
| byte | 字节 | |
| char | Unicode 字符 | |
| ascii | 7 位 ASCII 字符,补齐到 8 位 | |
| char8 | 8 位字符 | |
| char16 | 16 位字符 | |
| char32 | 32 位字符 |
true 和 false 字面量并不是 bool 或 flag 类型,实际上是编译器内置的内部类型,它们会根据上下文被隐式转换为相应的类型。
标志位和布尔值的区别在于,布尔值可以用在条件判断中,而标志位只能和 true false 进行赋值和比较。
也就是说:
flag my_flag = true;
if (my_flag) { // 错误,不能将 flag 用在条件判断中
...
}
if (my_flag == true) { // 正确
...
}
这在配置场景下能有效避免误用标志位进行条件判断,提供更清晰的语义区分。
对于字符类型 char,它表示一个完整的 Unicode 字符,可以存储任何 Unicode 字符,在底层使用 UTF-32 编码存储。
对于 ascii,它表示一个 7 位 ASCII 字符,存储时会在最高位补一位到 8 位。
字符串类型¶
| 简写 | 全称 | 解释 |
|---|---|---|
| str | string | 任意编码字符串(但保证没有 \0 字符) |
| str8 | UTF-8 编码字符串 | |
| str16 | UTF-16 编码字符串 | |
| str32 | UTF-32 编码字符串 | |
| cstr | cstring | C 风格字符串(以 \0 结尾) |
| cstr8 | UTF-8 编码 C 风格字符串 | |
| cstr16 | UTF-16 编码 C 风格字符串 | |
| cstr32 | UTF-32 编码 C 风格字符串 | |
| bytes | 字节数组,不强制指定编码 |
str是 Lumos 的默认字符串类型,它可以存储任意编码的字符串,但不允许包含空字符 (\0)。str8使用 UTF-8 编码,是一种变长编码,适合存储大部分文本数据,节省空间且兼容 ASCII。str16使用 UTF-16 编码,主要用于与 Windows API、Java 或 C# 等环境进行互操作。str32使用 UTF-32 编码,每个字符固定占用 4 字节。虽然内存占用较高,但它支持 O(1) 时间复杂度的随机字符访问。cstr及其变体 (cstr8、cstr16、cstr32) 是以空字符 (\0) 结尾的字符串类型,主要用于与 C 语言及其库进行互操作。bytes类型本质上是逻辑不可变的字节数组,它不保证任何特定的编码。通常用于处理原始二进制数据或与旧系统交互。
所有的字符串类型在 Lumos 中默认都是逻辑不可变的。如果需要修改字符串,应当使用
StringBuilder或转换为字符数组。
字符串字面量¶
val s1 = "Hello"; // 默认为 str
val s2 = u8"UTF-8"; // 显式指定为 str8
val s3 = u16"UTF-16"; // 显式指定为 str16
val s4 = u32"UTF-32"; // 显式指定为 str32
val s5 = b"Raw bytes"; // 显式指定为 bytes
val s6 = b16"UTF-16 bytes"; // bytes 类型的 UTF-16 编码字节串
字符串与字符的关系¶
字符串可以被视为相应字符类型的集合:
str8对应char8(UTF-8 code unit) 的序列。str16对应char16的序列。str32对应char32的序列。
注意:char 类型(Unicode 标量值)在迭代 str8 或 str16 时会进行解码。
二进制类型¶
bit类型不允许算术运算,只允许位运算。flag用于表示标志位,其在bit类型的基础上禁用了移位运算。flag类型支持位掩码(Bitmask)操作。
很多编程语言中使用
u32u64等类型来表示标志位,这样做是不合适的,因为这些类型允许算术运算和移位运算,而标志位不应该允许这些操作。一般来说,标志位应当只允许按位与、或、异或、非等位运算。
| 简写 | 全称 | 解释 |
|---|---|---|
| b8 | bit8 | 8 位二进制数据 |
| b16 | bit16 | 16 位二进制数据 |
| b32 | bit32 | 32 位二进制数据 |
| b64 | bit64 | 64 位二进制数据 |
| flag8 | 8 位标志位 | |
| flag16 | 16 位标志位 | |
| flag32 | 32 位标志位 | |
| flag64 | 64 位标志位 | |
| byte | 1 字节 | |
| byte2 | 2 字节 | |
| byte4 | 4 字节 | |
| byte8 | 8 字节 |
一般我们提供 b1 到 b65536 和 flag1 到 flag65536 的二进制类型扩展。
一般同时提供 byte1 到 byte8192 的字节类型扩展。
数字类型¶
整数使用 i 或 u 前缀表示有符号或无符号,浮点数使用 f 前缀表示浮点数。
Note
一般来说,看到 i u f 开头就表示它是用来做数学运算的类型。
我们将用作数学运算的类型和用作二进制数据存储的类型区分开来是为了避免混淆。
| 简写 | 全称 | 解释 |
|---|---|---|
| i8 | int8 | 8 位整数 |
| i16 | int16 | 16 位整数 |
| i32 | int32 | 32 位整数 |
| i64 | int64 | 64 位整数 |
| u8 | uint8 | 8 位无符号整数 |
| u16 | uint16 | 16 位无符号整数 |
| u32 | uint32 | 32 位无符号整数 |
| u64 | uint64 | 64 位无符号整数 |
| f32 | float32 | 32 位浮点数 |
| f64 | float64 | 64 位浮点数 |
此处以 64 位系统为例,在 32 位系统中 i64 f64 等类型应当是字长扩展类型
标识符保留策略:
int定义编译期整数类型uint定义编译期无符号整数类型float定义编译期浮点类型i[0-9]+有符号整数类型u[0-9]+无符号整数类型f[0-9]+浮点数类型
尝试使用上述保留标识符定义任何名称都会导致编译错误,当然使用字符串作为标识符来定义不在此限制内,因为这只会让导出名变成保留名字而已。
注意这些保留的标识符都是小写,如果你想定义的名称是大写开头的,可以放心使用这些名称。
整数类型¶
有符号整数使用补码表示,无符号整数使用原码表示。不支持其它表示方式。
现代硬件应该均满足此条件,若有特殊硬件不满足此条件,则可以:
- 使用软件补码实现(默认方案)
- 使用平台特定的整数类型实现(需手动指定)
库依赖的承诺
鉴于标准库和第三方库很可能会依赖于整数类型的补码表示,如果使用非补码表示的整数类型,可能会导致不可预期的行为,程序员需要自行承担风险。
浮点类型¶
浮点类型遵循 IEEE 754 及其扩展标准。
若平台不支持标准的 f32 与 f64 类型,则可以:
- 使用软件浮点实现(默认方案)
- 使用平台特定的浮点类型实现(需手动指定)
软件浮点实现
如果平台不支持硬件浮点运算,我们会使用软件浮点实现来提供浮点类型支持。
这种情况下浮点类型的性能会大幅下降,建议尽量避免使用浮点类型。
出于复杂性考虑,我们只会提供 f32 和 f64 两种浮点类型。
库依赖的承诺
鉴于标准库和第三方库很可能会依赖于浮点类型的 IEEE 754 表示,如果使用非标准表示的浮点类型,虽然不会像改变整数表示那样影响巨大,但也可能会导致不可预期的行为,程序员需要自行承担风险。
端序¶
默认采用平台定义的短序存储,如果需要指定小端或大端存储,请使用以下类型:
i16lei32lei64le小端有符号整数u16leu32leu64le小端无符号整数i16bei32bei64be大端有符号整数u16beu32beu64be大端无符号整数f32lef64le小端浮点数f32bef64be大端浮点数
所有符合 2 的幂次方的字长扩展类型均支持小端和大端表示法,如
i128leu256be等。将一个指定端序的类型(如
u32le或u32be)赋值给原生端序类型(如u32)时,如果端序不同,编译器会自动执行端序转换,反之亦然。
端序转换性能
端序转换会带来一定的性能开销,尤其是在大量数据处理时。
建议在性能敏感的场景下,尽量使用与平台端序一致的类型以减少转换开销。
如果源或目标是指定的端序类型,尽可能只在读取和写入时进行端序转换,而不是在每次操作时都进行转换。
u8/i8 类型的端序
由于 u8 和 i8 类型只有一个字节,因此它们没有端序的概念。
使用 u8le、u8be、i8le 或 i8be 类型是允许的,但它们与 u8 和 i8 类型完全相同,没有任何区别。
数字字长扩展¶
一般来说我们提供 i2 到 i65536 和 u2 到 u65536 的整数类型扩展,但一般情况下应当少用这些扩展类型以保证性能。
一般来说 i128 u128 i256 u256 等符合 2 的幂次方的类型扩展会有更好的性能。
对于浮点类型扩展,我们在尽力而为的原则下提供。
浮点类型扩展
由于没有将浮点数扩展到任意位宽的标准,我们最多只能提供一些常见的浮点类型扩展。
我们只提供平台支持的浮点类型扩展,如 f16 f32 f64 f128 等。
我们不会考虑提供一些平台的私有浮点类型扩展,除非它们被广泛使用。
当然如果你的平台不支持任何扩展浮点类型,我们就不会提供任何扩展浮点类型。
编译期整数推导¶
我们提供 int 和 uint 两种编译期整数类型,对应于不限精度的有符号整数和无符号整数。
这些类型只能在编译期使用,不能作为变量的类型。
要不然是在编译期计算中计算出结果,要不然是可以找到一个确定的最小类型来存储该值。
推导的目标类型只会是 i32 u32 或以上的 2 的幂次方类型。
如果计算结果超过了 i64 的表示范围,编译器会自动推导到 i128 或更高类型。如果结果超过了编译器的原生上限,则会报错。
第三方编译器可以自定义此上限,但该上限不得小于
i128。
def fib(int n) -> int {
if (n <= 1) {
return n;
}
return fib(n - 1) + fib(n - 2);
}
val result = fib(10); // result 的类型为 i32
当然也可以显式转换为任何兼容的类型,但请注意编译期的有损转换会导致报错。
什么算有损转换
val a = 1.1 as i32; // 错误,浮点数转换为整数会丢失小数部分 val b = (1.1).floor() as i32; // 正确,先取整再转换
Warning
请不要在编译期使用过多的不限精度类型,以免导致编译期间过长或内存占用过高。
编译期浮点推导¶
我们提供 float 编译期浮点类型,对应于不限精度的浮点数,实际在编译期中存储为有理数。
这个类型只能在编译期使用,不能作为变量的类型。
这个类型只能显式转换为具体的浮点类型。
def half() -> float {
return 1 / 2;
}
val result_f32 = half() as f32; // 显式转换为 f32
val result_f64 = half() as f64; // 显式转换为 f64
当然也可以显式转换为任何兼容的类型,但请注意编译期的有损转换会导致报错。
Warning
请不要在编译期使用过多的不限精度类型,以免导致编译期间过长或内存占用过高。
类型通配¶
inttype整数类型- 实现整数所具有的运算符
- 只能存整数而不能存实数
floattype浮点数类型 (实现浮点数所具有的运算符的类型)numtype通用数字类型 (实现数字所具有的运算符的类型)+ - * / %
Num 数字标准实现:
- 方法 (Functions)
zero返回数字类型的零值one返回数字类型的单位值
- ops
add加法运算sub减法运算mul乘法运算div除法运算
SignedNum 有符号数字标准实现:
- ops
neg取反运算
Int 整数扩展实现:
rem取余运算mod取模运算
BinaryInt 二进制整数实现:
shl左移运算 如果移位数大于等于类型位宽,则结果为 0 。shr右移运算
SizedBinaryInt 固定位宽二进制整数实现:
rol循环左移运算ror循环右移运算
扩展类型¶
| 简写 | 全称 | 解释 |
|---|---|---|
| c32 | complex float32 | 32 位浮点复数 |
| c64 | complex float64 | 64 位浮点复数 |
| vec2 | vector2 float32 | 2 维 32 位浮点向量 |
| vector2 xxx | 可接所有数字类型 | |
| vec3 | vector3 float32 | 3 维 32 位浮点向量 |
| vector3 xxx | 可接所有数字类型 | |
| vec4 | vector4 float32 | 4 维 32 位浮点向量 |
| vector4 xxx | 可接所有数字类型 |
complex作为变量修饰符,用于表示复数。(单独存在时代表complex float32)
高精度扩展¶
| 简写 | 全称 | 解释 |
|---|---|---|
| MPN | multiple precision natural numbers | 大自然数 |
| MPZ | multiple precision integers | 大整数 |
| MPQ | multiple precision rational numbers | 大有理数 |
| MPF | multiple precision floating-point | 大浮点数 |
这些类型与 int uint float 类似,均为不限精度类型,但它们使用会进行内存分配并且可以在运行时使用。
我使用 GMP 作为这些类型的后端实现。
用 GMP 用的
指针类型¶
Lumos 使用 [T] 符号来表示非空指针类型,使用 [T]? 表示可空指针类型。并使用后缀 [] 进行解引用。这种设计统一了指针与数组的语义。
i32 a = 1;
[i32] b = &a; // b 是一个指向 i32 的非空指针
[i32]? c = b; // c 是一个指向 i32 的可空指针
i32 d = b[]; // 解引用,将 b 指向的值赋值给 d
注意:
[i32]仅表示一个内存地址,不包含长度信息。
切片类型 (Slice)¶
切片是数组或内存区域的一个动态视图,它在底层是一个“胖指针”(包含一个起始地址和一个长度)。
[10]i32 arr = $[1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
[]i32 s = arr[2..5]; // s 是一个切片,包含 arr 的第 3 到第 5 个元素
println(s.len); // 输出 3
切片类型 []T 与指针类型 [T] 的区别:
[T]:原始指针,不记录长度,不支持边界检查,解引用为p[]。[]T:切片,记录长度,支持.len属性,访问元素使用s[i],支持安全的边界检查。
引用类型¶
引用使用 &T 符号表示,它在语义上是变量的别名。
i32 a = 1;
&i32 b = a; // b 是 a 的引用
多个返回类型¶
variant IntOrUnit = i32 | unit;
def foo(i32 arg) -> IntOrUnit {
if (arg < 0) {
return unit;
}
return arg;
}
act[io.out] main() -> i32 {
val value = foo(10); // 使用 val 进行类型推导
switch (value) {
i32: println("返回值是 i32: ", value);
unit: println("返回值是 unit");
}
return 0;
}
变量声明规范¶
Lumos 提供了四种不同的变量修饰符,用于精确控制变量的可变性和内存行为。
完全可变 (var):
var i32 a = 10;(隐含mut)
逻辑不可变 (val / 默认):
i32 b = 20;(等同于val i32 b)
物理不可变 (imv):
imv i32 c = 30;(隐含imm)
编译期常量 (lit):
lit i32 d = 40;
var i32 a = 10; // 完全可变
i32 b = 20; // 逻辑不可变
imv i32 c = 30; // 物理不可变
lit i32 d = 40; // 编译期常量
fin e = 50; // 禁止重新绑定
注意:var 隐含了 mut,imv 隐含了 imm。重复书写会导致编译错误。此外,逻辑不可变(val)或物理不可变(imv)变量不能被隐式默认初始化,必须在声明时或构造函数中明确初始化。
类型转换¶
Lumos 仅支持 expr as T 格式的显式类型转换。不支持 C 风格的强制转换。
指针不能和任何非指针类型转换。
通常情况下使用 = 时进行的转换是隐式转换,而使用 as 时进行的转换是显式转换。但在变量初始化时,= 会进行显式转换。
i32 a = 1;
f32 b = a as f32; // 显式转换
b = 3.0; // 隐式转换
类型别名¶
use MyInt = i32;
比较类型¶
使用 is !is 运算符来比较类型是否相同。用法为 变量 is 类型 类型 is 类型,此处的类型可以为类型别名。
val a = 1;
if (a is i32) {
println("a is i32.");
} else {
println("a is not i32.");
}
类型属性¶
@limit(最小值, 最大值)限制数值的范围。
类型修饰¶
所有类型遵循,修饰在前,基本类型在后的规则。
[i32] // 指向 i32 的非空指针
[i32]? // 指向 i32 的可空指针
[]i32 // i32 的切片
&i32 // i32 的引用
指定初始化值¶
i32(10) // 默认初始化为 10 的 i32 类型
${10_i32} // 默认初始化为 10 的 i32 类型
数组¶
[10]i32 // 10 个 i32 类型组成的数组
[20][10]i32 // 20 乘 10 个 i32 类型组成的数组
类型内置成员¶
所有数字类型都拥有一系列内置的静态成员,用于获取该类型的元数据和常用常量。
数字类型共有成员¶
| 成员 | 说明 |
|---|---|
RADIX |
进制基数 (通常为 2) |
ZERO |
零值 |
ONE |
单位值 |
整数类型成员¶
对整数类型本身实现以下成员:
| 成员 | 说明 |
|---|---|
BITS |
类型位宽常量 (如 i32.BITS 为 32) |
BYTES |
类型字节宽常量 |
SIGNED |
布尔常量,表示该整数类型是否为有符号整数 |
RADIX |
进制基数常量 (通常为 2) |
MIN |
该类型能表示的最小值常量 |
MAX |
该类型能表示的最大值常量 |
ZERO |
整数类型的零值常量 |
ONE |
整数类型的单位值常量 |
可以这样使用:
println("i32 的最大值为: ", i32.MAX);
println("i32 是否有符号: ", i32.SIGNED);
浮点类型成员¶
对于浮点类型本身实现以下成员:
| 成员 | 说明 |
|---|---|
BITS |
浮点类型的位宽常量 |
BYTES |
浮点类型的字节宽常量 |
RADIX |
进制基数常量 |
DIGITS |
十进制有效数字位数 |
MIN |
最小有限负数值常量 |
MAX |
最大有限正数值常量 |
MIN_POSITIVE |
最小正正规数常量 |
EPSILON |
浮点类型的最小正数常量 (1.0 与下一个可表示值之差) |
ZERO |
浮点类型的零值常量 |
ONE |
浮点类型的单位值常量 |
NAN |
浮点类型的 NaN 常量 |
INF |
浮点类型的正无穷大常量 |
NEG_INF |
浮点类型的负无穷大常量 |
| 成员 | 说明 |
|---|---|
PI |
圆周率 \pi |
TAU |
圆周率 2\pi |
E |
自然对数底数 e |
SQRT2 |
根号 2 |
LN2 |
自然对数 2 |
LN10 |
自然对数 10 |
LOG2E |
以 2 为底的 e 对数 |
LOG10E |
以 10 为底的 e 对数 |
可以这样使用:
println("f32 的圆周率为: ", f32.PI);
println("f32 的精度位数: ", f32.DIGITS);
相关内容:单位系统见 单位系统。