Skip to content
📖 Auto 编程语言(主书)本书目录

集合与节点 ​

到目前为止,我们创建的每个变量都只保存一个值——一个数字、一个字符串、一个布尔值。但真正的程序处理的是一组组事物:一组名字、一张成绩表、一组唯一 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 构建链式结构

数组 ​

数组 是一个有序的、可索引的值集合。你用方括号和逗号分隔的元素列表来创建它:

auto
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 对来创建它:

auto
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 而不是普通对象:

auto
var scores = { "Alice": 90, "Bob": 85, "Charlie": 92 }

与普通对象的区别在于,你用括号记法访问值(scores["Alice"]),而且映射可以在运行时增长——你可以插入新的键。类型被推断为 Map<str, int>。

从字符串键字面量推断 HashMap

输出:

90
4

Auto 中对象和 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 是一个唯一值集合。你用花括号和逗号分隔的值来创建它——语法与对象相同,但元素不是字符串:

auto
var unique = {1, 2, 3, 2, 1}

重复值会自动被丢弃。编译器推断为 Set<int>,因为元素是整数。你可以用 .add() 添加元素,用 .contains() 检查成员。

Set 字面量的去重和成员检查

输出:

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() 原地排序。

列表的 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:

auto
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

遍历模式在每种语言中都是相同的:

  1. 从 current = first(链的头部)开始。
  2. 当 current 不为 none / NULL / null 时循环。
  3. 打印 current.value。
  4. 移动到下一个节点:current = current.next。

在 Auto 中,for current != none { ... } 是一个条件循环——只要条件为真就持续运行。转译器将其转换为每种目标语言中的 while,因为 for cond 是 Auto 书写其他语言中 while 的方式。

注意遍历代码在所有五种语言中几乎完全相同。链表遍历是那种 逻辑 通用但只有"是否为空"的语法不同的模式之一:

语言空值检查空值
Autocurrent != nonenone
Rustcurrent.is_some()None
Pythoncurrent is not NoneNone
Ccurrent != NULLNULL
TypeScriptcurrent !== nullnull

实用排序与矩阵操作 ​

上面的集合演示了基本构建块。以下是三种常用操作:排序基本类型、按键排序结构体和矩阵运算。

对整数进行升序排序
使用 sort_by_key 按字段排序结构体
使用嵌套数组进行二维矩阵加法

总结 ​

在本章中,你学习了如何在 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 构建方法。

基于 MIT 许可发布。