程序与外界对话的通道。print 家族四个函数的确切差异,一次讲清。
从第 1 章到现在,我们已经用了几十次 print 和 println,但一直是"会用"而非"懂"。这一章把输出这件事彻底讲透:print 家族四个函数的精确差异、各种类型打印出来长什么样、命令行参数怎么进程序,以及一个诚实的现状说明——xlang 当前版本如何"读"输入。
卷一至此收官:装好工具(第 1 章)、看懂结构(第 2 章)、认识数据(第 3 章)、学会存取(第 4 章)、能进能出(本章)。从卷二开始,我们让程序"动起来":运算与控制。
精确区分 print/println/write/writeln;掌握各类型的打印格式;会用 arg 接收命令行参数;了解当前版本读取输入的两条现实途径(命令行参数、文件)。
print 家族四个全局函数,分两组:
| 函数 | 参数 | 参数间空格 | 末尾换行 | 值格式化 |
|---|---|---|---|---|
print(...) | 任意数量、任意类型 | 自动加一个空格 | 不加 | 有 |
println(...) | 任意数量、任意类型 | 自动加一个空格 | 加(\n) | 有 |
write(text) | 恰好一个 string | 无(只有一个参数) | 不加 | 无,原样输出 |
writeln(text = "") | 恰好一个 string | 无 | 加(\r\n) | 无,原样输出 |
一句话记忆:print 组管"值",write 组管"字"。print 组接收任意值并负责把它们变成文本(值格式化);write 组只接受已经是字符串的文本,原样写出,不做任何加工。
注意一个实测差异:println 追加的换行是 \n,而 writeln 追加的是 \r\n(Windows 风格的回车换行)——深入细节拾遗 1 有字节级证据。
值输出两兄弟的唯一区别是末尾换不换行。看一组实测(输出用字节形式展示):
print("a") print("b") println("c") println("d", "e") print(1, 2.5, true) println()
逐行读:print("a") 和 print("b") 不换行,所以 ab 连在一起,直到 println("c") 才把这一行收尾;多个参数之间自动插入一个空格(d e、1 2.5 true);println() 空参数调用只输出一个换行——这是打印空行的惯用法。
\n。write 组是"原样写出"的纯文本通道:
write("no") write("space") writeln("!") writeln() // 等价于 writeln(""),输出空行
与 print 组的三条硬差异:
writeln 的参数有默认值 "",所以 writeln() 合法且只输出换行。那 write 组有什么用?它和 f-string 是黄金搭档:先用 f-string 把值精确拼成想要的文本,再用 write 原样输出,不引入任何额外的空格或格式:
int n = 42 write(f"n={n}") // 输出 n=42,无多余空格、无换行 writeln(f"n={n}") // 输出 n=42 并换行
更完整的格式化能力(对齐、位数控制等)是第 19 章的主题。
print 组的"值格式化"把各种类型变成合理文本。实测一屏看全:
println(42, 3.14, true, null, #2026-07-18#) println([1, 2, 3]) println({"a": 1})
格式约定:
true/false、null 直接输出字面;time 输出为 年-月-日 时:分:秒(纯日期的时间部分是 00:00:00,第 3 章讲过零值日期);[1,2,3]、字典 {"a":1}——调试时一眼看清内容(容器的正式学习在卷四)。第 3 章坑 4 讲过的 RC0 已知问题在本章依然适用:println 直接打印无符号整型(uint8/16/32/64)会触发运行时链接错误。变通:println(string(u)) 或先装入 var。详见常见错误坑 4。
命令行参数是程序最常用的一种"输入"。第 2 章已经见过机制,这里做一次完整复盘:
func main(...) as int if arg.length < 2 then println("用法: xl run args.xl -- 名字") return 1 end println(f"你好, {arg[1]}!") return 0 end
要点回顾:-- 之后的参数进入 arg 数组;arg[0] 是脚本路径本身,第一个用户参数是 arg[1];arg.length 取参数个数;下标 arg[1] 取具体参数(数组下标的完整规则见第 16 章)。配合退出码,一个行为规范的命令行工具就成型了。
讲了半天"输出","输入"呢?这里要诚实地说明当前版本(DEMO6 RC0)的状况:
标准库目前没有"从控制台读一行"这类交互输入函数。process 命名空间里只有 process.stdin/stdout/stderr 三个标准流编号常量(用于子进程管道,第 41 章前后才会用到),没有面向终端用户的 readline 式 API。所以当前程序获取输入的现实途径有两条:
file.readText 读取整个文件为字符串:string text = file.readText("input.txt") print(text)
文件读写(file 命名空间)与更丰富的数据通道将在卷四之后展开;控制台交互输入如果进入后续版本标准库,本书会在卷四补充。现在写练习程序时,请用命令行参数或读文件的方式"喂数据"。
把 println("x") 和 writeln("y") 的输出重定向后用字节工具查看,实测结果是 78 0A 79 0D 0A——即 x、\n、y、\r\n。两个函数的换行约定不同:print 组追加 Unix 风格 \n,writeln 追加 Windows 风格 \r\n(回车+换行)。Windows 记事本等老工具只认 \r\n,现代编辑器和终端两者都认。写文本文件或对接老式 Windows 工具时,这个差异值得记住。
通常语言打印最终落到 C 的 printf/fwrite。xlang 不是:生成代码中的 xl6_xrt_print 不使用 C FILE*、stdout,Windows 下直接通过标准输出句柄调用 WinAPI 写入,POSIX 下直接 write(1, ...)。这样做砍掉了对 C 标准库 stdio 的依赖(与全书反复出现的"零外部依赖"设计一致),也让输出行为在所有平台上完全可控。值到文本的转换(整数、浮点、时间、容器的 JSON 风格文本)则由 XRT 的转换能力完成。
[1,2,3]、{"a":1} 这种输出不是巧合:print 的值格式化复用了 XRT 的 JSON/XSON 序列化能力。这意味着打印结果往往就是合法的 JSON——调试时可以直接把输出贴进 JSON 工具里检查。但注意它是紧凑格式(无空格无缩进),仅供调试阅读;正式的 JSON 序列化 API 在第 52 章(JSON 解析器项目)前后会详细使用。
以下报错与输出均为真实结果。
原因:print 不追加换行,三个调用的输出首尾相接;参数之间的空格是"同一调用内多参数"才有的。正确做法:每个独立一行用 println;或在字符串里显式写 \n。
原因:write 只接受一个参数——多参数逗号分隔是 print 组的能力。报错里 expected 1, actual 2 把签名和实参数量说得明明白白。正确做法:多值输出用 print/println;要精确拼接先 f-string 成一个字符串再 write。
原因:write 不做值格式化,这是它与 print 的分工。正确做法:write(f"{42}") 或 write(string(42));或者干脆用 print(42)。
原因:第 3 章坑 4 已登记的编译器缺陷——println 对 uint 全族的值格式化缺少运行时函数。这不是你的代码错误。正确做法(变通):println(string(port)) 或 var v = port; println(v),两种写法均实测可用;有符号整型不受影响。
| 知识点 | 速查 |
|---|---|
| print(...) | 任意数量任意类型,参数间一个空格,不换行。 |
| println(...) | 同 print,末尾加 \n;println() 打空行。 |
| write(text) | 恰好一个 string,原样输出,不换行不加空格。 |
| writeln(text) | 同 write,末尾加 \r\n;参数默认 ""。 |
| 选型口诀 | print 组管"值",write 组管"字";精确格式用 f-string + write。 |
| 类型打印 | 数字原样、true/false/null 字面、time 全格式、容器 JSON 紧凑风格。 |
| uint 打印 | RC0 缺陷:需 string(u) 或装入 var 变通。 |
| 命令行参数 | xl run p.xl -- a b;arg[0] 是脚本路径,用户参数从 arg[1] 起;arg.length 计数。 |
| 读取输入 | RC0 无控制台交互输入函数;用命令行参数或 file.readText(path) 读文件。 |
| 格式化 | f-string 当前主力;完整格式化第 19 章。 |
print 和 println 的区别是什么?write 和 writeln 呢?两对答案一样吗?println("a", "b") 与 write("a", "b") 各自的命运如何?为什么?n=42(无空格无换行),有哪些写法?xl run tool.xl -- report.csv 中,程序里 arg[0]、arg[1]、arg.length 分别是什么?编写 family.xl:用 print、println、write、writeln 各输出同一句话两次,运行后把输出重定向到文件(xl run family.xl > out.txt),用编辑器查看,逐字节解释每处换行与空格来自哪个函数的哪条规则。
参考思路:重点观察 println 与 writeln 换行的不同(\n vs \r\n,Windows 下可用 VS Code 状态栏或 od -c 查看)。
编写 calc.xl:接收两个命令行参数作为操作数,打印它们的和。提示:参数是字符串,用 int(arg[1]) 转换(第 7 章正式讲转换,先用起来);参数不足两个时打印用法并 return 1。
参考思路:xl run calc.xl -- 3 4 应输出 7。这个小程序把 arg、类型转换、退出码串在了一起,是命令行工具的完整雏形。
编写 echo_file.xl:从命令行参数接收一个文件路径,用 file.readText 读出内容,再原样打印到控制台。用本书任意一章的 .xl 源文件做测试。
参考思路:路径来自 arg[1];打印用 print(文件内容自带换行,用 println 会多一个空行——试一次就理解为什么)。