集合与节点
到目前为止,我们创建的每个变量都只保存一个值——一个数字、一个字符串、一个布尔值。但真正的程序处理的是一组组事物:一组名字、一张成绩表、一组唯一 ID。Auto 提供了多种集合类型,让你可以存储、查询和转换多个值。在本章中,我们还将认识 node——Auto 中用于构建链式结构(如链表和树)的关键字。
在本章中,我们将讨论:
- 数组——使用
[1, 2, 3]字面量语法创建的有序序列 - 对象——使用
{ name: "Alice", age: 30 }创建的命名字段记录 - HashMap——从字符串键字面量推断出的键值映射
- Set——使用
{1, 2, 3}创建的去重集合 - 列表操作——
push、pop、contains、sort等 - 字符串作为集合——
len、索引、split、to_upper - 节点——使用
node、?Item和none构建链式结构
数组
数组 是一个有序的、可索引的值集合。你用方括号和逗号分隔的元素列表来创建它:
var numbers = [1, 2, 3, 4, 5]类型从元素中推断——因为这里的每个值都是 int,所以 numbers 的类型是 [int]。你可以用 numbers[0] 通过从零开始的索引访问元素,用 numbers.len() 获取长度。你可以用 for item in array 遍历数组。
输出:
1
5
Alice
Bob
Charlie注意转译器如何将数组概念适配到每种语言:
- Rust 使用
Vec<i64>——一个堆分配的动态向量。for name in &names循环借用了每个元素。 - Python 保持原生列表语法
[1, 2, 3]。内置函数len()替代了 Auto 的.len()方法。 - C 使用普通 C 数组
long long numbers[]和一个单独的int存储长度。遍历使用带索引变量的传统for循环。 - TypeScript 使用
number[]语法。for...of循环直接遍历数组,.length替代了.len()。
对象
对象 是一组命名字段的集合。你用花括号和 key: value 对来创建它:
var person = { name: "Alice", age: 30 }类型从字段中推断——person 有 name(类型 str)和 age(类型 int)两个字段。你用点号访问字段(person.name),用点号赋新值(person.age = 31)。因为我们用 var 声明了 person,所以对象是可变的。
输出:
Alice
30
31每种语言处理类对象数据的方式不同:
- Rust 使用
AutoObject,一个动态键值存储。字段通过.set()和.get()设置和获取,值包装在AutoValue中。 - Python 直接映射到字典
dict。Auto 中的点号访问变成了 Python 中的括号访问person["name"]。 - C 生成一个
typedef struct,包含命名字段。这是最接近的映射——C 结构体和 Auto 对象都使用固定布局和点号访问。 - TypeScript 使用内联类型
{ name: string; age: number }。点号访问和 Auto 完全一样。
HashMap
当对象字面量使用字符串键时,Auto 会推断为 HashMap 而不是普通对象:
var scores = { "Alice": 90, "Bob": 85, "Charlie": 92 }与普通对象的区别在于,你用括号记法访问值(scores["Alice"]),而且映射可以在运行时增长——你可以插入新的键。类型被推断为 Map<str, int>。
输出:
90
4Auto 中对象和 HashMap 的关键区别在于 键类型。对象使用标识符键({ name: "Alice" }),通过点号访问。HashMap 使用字符串键({ "Alice": 90 }),通过括号访问。编译器根据语法来判断你要的是哪一种。
看看每种语言如何处理这种情况:
- Rust 使用
std::collections::HashMap<String, i64>,配合.insert()和.get()。.get()上的.unwrap()在这里是安全的,因为我们知道键存在。 - Python 使用普通的
dict——与 Auto 语法相同。括号访问和len()自然可用。 - C 没有内置的哈希映射,所以转译器生成了一个基于链表的映射,包含
map_insert和map_get辅助函数。 - TypeScript 使用索引签名
{ [key: string]: number }和Object.keys()获取长度。
Set
Set 是一个唯一值集合。你用花括号和逗号分隔的值来创建它——语法与对象相同,但元素不是字符串:
var unique = {1, 2, 3, 2, 1}重复值会自动被丢弃。编译器推断为 Set<int>,因为元素是整数。你可以用 .add() 添加元素,用 .contains() 检查成员。
输出:
3
true集合初始有五个值,但只有三个是唯一的,所以 len() 返回 3。添加 4 后,contains(3) 返回 true,因为 3 在原始集合中。
注意语言差异:
- Rust 使用
std::collections中的HashSet<i64>。.insert()方法会静默忽略重复项,.contains()接受一个引用。 - Python 使用原生
set字面量。in运算符替代了 Auto 的.contains()方法。 - C 生成了一个基于链表的集合,配有辅助函数。插入前会检查重复。
- TypeScript 使用
Set<number>。.size属性替代了.len(),.has()替代了.contains()。
Auto 如何区分 Set {1, 2, 3}、对象 { name: "Alice" } 和 HashMap { "Alice": 90 }?通过 键 的形式:
- 标识符键(无引号):
{ name: "Alice" }是对象。 - 字符串键(有引号):
{ "Alice": 90 }是 HashMap。 - 无键(只有值):
{1, 2, 3}是 Set。
编译器使用这些语法线索来推断正确的类型。你永远不需要显式写 HashMap<String, i64> 或 HashSet<int>——Auto 会自己搞定。
列表操作
数组除了简单的索引之外,还支持丰富的操作。你可以用 .push() 添加元素,用 .pop() 移除最后一个元素,用 .contains() 检查成员,用 .sort() 原地排序。
输出:
[10, 20, 30, 40]
40
true
[10, 20, 30]列表操作映射到每种语言中的常见模式:
- Rust 使用
Vec::push()、Vec::pop()(返回Option<i64>)和Vec::contains()。使用{:?}调试格式打印整个向量。 - Python 使用
.append()替代.push(),in运算符替代.contains()。.sort()的工作方式相同。 - C 需要一个自定义的
Vec结构体,配合手动内存管理。标准库中的qsort负责排序。 - TypeScript 使用
.push()、.pop()和.includes()。.sort()方法 需要比较器函数(a, b) => a - b才能按数字排序——没有它,TypeScript 会 按字典序排序。
字符串作为集合
Auto 中的字符串是字符的集合。你可以用 .len() 获取长度,用索引访问单个字符,用 .split() 将字符串拆分为数组,用 .to_upper() 和 .to_lower() 转换大小写。
输出:
12
H
["Hello", "Auto!"]
HELLO, AUTO!字符串方法在所有四种目标语言中都是一致的,但细节有所不同:
- Rust 使用
.len()获取字节长度,.chars().nth(0)访问字符(因为 Rust 字符串是 UTF-8 编码的),.split(", ").collect()拆分,.to_uppercase()转换大写。 - Python 使用内置函数
len(s),s[0]索引访问,s.split(", ")拆分,s.upper()转大写。 - C 使用
strlen()获取长度,直接数组索引s[0],strtok()拆分,手动 循环配合toupper()转换大写。 - TypeScript 使用
.length(属性,不是方法),s[0]索引访问,.split(", ")拆分,.toUpperCase()转大写。
节点
数组、对象和映射都是 连续 数据结构——它们的元素在内存中相邻存储。有时候你需要一种 链式 结构,其中每个元素指向下一个元素。想象一条链条:每个环节连接到下一个环节,最后一个环节什么都不连接。
Auto 提供了 node 关键字来实现这种模式。节点类似于轻量级的结构体,其中一个或多个字段可以是 可选的——它们可能指向另一个节点,也可能是 none。
定义节点
你用 node 关键字、字段名和类型来定义一个节点。? 前缀表示"可选"——值可以是一个实际实例,也可以是 none:
node Item {
value int
next ?Item
}这定义了一个名为 Item 的节点,包含两个字段:value(整数)和 next(另一个 Item 或 none)。?Item 类型是 Auto 表达"可空 Item"的方式——类似于 Rust 中的 Option<Item> 或 TypeScript 中的 Item | null。
输出:
10
20我们从后往前构建链。先创建 third(值 30,next 为 none),然后 second(值 20,next 指向 third),最后 first(值 10,next 指向 second)。访问 first.next.value 时,从第一个节点沿着链接走到第二个节点并读取其值。
转译器将 node 映射到每种语言中惯用的链式结构:
- Rust 使用
struct,next字段类型为Option<Rc<RefCell<Item>>>。Rc(引用计数)允许多重所有权,RefCell提供内部可变性。这是 Rust 中链式 结构的标准模式。 - Python 使用带有
__init__的class。next字段默认为None。Python 的动态类型让这很自然。 - C 使用
typedef struct,配合指针struct Item* next。item_new()辅助 函数用malloc()分配内存。NULL替代none。 - TypeScript 使用
class,next: Item | null。非空断言first.next!.value告诉 TypeScript 该处的值不为 null。
遍历节点链
访问 first.next.value 对于两层深度可以工作,但真正的链式结构可以任意长。标准模式是从头部开始,逐个走过每个节点,直到到达 none:
输出:
10
20
30遍历模式在每种语言中都是相同的:
- 从
current = first(链的头部)开始。 - 当
current不为none/NULL/null时循环。 - 打印
current.value。 - 移动到下一个节点:
current = current.next。
在 Auto 中,for current != none { ... } 是一个条件循环——只要条件为真就持续运行。转译器将其转换为每种目标语言中的 while,因为 for cond 是 Auto 书写其他语言中 while 的方式。
注意遍历代码在所有五种语言中几乎完全相同。链表遍历是那种 逻辑 通用但只有"是否为空"的语法不同的模式之一:
| 语言 | 空值检查 | 空值 |
|---|---|---|
| Auto | current != none | none |
| Rust | current.is_some() | None |
| Python | current is not None | None |
| C | current != NULL | NULL |
| TypeScript | current !== null | null |
实用排序与矩阵操作
上面的集合演示了基本构建块。以下是三种常用操作:排序基本类型、按键排序结构体和矩阵运算。
总结
在本章中,你学习了如何在 Auto 中使用集合和链式结构:
- 数组
[1, 2, 3]存储有序序列。通过arr[0]索引访问,通过arr.len()获取长度,通过for item in arr遍历。 - 对象
{ name: "Alice" }存储命名字段。通过点号访问(obj.name),通过 赋值修改(obj.age = 31)。 - HashMap
{ "Alice": 90 }从字符串键对象字面量推断。通过括号访问 (map["Alice"]),通过括号赋值插入。 - Set
{1, 2, 3}存储唯一值。通过.add()添加,通过.contains()检查成员。 - 列表操作——
.push()、.pop()、.contains()、.sort()——让你原地 操作数组。 - 字符串作为集合——
.len()、s[0]、.split()、.to_upper()——将 字符串视为字符序列。 - 节点 使用
node和?T构建链式结构。none标记链的末端。遍历使用for current != none { ... }。
编译器从语法中推断集合类型——你永远不需要写 Vec<i64>、HashMap<String, i64> 或 HashSet<i64>。Auto 从字面量中读取你的意图,选择正确的类型。在 下一章中,我们将了解如何用 type 定义自己的自定义类型,并用 ext 构建方法。