数据是程序的原料。这一章把 xlang 的每一种基本类型、每一种字面量写法全部过一遍。
第 2 章搭好了程序的骨架,这一章往里填"数据"。任何程序处理的无非是数字、文字、真假、时间这几类东西,xlang 为它们各自准备了哪些类型、字面上怎么写,就是本章要一次讲透的内容。
本章的写法是"全形态":不是挑常用的讲,而是把每一种合法写法都列出来并实测——包括十六进制整数字母必须小写、raw 字符串不转义、f-string 里能写任意表达式这类容易踩坑的细节。读完本章,你看到任何 xlang 字面量都不会陌生。
认识 xlang 全部基本类型;掌握整数、浮点、字符串、时间、布尔、空值的全部字面量形态;理解 var/auto 的区别;会用 type() 查看任何值的类型。
xlang 是静态类型语言:每个变量在声明时就有确定的类型,编译器在运行前就会检查类型错误。声明采用 C 风格类型前置:
bool flag = true int count = 42 float pi = 3.14159 string name = "xlang" time today = #2026-07-18# var anything = "动态" // 动态类型 auto inferred = 100 // 编译期推断为 int
想随时确认一个值"到底是什么类型",用全局函数 type():
xlang 的基本类型全集(本章逐个讲):
| 类别 | 类型 |
|---|---|
| 空值与布尔 | null、bool |
| 整数 | int、int8 uint8 int16 uint16 int32 uint32 int64 uint64 |
| 浮点 | float、float32、float64 |
| 文本 | string |
| 时间 | time |
| 动态与推断 | var、auto、type |
| 指针标签 | point、point<T>(C 互操作用,本章只建立概念) |
容器类型(array/list/set/dict)和用户定义类型(class/union)不在本章——它们分别是卷四和卷五的主角。
bool 只有两个取值:true 和 false,全部小写:
bool ok = true bool bad = false println(ok, bad) // 输出: true false var empty = null println(empty == null) // 输出: true
null 表示"什么都没有"。它不是 0,不是空字符串,不是 false——它是一个独立的值,表示"此处无值"。判断一个值是否为 null 用 == 比较(判断类型是否为空用 is null,第 7 章详解)。
if empty then 来判断?第 8 章分支结构会讲条件表达式的类型要求,这里先记住用 == null 是最明确的写法。int a = 123 // 十进制 int b = 1_000_000 // 下划线分组,仅便于阅读 int c = 0x2A // 十六进制 = 42 int d = 0o52 // 八进制 = 42 int e = 0b101010 // 二进制 = 42 println(a, b, c, d, e)
三个细节:
_ 可以插在数字中间做分组,编译器直接忽略,1_000_000 就是 1000000;0x、0o、0b。大写 0X 是语法错误(见常见错误坑 1);VB 风格的 &H、&O、&B 在 xlang 中不存在;int 不带后缀时是 64 位整数(深入细节拾遗 1 有实测证据)。需要精确控制宽度时(二进制协议、硬件寄存器、文件格式),xlang 提供 8/16/32/64 位、各有符号和无符号共八种定宽类型:
int8 i8 = 127 // -128 ~ 127 int16 i16 = 32767 // -32768 ~ 32767 int32 i32 = 2147483647 int64 i64 = 9223372036854775807 uint8 u8 = 255 // 0 ~ 255 uint16 u16 = 65535 uint32 u32 = 4294967295 uint64 u64 = 18446744073709551615 println(i8, i16, i32, i64) // 127 32767 2147483647 9223372036854775807
字面量超出声明类型的范围时,编译期就会被拦下,例如 int8 a = 200 会得到明确的报错(见常见错误坑 3)。
写作本章时实测:println(u8) 这类直接打印无符号整型的写法会触发运行时链接错误(详见常见错误坑 4)。在修复版本发布前,请用 println(string(u8)) 或先装入 var 再打印。本书后续示例会主动避开这个缺陷。
float a = 1.25 // 小数形式 float b = 1e3 // 科学计数法 = 1000.0 float c = 1.2e-3 // = 0.0012 println(a, b, c) float32 f32 = 3.14 float64 f64 = 2.718281828459045 println(f32, f64)
float 不带后缀时是 64 位双精度(等价 float64);float32 是 32 位单精度。注意上面第二行输出:float32 存不下 3.14 的精确值,打印出来是 3.140000104904175——这不是 bug,而是 IEEE 754 单精度浮点的固有精度(拾遗 2 展开)。
e 或 E 表示"×10 的多少次方",1e3 = 1×10³。没有小数点也没有 e 的字面量是整数;想让整数当浮点用,写 1.0 或用转换函数(第 7 章)。xlang 有六种字符串写法,应对六种场景。
string a = "hello" string b = 'world' // 单双引号完全等价 string e = "换行\n制表\t引号\"反斜杠\\"
普通字符串支持转义序列:\n 换行、\t 制表、\" 引号、\\ 反斜杠等。选择单引号还是双引号的唯一理由:少写转义——字符串里有双引号就用单引号包,反之亦然。
r"..." / r'...'——不处理转义println(r"a\nb") // 输出: a\nb(\n 是字面两个字符) println(r"c:\new\file.txt") // 输出: c:\new\file.txt
前缀 r 表示 raw(原样):反斜杠不再有任何特殊含义。写 Windows 路径、正则表达式时,raw 字符串能省掉成倍的反斜杠。
"""...""" / '''...'''——多行字符串string a = """ 第一行 第二行 """ string b = ''' 单引号三引号 也可以 '''
三引号之间的换行原样保留,适合大段文本、SQL、HTML 模板。注意上面变量 a 的值以换行开头、以换行结尾——三引号"原样保留"包括首尾换行。
前缀 f 让字符串支持 {表达式} 插值——这是拼接文本最顺手的方式:
string name = "xlang" int ver = 6 println(f"hello {name}") // hello xlang println(f"1 + 2 = {1 + 2}") // 1 + 2 = 3 —— 任意表达式 println(f"{name} DEMO{ver}") // xlang DEMO6 println(f"字面量大括号 {{ }}") // 字面量大括号 { } println(rf"c:\tmp\{name}.txt") // c:\tmp\xlang.txt
要点:
{...} 里可以放任意表达式,不只变量名;{{ 和 }};rf"..." 是 raw + f-string 的组合:反斜杠不转义,但插值仍然生效——Windows 路径插值的完美组合;{} 是解析错误(见常见错误坑 5),大括号必须配对。println 多参数输出的选择:多参数适合快速调试打印;f-string 适合需要精确控制格式的正式输出。第 19 章会讲更完整的格式化能力。时间是 xlang 的内置类型 time,字面量用 # 包裹,三种形态:
time d = #2026-06-18# // 纯日期 time t = #12:30:45# // 纯时间 time dt = #2026-06-18 12:30:45# // 日期时间 println(d) println(t) println(dt)
三行输出都值得读:纯日期的时间部分是 00:00:00;纯时间的日期部分显示为 0-01-01("第 0 年第 1 天"的零值日期,拾遗 3 展开)。
两条边界规则:时间字面量内部不能换行;忘记写结尾的 # 会得到专门的报错"未闭合的时间字面量"(常见错误坑 2)。时间的加减、比较、格式化是第 20 章的内容。
var 是"什么都能装"的动态类型,同一个变量先后可以装不同类型的值:
var v = 123 println(type(v)) // int v = "现在变字符串" println(type(v)) // string
var 适合容器里的混合数据、脚本快速原型。代价是类型错误要到运行时才暴露——第 15 章会专门讨论何时该用 var、何时该用静态类型。
auto n = 42 // 推断为 int auto s = "hi" // 推断为 string println(type(n), type(s)) // int string
关键区别:auto 推断发生在编译期,推断完成后变量就是静态类型,之后不能再装别的类型;var 是运行时动态类型,可以随时换装。一句话:auto 是"帮我写类型",var 是"不要类型"。
type(expr) 返回表达式的类型值,类型值之间可以比较。本章已经多次用它验证类型。类型系统的完整玩法(typedef、类型比较、泛型)在第 15 章。
point p println(type(p)) // point
point 是零开销的静态不透明指针标签,对应 C 的 void*;point<T> 是带标签的版本,不同标签之间不能隐式转换。它不面向日常编程——你在调用 C 库、处理原生句柄时才会用到它(第 32 章详解)。现在只需知道:基本类型全家福里有它一个位置。
很多语言的 int 是 32 位,xlang 不是。实测:int a = 2147483648(超出 32 位上限 1)声明成功,int 可以一直用到 9223372036854775807(2⁶³−1)。工程建议:日常写代码用 int 即可,不必纠结宽度;只有在对二进制布局有精确要求时(读写文件格式、网络协议、C 结构体映射)才使用 int8/int16/int32 等定宽类型——定宽类型的价值是"布局确定",不是"省内存"。
3.4 节实测 float32 f32 = 3.14 打印出 3.140000104904175。原因:IEEE 754 单精度浮点只有 23 位尾数(约 7 位十进制有效数字),3.14 在二进制下是无限循环小数,只能存最近似值。双精度 float64 有 52 位尾数(约 15–16 位十进制有效数字),日常计算足够。推论:不要用 == 直接比较浮点数,金额计算不要用二进制浮点——这是所有 IEEE 754 语言的共同课题,第 6 章讲比较运算时还会提醒。
#12:30:45# 打印为 0-01-01 12:30:45:time 是"日期+时间"的统一类型,纯时间字面量只填时间部分,日期部分保持零值,显示出来就是第 0 年 1 月 1 日。做时间比较和计算时这个零值日期通常无害,但把时间值格式化输出给用户看时,应只取时间部分(第 20 章的时间格式化会讲)。
以下报错均为真实输出。
原因:xlang 只保留小写规范形式 0x/0o/0b。词法器读到 0 就按十进制整数收尾,后面的 X2A 无法接续,于是在第 10 列报语法错误。历史语言里的大写 0X、VB 风格 &H 在 xlang 中一律不存在。正确做法:统一小写 0x2A。
原因:词法器对这三类字面量(字符串、时间、多行注释)给出了专属错误,而不是笼统的"语法错误"。报错列号指向字面量开始扫描后"发现收不了尾"的位置。正确做法:补上配对的引号或 #;普通字符串不能跨行,要跨行请用三引号。
原因:两条都是静态类型检查([sem])在编译期拦下的问题。第一条:xlang 不会偷偷把字符串转成整数;第二条:200 超出 int8 上限 127,编译器宁可报错也不静默截断。正确做法:类型不一致时用显式转换(int("123") 等,第 7 章);定宽类型声明前先确认取值范围。
原因:这不是你的代码有问题——实测确认 RC0 中 println 打印任何无符号整型(uint8/16/32/64)都会因为缺少对应的运行时转换函数而链接失败,是编译器/运行时的已知缺陷。正确做法(修复前的变通):println(string(a)) 先转成字符串,或 var v = a 装入动态类型再打印,两种写法均已实测可用。有符号整型(含 int64)不受影响。
原因:{} 必须成对且内部必须是合法表达式。正确做法:插值写完整;想要字面大括号就写 {{ 和 }}。
| 知识点 | 速查 |
|---|---|
| 类型声明 | C 风格类型前置 int a = 1;静态类型,编译期检查。 |
| bool / null | true/false 全小写;null 表示无值,用 == null 判断。 |
| 整数字面量 | 十进制、1_000_000 下划线、0x/0o/0b 仅小写;int 默认 64 位。 |
| 定宽整数 | int8/16/32/64 与 uint8/16/32/64;字面量超范围编译期报错;RC0 打印无符号需 string() 或 var 变通。 |
| 浮点 | 1.25、1e3、1.2e-3;float = float64;float32 仅约 7 位有效数字。 |
| 字符串六种 | "" '' 普通(支持 \n \t \" \\);r"" r'' 原样;"""...""" '''...''' 多行。 |
| f-string | f"{任意表达式}";{{ }} 字面大括号;rf"" 原样+插值。 |
| time 字面量 | #日期# #时间# #日期 时间#;内部不可换行;纯时间日期部分为零值。 |
| var / auto | var 运行时动态、可换装;auto 编译期推断、推断后静态。 |
| type() | type(expr) 返回类型值,随时验证类型。 |
| point | 静态不透明指针标签,对应 C void*,C 互操作专用(第 32 章)。 |
int、int32、uint8 三个类型的取值范围分别是什么?什么场景才需要后两个?r"c:\new" 和 "c:\\new" 的值是否相同?raw 字符串的适用场景是什么?var 和 auto 都"不用写类型",它们的本质区别是什么?float32 f = 3.14 打印出来不是 3.14?由此得出什么工程结论?f"{{count}} = {count}" 中,哪对花括号会被求值、哪对会原样输出?编写 bases.xl:声明同一个数值(例如 42)的十进制、十六进制、八进制、二进制四种字面量形式,用 println 输出验证它们相等;再用 f-string 输出一行形如 0x2A = 42 的说明。
参考思路:四种前缀的字面值相同则打印结果相同;f-string 里 0x2A 是普通文本,直接写即可,注意前缀小写。
编写 detective.xl:分别用 type() 打印以下表达式的类型——42、42.0、"42"、true、null、1e3、#2026-01-01#。先把你的预测写在注释里,再运行对照。
参考思路:42.0 和 1e3 都是 float;"42" 是 string 不是数字。预测错的地方往往就是理解盲区,值得回到对应小节重读。
编写 card.xl:声明姓名(string)、年龄(int)、生日(time)、是否学生(bool)四个变量,用三引号多行字符串 + f-string 输出一张带边框的文本名片。
参考思路:三引号保留换行,f-string 负责插值,但二者不能直接合并前缀——可以先 f 插值出各行再拼接,或直接在 f-string 里用 \n。边框用 - 和 | 字符即可。