6.1 表的创建 Lua 表 (Table) 的创建详解 在 Lua 语言中,表 (Table) 是一个强大且核心的数据结构。它不仅仅是一个简单的数组或哈希表,更是一种动态关联数组,能够容纳各种类型的数据,并作为 Lua 中实现模块、类和对象的基础。理解表的创建方式是掌握 Lua 编程的关键一步。 空表的创建 最简单的表创建方式就是创建一个空表。空表不包含任何键值对,可以将其视为一个容器,之后可以动态地向其中添加元素。 代码实践: 内容详解: : 这行代码声明了一个局部变量 ,并使用花括号 初始化它为一个空表。 是 Lua 中创建空表的字面量语法。 : 当我们直接打印一个表时,Lua 会输出表的类型 (table) 以及它在内存中的地址 (0xxxxxxxxx)。
在 Lua 语言中,表 (Table) 是一个强大且核心的数据结构。它不仅仅是一个简单的数组或哈希表,更是一种动态关联数组,能够容纳各种类型的数据,并作为 Lua 中实现模块、类和对象的基础。理解表的创建方式是掌握 Lua 编程的关键一步。
最简单的表创建方式就是创建一个空表。空表不包含任何键值对,可以将其视为一个容器,之后可以动态地向其中添加元素。
代码实践:
-- 创建一个空表 local emptyTable = {} -- 打印空表(会输出 table: 0xxxxxxxxx,地址可能不同) print(emptyTable) -- 检查空表的大小 (使用 # 运算符,对于空表返回 0) print("空表的大小:", #emptyTable)
内容详解:
local emptyTable = {}: 这行代码声明了一个局部变量 emptyTable,并使用花括号 {} 初始化它为一个空表。 {} 是 Lua 中创建空表的字面量语法。
print(emptyTable): 当我们直接打印一个表时,Lua 会输出表的类型 (table) 以及它在内存中的地址 (0xxxxxxxxx)。这表明 emptyTable 确实被创建为一个表类型。
print("空表的大小:", #emptyTable): # 运算符在 Lua 中用于获取表的“长度”。对于一个序列式表(我们将在后面讨论),# 返回的是最大的正整数索引值。对于空表,由于没有任何元素,所以 #emptyTable 返回 0。
总结:
使用 {} 字面量是创建空表最简洁的方式。空表是动态的,可以根据需要随时添加键值对。
除了空表,我们通常需要在创建表的同时赋予一些初始值。Lua 提供了多种方式来创建带有初始值的表,主要可以分为以下几类:
序列式表类似于其他语言中的数组,它的键是连续的正整数,从 1 开始。我们可以使用花括号 {} 并在其中列出初始值来创建序列式表。
代码实践:
-- 创建一个序列式表,包含三个元素 local numericTable = {"apple", "banana", "orange"} -- 打印整个表 print(numericTable) -- 访问表中的元素 (使用数字索引,从 1 开始) print("第一个元素:", numericTable[1]) print("第二个元素:", numericTable[2]) print("第三个元素:", numericTable[3]) -- 检查序列式表的大小 print("序列式表的大小:", #numericTable) -- 遍历序列式表 print("遍历序列式表:") for i = 1, #numericTable do print(i, numericTable[i]) end
内容详解:
local numericTable = {"apple", "banana", "orange"}: 这行代码创建了一个名为 numericTable 的表。花括号 {} 内的 "apple", "banana", "orange" 是初始值列表,它们会被依次赋值给表的数字索引 1, 2, 3。
print(numericTable): 打印序列式表时,Lua 会以类似数组的形式输出,例如 table: 0xxxxxxxxx { [1] = "apple", [2] = "banana", [3] = "orange" }。
print("第一个元素:", numericTable[1]) 等: Lua 表使用方括号 [] 来访问元素,索引从 1 开始。 numericTable[1] 访问的是索引为 1 的元素,即 "apple"。
print("序列式表的大小:", #numericTable): 对于序列式表,#numericTable 返回的是表中最大的连续正整数索引。在这个例子中,最大连续索引是 3,所以返回 3。
遍历序列式表: for i = 1, #numericTable do ... end 是一个典型的遍历序列式表的循环。它从索引 1 遍历到 #numericTable,依次访问表中的每个元素。
重要概念:序列式表的 "长度" (# 运算符)
对于序列式表,# 运算符的行为需要特别注意。它返回的是表中最大的连续正整数索引。这意味着:
如果表是连续的数字索引,# 会返回正确的元素个数。
如果表中间存在 "空洞" (即缺失某些数字索引),# 可能会返回不准确的结果。
示例:带有 "空洞" 的序列式表
local tableWithHole = {10, 20, [5] = 50, 60} print(tableWithHole) -- 输出: table: 0xxxxxxxxx { [1] = 10, [2] = 20, [5] = 50, [3] = 60 } print(#tableWithHole) -- 输出: 3 (因为索引 1, 2, 3 是连续的,到 3 之后就不连续了)
在这个例子中,虽然表中有 4 个元素,但是 #tableWithHole 返回 3,因为索引 4 缺失了 (索引 1, 2, 3 是连续的,但索引 4 缺失,索引 5 不参与连续性判断)。
总结:
序列式表适用于存储有序的数据集合,类似于数组。使用 {} 字面量并列出值即可创建序列式表。# 运算符对于连续索引的序列式表能够正确返回长度,但对于存在 "空洞" 的表需要谨慎使用。
关联式表使用任意类型的值 (除了 nil) 作为键 (key),并将其与对应的值 (value) 关联起来。这类似于其他语言中的哈希表或字典。
最常见的关联式表是使用字符串作为键。我们可以使用两种语法来创建带有字符串键的表:
语法一: key = value 形式 (仅适用于合法的 Lua 标识符作为键)
local stringTable1 = { name = "Alice", age = 30, city = "New York" } print(stringTable1) print("姓名:", stringTable1.name) -- 使用点号 . 访问 print("年龄:", stringTable1["age"]) -- 使用方括号 [] 访问
语法二: ["key"] = value 形式 (适用于任意字符串作为键)
local stringTable2 = { ["first name"] = "Bob", -- 键包含空格,必须使用方括号 ["last-name"] = "Smith", -- 键包含连字符,必须使用方括号 ["age"] = 25 } print(stringTable2) print("名字:", stringTable2["first name"]) print("姓氏:", stringTable2["last-name"]) print("年龄:", stringTable2["age"])
内容详解:
local stringTable1 = { name = "Alice", ... }: 这种语法形式简洁,适用于键是合法的 Lua 标识符 (字母、数字、下划线,不能以数字开头)。 Lua 会将 name, age, city 解释为字符串字面量 "name", "age", "city"。
print("姓名:", stringTable1.name): 可以使用点号 . 运算符来访问字符串键的表元素,这是一种更简洁的语法,类似于访问对象的属性。 stringTable1.name 等价于 stringTable1["name"]。
local stringTable2 = { ["first name"] = "Bob", ... }: 这种语法形式更通用,可以使用任意字符串作为键,包括包含空格、特殊字符或不是合法标识符的字符串。 键需要用方括号 [] 和双引号 "" 或单引号 '' 包裹。
print("名字:", stringTable2["first name"]): 对于使用 ["key"] = value 形式创建的表,或者键不是合法标识符的情况,必须使用方括号 [] 语法来访问元素。
总结:
使用字符串键的关联式表非常灵活,可以用于表示各种结构化数据,例如配置信息、对象属性等。 key = value 语法更简洁,但仅限于合法标识符键。 ["key"] = value 语法更通用,适用于任意字符串键。
虽然序列式表默认使用数字键 (1, 2, 3...),但我们也可以显式地使用任意数字作为键,创建非序列式的关联式表。
代码实践:
local numberKeyTable = { [100] = "value100", [2.5] = "value2.5", [-5] = "value-5", [0] = "value0" } print(numberKeyTable) print("键 100 的值:", numberKeyTable[100]) print("键 2.5 的值:", numberKeyTable[2.5]) print("键 -5 的值:", numberKeyTable[-5]) print("键 0 的值:", numberKeyTable[0]) -- 检查大小 (非序列式表,# 运算符行为不确定) print("非序列式表的大小:", #numberKeyTable) -- 结果可能为 0 或其他不确定的值
内容详解:
local numberKeyTable = { [100] = "value100", ... }: 使用方括号 [] 和数字字面量 (整数或浮点数) 来显式指定数字键。
print("键 100 的值:", numberKeyTable[100]) 等: 使用方括号 [] 和对应的数字键来访问元素。
print("非序列式表的大小:", #numberKeyTable): 对于非序列式表,# 运算符的行为是不确定的。它可能返回 0,或者返回一个看似随机的值,不应该依赖 # 运算符来获取非序列式表的大小。
总结:
可以使用任意数字作为键来创建关联式表。这种方式可以用于表示例如稀疏数组、索引映射等数据结构。 非序列式表不适用于 # 运算符获取长度。
Lua 表的键可以是除了 nil 之外的任何 Lua 值,包括布尔值、函数、表 (但表不能作为自身的键,会造成循环引用)。
代码实践:
local mixedKeyTable = { [true] = "boolean key", [false] = "another boolean key", [function() return "function key" end] = "function value", -- 函数作为键 (不常见,但合法) [numericTable] = "table key" -- 表作为键 (不推荐,可能导致问题) } print(mixedKeyTable) print("布尔值 true 键的值:", mixedKeyTable[true]) print("布尔值 false 键的值:", mixedKeyTable[false]) -- print("函数键的值:", mixedKeyTable[function() return "function key" end]) -- 无法直接用字面量函数访问,因为函数是引用类型 -- print("表键的值:", mixedKeyTable[numericTable]) -- 无法直接用字面量表访问,因为表是引用类型 -- 对于函数和表作为键的情况,需要使用相同的引用来访问 local funcKey = function() return "function key" end local tableKey = numericTable mixedKeyTable[funcKey] = "function value (accessed correctly)" mixedKeyTable[tableKey] = "table value (accessed correctly)" print("函数键的值 (正确访问):", mixedKeyTable[funcKey]) print("表键的值 (正确访问):", mixedKeyTable[tableKey])
内容详解:
local mixedKeyTable = { [true] = "boolean key", ... }: 展示了使用布尔值、函数和表作为键的示例。
print("布尔值 true 键的值:", mixedKeyTable[true]): 使用布尔值 true 和 false 作为键是合法的。
[function() return "function key" end] = "function value": 将一个匿名函数作为键。注意: 要访问这个键对应的值,你需要使用完全相同的函数引用,而不能使用另一个字面量相同的函数。 这是因为函数在 Lua 中是引用类型,只有引用地址相同的函数才会被认为是同一个键。
[numericTable] = "table key": 将一个表 numericTable 作为键。注意: 同样地,要访问这个键对应的值,你需要使用完全相同的表引用。 使用表作为键通常不推荐,因为它容易导致混淆和潜在的内存管理问题。
访问函数键和表键: 代码注释中解释了为什么不能直接使用字面量函数或表来访问键值。 需要将函数或表赋值给变量,然后使用变量作为键来访问。
总结:
Lua 表的键可以是各种类型的值,这体现了 Lua 的灵活性。 虽然可以使用函数和表作为键,但通常情况下,字符串和数字是更常用和更实用的键类型。 使用函数和表作为键需要注意引用类型的问题。
表在 Lua 中是动态的,我们可以在运行时动态地创建和修改表。 一个常见的场景是在函数中创建表并返回。
代码实践:
-- 函数:创建一个表示 Person 对象的表 function createPerson(name, age, city) local person = { name = name, age = age, city = city, greet = function(self) -- 表的方法 (函数) print("你好,我叫 " .. self.name .. ", 今年 " .. self.age .. " 岁,来自 " .. self.city .. "。") end } return person end -- 创建 Person 对象 local person1 = createPerson("Alice", 25, "London") local person2 = createPerson("Bob", 32, "Paris") -- 打印 Person 对象 print(person1) print(person2) -- 调用 Person 对象的方法 person1:greet() -- 使用冒号 : 语法调用方法 person2.greet(person2) -- 使用点号 . 语法调用方法,需要显式传递 self 参数
内容详解:
function createPerson(name, age, city) ... end: 定义了一个名为 createPerson 的函数,它接受姓名、年龄和城市作为参数。
local person = { ... }: 在函数内部,创建一个局部变量 person 并初始化为一个表。 这个表用于表示一个 "Person" 对象,包含 name, age, city 属性和一个 greet 方法 (函数)。
greet = function(self) ... end: 在表中定义了一个名为 greet 的键,其值为一个匿名函数。 这个函数作为 person 表的方法。 self 参数用于在方法内部访问表自身。
return person: 函数返回创建的 person 表。
local person1 = createPerson("Alice", 25, "London"): 调用 createPerson 函数创建两个 person 对象,并赋值给 person1 和 person2 变量。
person1:greet(): 使用冒号 : 语法调用 person1 表的 greet 方法。 冒号语法会自动将 person1 表作为第一个参数 (即 self 参数) 传递给 greet 函数。
person2.greet(person2): 使用点号 . 语法调用 person2 表的 greet 方法。 使用点号语法时,需要显式地将 person2 表作为第一个参数传递给 greet 函数。
总结:
函数是动态创建表的常用方式。 函数可以根据输入参数和逻辑,灵活地创建不同结构的表,并返回给调用者。 这种方式常用于创建对象、配置数据结构等。
表可以嵌套在另一个表中,形成多层结构的数据表示。 这对于表示复杂的数据关系非常有用。
代码实践:
local nestedTable = { person = { -- 键 "person" 对应一个子表 name = "Charlie", age = 28, address = { -- 键 "address" 对应一个更深层的子表 street = "Main Street", number = 123, city = "Springfield" } }, items = {"book", "pen", "notebook"} -- 键 "items" 对应一个序列式表 } print(nestedTable) -- 访问嵌套表中的元素 print("姓名:", nestedTable.person.name) print("城市:", nestedTable.person.address.city) print("第一个物品:", nestedTable.items[1])
内容详解:
local nestedTable = { ... }: 创建了一个名为 nestedTable 的表。
person = { ... }: 在 nestedTable 中,键 "person" 对应的值又是一个表,这个表表示一个人的信息。 这就形成了嵌套表。
address = { ... }: 在 "person" 子表中,键 "address" 对应的值又是另一个表,表示地址信息。 可以无限嵌套下去,形成更复杂的数据结构。
items = {"book", "pen", "notebook"}: 在 nestedTable 中,键 "items" 对应的值是一个序列式表,表示物品列表。
print("姓名:", nestedTable.person.name): 使用链式点号 . 运算符来访问嵌套表中的元素。 nestedTable.person 访问 "person" 子表, nestedTable.person.name 访问 "person" 子表中的 "name" 键的值。
print("城市:", nestedTable.person.address.city): 更深层次的嵌套访问。
print("第一个物品:", nestedTable.items[1]): 访问嵌套的序列式表中的元素。
总结:
嵌套表是 Lua 表的强大特性之一,允许我们构建复杂、层次化的数据结构。 可以通过链式点号 . 运算符或方括号 [] 运算符来访问嵌套表中的元素。 嵌套表常用于表示树形结构、配置文件、复杂对象模型等。
Lua 表最显著的特点是其动态性和灵活性。 这意味着:
动态添加和删除键值对: 在表创建后,可以随时添加新的键值对,或者删除已有的键值对。
键和值的类型不固定: 同一个表中的键和值可以是不同的类型。
表的大小可以动态扩展: 表的大小没有固定限制,可以根据需要动态增长。
代码实践 (动态添加和删除):
local dynamicTable = {} -- 创建一个空表 -- 动态添加键值对 dynamicTable.name = "David" dynamicTable["age"] = 35 dynamicTable[1] = "first element" print(dynamicTable) -- 删除键值对 (将键对应的值设置为 nil) dynamicTable.age = nil dynamicTable[1] = nil print(dynamicTable) print("表的大小:", #dynamicTable) -- 大小可能会受删除操作影响
内容详解:
local dynamicTable = {}: 从一个空表开始。
dynamicTable.name = "David": 动态添加键 "name" 和值 "David"。 如果键 "name" 之前不存在,则会创建新的键值对;如果键 "name" 已经存在,则会更新其值。
dynamicTable["age"] = 35 和 dynamicTable[1] = "first element": 继续动态添加不同类型的键值对。
dynamicTable.age = nil 和 dynamicTable[1] = nil: 删除键值对。 在 Lua 中,将一个键对应的值设置为 nil 即可删除该键值对。 注意: 这并不会真正释放表占用的内存,Lua 的垃圾回收机制会在适当的时候回收不再使用的内存。
print("表的大小:", #dynamicTable): 删除元素可能会影响序列式表的 # 运算符的结果,具体行为取决于被删除的元素的位置和表的连续性。
总结:
表的动态性是 Lua 语言灵活性的重要体现。 动态添加和删除键值对使得表可以适应各种动态变化的数据需求。 键和值类型不固定的特性,以及动态扩展的大小,进一步增强了表的通用性。
Lua 表是其核心数据结构,理解表的创建方式至关重要。 本文详细介绍了 Lua 表的各种创建方法,包括:
空表的创建: 使用 {} 字面量。
序列式表的创建: 使用 {value1, value2, ...} 字面量,键为连续正整数。
关联式表的创建:
使用字符串键: key = value (合法标识符键) 和 ["key"] = value (任意字符串键)。
使用数字键: [number] = value (任意数字键)。
使用其他类型键: [value] = value (布尔值、函数、表等,但需注意引用类型)。
动态创建表 (函数中创建): 在函数中创建表并返回,用于封装数据结构和对象。
嵌套表的创建: 表中嵌套表,形成多层结构。
表的动态性: 动态添加、删除键值对,键值类型不固定,大小动态扩展。
通过掌握这些表创建技巧,你将能够灵活地运用 Lua 表来组织和管理各种数据,为更深入的 Lua 编程打下坚实的基础。 在实际应用中,选择合适的表创建方式取决于具体的场景和数据结构需求。 理解不同创建方式的特点和适用性,才能更好地发挥 Lua 表的强大功能。