Lua


6.3 表的常用操作


文档摘要

6.3 表的常用操作 Lua 表 (Table) 常用操作详解:代码实践与深度解析 虽然标题提到 "3 表的常用操作",但实际上,表的操作通常不局限于三个表。更准确地说,我们关注的是针对表进行各种常见操作,这些操作可能涉及单个表、两个表或多个表。为了更清晰地组织内容,我们将操作大致分为以下几类: 表的创建与初始化 表的访问与修改 表的迭代与遍历 表的复制与克隆 表的合并与连接 表的过滤与筛选 表的排序 表的查找与搜索 表的移除与清空 表的元表与元方法 (进阶) 1. 表的创建与初始化 在 Lua 中,创建表非常简单,只需使用花括号 即可。表可以包含各种类型的数据,包括数字、字符串、布尔值、函数,甚至其他的表。 1.

6.3 表的常用操作

Lua 表 (Table) 常用操作详解:代码实践与深度解析

虽然标题提到 "3 表的常用操作",但实际上,表的操作通常不局限于三个表。更准确地说,我们关注的是针对表进行各种常见操作,这些操作可能涉及单个表、两个表或多个表。为了更清晰地组织内容,我们将操作大致分为以下几类:

  1. 表的创建与初始化

  2. 表的访问与修改

  3. 表的迭代与遍历

  4. 表的复制与克隆

  5. 表的合并与连接

  6. 表的过滤与筛选

  7. 表的排序

  8. 表的查找与搜索

  9. 表的移除与清空

  10. 表的元表与元方法 (进阶)

1. 表的创建与初始化

在 Lua 中,创建表非常简单,只需使用花括号 {} 即可。表可以包含各种类型的数据,包括数字、字符串、布尔值、函数,甚至其他的表。

1.1 创建空表:

-- 创建一个空表 local emptyTable = {} print(emptyTable) -- 输出:table: 0xXXXXXXXX (地址可能不同) print(type(emptyTable)) -- 输出:table

详解:

  • local emptyTable = {}: 这行代码声明了一个局部变量 emptyTable 并将其初始化为一个空表。

  • {}: 花括号是 Lua 中创建表字面量的语法。当花括号内为空时,就创建了一个没有任何键值对的空表。

  • print(emptyTable)print(type(emptyTable)): 用于验证 emptyTable 确实是一个表类型。

1.2 创建并初始化数字索引表:

-- 创建并初始化数字索引表 (数组) local arrayTable = {10, 20, 30, "hello", true} print(arrayTable[1]) -- 输出:10 (Lua 表索引从 1 开始) print(arrayTable[4]) -- 输出:hello print(arrayTable[#arrayTable]) -- 输出:true (# 运算符获取数组长度) print(arrayTable) -- 输出:table: 0xXXXXXXXX (地址可能不同)

详解:

  • local arrayTable = {10, 20, 30, "hello", true}: 使用花括号 {} 创建表,并在其中直接列出元素。这些元素会按照列出的顺序被自动赋予数字索引,从 1 开始。

  • arrayTable[1]arrayTable[4]: 使用方括号 [] 和数字索引来访问表中的元素。Lua 表的索引从 1 开始,而不是像某些语言从 0 开始。

  • #arrayTable# 运算符用于获取数字索引表的长度,即表中最大的数字索引值。对于数组类型的表,它通常返回元素的个数。

  • arrayTable[#arrayTable]: 访问数组的最后一个元素。

1.3 创建并初始化关联数组表 (哈希表):

-- 创建并初始化关联数组表 (哈希表) local hashTable = { name = "Alice", age = 30, city = "New York", isStudent = false } print(hashTable.name) -- 输出:Alice (点号访问) print(hashTable["age"]) -- 输出:30 (方括号字符串索引访问) print(hashTable.city) -- 输出:New York print(hashTable.isStudent) -- 输出:false print(hashTable) -- 输出:table: 0xXXXXXXXX (地址可能不同)

详解:

  • local hashTable = { ... }: 使用花括号创建表,并在其中指定键值对。

  • name = "Alice", age = 30, ...: 键值对使用 key = value 的形式定义,键 (key) 可以是字符串(如 "name"),也可以是其他类型(但通常字符串和数字更常见)。值 (value) 可以是任何 Lua 数据类型。

  • hashTable.name: 使用点号 . 语法来访问字符串键的值。这是一种更简洁的访问方式,但仅适用于键是有效的 Lua 标识符(字母、数字、下划线开头,不能以数字开头)的情况。

  • hashTable["age"]: 使用方括号 [] 和字符串索引来访问键为 "age" 的值。这种方式更通用,可以访问任何字符串键,即使键不是有效的标识符。

1.4 混合使用数字索引和关联索引:

-- 创建混合索引的表 local mixedTable = { [1] = "first", name = "Bob", [2] = "second", age = 25, [3] = "third" } print(mixedTable[1]) -- 输出:first print(mixedTable.name) -- 输出:Bob print(mixedTable[2]) -- 输出:second print(mixedTable.age) -- 输出:25 print(mixedTable[3]) -- 输出:third print(mixedTable) -- 输出:table: 0xXXXXXXXX (地址可能不同)

详解:

  • local mixedTable = { ... }: 表可以同时包含数字索引和关联索引的键值对。

  • [1] = "first", name = "Bob", ...: 数字索引使用 [index] = value 的形式,关联索引使用 key = value 的形式。

  • 混合索引的表在某些场景下很有用,例如,你可以使用数字索引存储有序的数据,同时使用字符串键存储一些描述性的属性。

2. 表的访问与修改

访问和修改表中的元素是表操作的基础。

2.1 访问表元素:

  • 数字索引访问: tableName[index]

  • 关联索引访问: tableName["key"]tableName.key (仅限字符串键且为有效标识符)

我们已经在上面的例子中看到了访问表元素的用法,这里不再赘述。

2.2 修改表元素:

修改表元素与访问元素类似,只需在访问的基础上进行赋值操作即可。

local myTable = {name = "Charlie", age = 20} -- 修改关联索引的值 myTable.age = 21 myTable["name"] = "Charles" print(myTable.name) -- 输出:Charles print(myTable.age) -- 输出:21 local myArray = {1, 2, 3} -- 修改数字索引的值 myArray[1] = 100 myArray[3] = 300 print(myArray[1]) -- 输出:100 print(myArray[3]) -- 输出:300

详解:

  • myTable.age = 21myTable["name"] = "Charles": 通过点号和方括号索引访问并赋值,直接修改了表中已存在的键 agename 的值。

  • myArray[1] = 100myArray[3] = 300: 同样的方式修改了数字索引表 myArray 中索引为 1 和 3 的值。

2.3 添加新元素:

如果访问的键在表中不存在,并且进行赋值操作,则会向表中添加新的键值对。

local myTable = {name = "David"} -- 添加新的关联索引 myTable.city = "London" myTable["country"] = "UK" print(myTable.city) -- 输出:London print(myTable.country) -- 输出:UK local myArray = {1, 2} -- 添加新的数字索引 (不推荐直接使用数字索引添加,通常用 table.insert) myArray[3] = 3 myArray[4] = 4 print(myArray[3]) -- 输出:3 print(myArray[4]) -- 输出:4

详解:

  • myTable.city = "London"myTable["country"] = "UK": 当键 citycountrymyTable 中不存在时,赋值操作会向表中添加新的键值对。

  • myArray[3] = 3myArray[4] = 4: 对于数字索引表,直接使用数字索引赋值也可以添加新元素。但对于数组类型的表,更推荐使用 table.insert 函数来添加元素,以确保数字索引的连续性。

3. 表的迭代与遍历

遍历表中的所有键值对是常见的操作。Lua 提供了多种迭代表的方式。

3.1 for 循环与数字索引 (ipairs):

ipairs 迭代器专门用于遍历数字索引表,它会按照数字索引的顺序迭代,直到遇到第一个 nil 值。

local myArray = {"apple", "banana", "cherry", nil, "date"} -- 注意中间有个 nil print("使用 ipairs 遍历:") for index, value in ipairs(myArray) do print(index, value) end -- 输出: -- 使用 ipairs 遍历: -- 1 apple -- 2 banana -- 3 cherry

详解:

  • ipairs(myArray): 返回一个迭代器函数,用于遍历 myArray

  • for index, value in ipairs(myArray) do ... endfor...in 循环结构,每次迭代 ipairs 返回两个值:当前元素的索引 index 和值 value

  • ipairs 的特点是遇到 nil 值会停止迭代。在上面的例子中,虽然 myArray 中索引为 4 的位置是 nil,但 ipairs 只迭代到了索引 3。因此,ipairs 更适合遍历连续的数字索引数组

3.2 for 循环与所有键值对 (pairs):

pairs 迭代器用于遍历表中的所有键值对,包括数字索引和关联索引。但 pairs 迭代的顺序是不确定的,不保证按照任何特定顺序(例如,数字索引的顺序,或插入顺序)。

local myTable = { [1] = "one", name = "Eve", [2] = "two", age = 28, city = "Paris" } print("\n使用 pairs 遍历:") for key, value in pairs(myTable) do print(key, value) end -- 输出 (顺序可能不同): -- 使用 pairs 遍历: -- 1 one -- 2 two -- name Eve -- age 28 -- city Paris

详解:

  • pairs(myTable): 返回一个迭代器函数,用于遍历 myTable 的所有键值对。

  • for key, value in pairs(myTable) do ... endfor...in 循环结构,每次迭代 pairs 返回两个值:当前元素的键 key 和值 value

  • pairs 遍历的顺序是不确定的,因此如果你需要按照特定顺序遍历,例如数字索引的顺序,应该使用 ipairs 或手动排序键后再遍历。

3.3 while 循环与手动索引迭代 (数字索引表):

对于数字索引表,可以使用 while 循环和手动递增索引的方式进行遍历。

local myArray = {"apple", "banana", "cherry"} local index = 1 print("\n使用 while 循环遍历数字索引表:") while myArray[index] ~= nil do print(index, myArray[index]) index = index + 1 end -- 输出: -- 使用 while 循环遍历数字索引表: -- 1 apple -- 2 banana -- 3 cherry

详解:

  • local index = 1: 初始化索引为 1。

  • while myArray[index] ~= nil do ... endwhile 循环条件判断当前索引位置的值是否为 nil。如果不是 nil,则继续循环;如果是 nil,则表示已经遍历到数组的末尾(或者遇到了 nil 值),循环结束。

  • index = index + 1: 在循环体中递增索引,移动到下一个元素。

注意: 使用 while 循环手动索引遍历时,需要注意处理 nil 值的情况,避免无限循环或访问超出表范围的索引。通常情况下,ipairs 是更安全和方便的数字索引表遍历方式。

4. 表的复制与克隆

在 Lua 中,表是引用类型。这意味着当你将一个表赋值给另一个变量时,实际上只是复制了表的引用,而不是表本身的内容。如果需要创建一个表的副本,需要进行复制或克隆操作。

4.1 浅拷贝:

浅拷贝只复制表的第一层结构。如果表的值是基本类型(数字、字符串、布尔值),则会复制这些值。如果表的值是表或其他引用类型,则浅拷贝只会复制这些引用,而不会复制引用的对象本身。

-- 浅拷贝函数 local function shallowCopy(originalTable) local newTable = {} for key, value in pairs(originalTable) do newTable[key] = value end return newTable end local table1 = {a = 1, b = "hello", c = {10, 20}} local table2 = shallowCopy(table1) print("原始表 table1:") for k, v in pairs(table1) do print(k, v, type(v)) end print("\n浅拷贝表 table2:") for k, v in pairs(table2) do print(k, v, type(v)) end -- 修改 table2 的第一层值 table2.a = 100 table2.b = "world" print("\n修改 table2 第一层后:") print("table1.a:", table1.a) -- 输出:table1.a: 1 print("table2.a:", table2.a) -- 输出:table2.a: 100 -- 修改 table2 嵌套表的值 table2.c[1] = 1000 print("\n修改 table2 嵌套表后:") print("table1.c[1]:", table1.c[1]) -- 输出:table1.c[1]: 1000 (table1 也被修改了!) print("table2.c[1]:", table2.c[1]) -- 输出:table2.c[1]: 1000

详解:

  • shallowCopy(originalTable) 函数实现了浅拷贝逻辑。它创建一个新表 newTable,然后遍历 originalTable 的所有键值对,将键和值直接复制到 newTable 中。

  • 修改 table2.atable2.b 只会影响 table2,不会影响 table1,因为基本类型的值被复制了。

  • 修改 table2.c[1] 会同时影响 table1.c[1],因为 table1.ctable2.c 仍然指向同一个嵌套表对象,浅拷贝只复制了嵌套表的引用。

4.2 深拷贝:

深拷贝会递归地复制表的所有层级结构。如果表的值是表或其他引用类型,深拷贝也会创建这些引用类型的新副本,而不是仅仅复制引用。深拷贝可以确保完全独立的副本。

-- 深拷贝函数 local function deepCopy(originalTable) local newTable = {} for key, value in pairs(originalTable) do if type(value) == "table" then newTable[key] = deepCopy(value) -- 递归调用深拷贝 else newTable[key] = value end end return newTable end local table1 = {a = 1, b = "hello", c = {10, 20}} local table2 = deepCopy(table1) print("原始表 table1:") for k, v in pairs(table1) do print(k, v, type(v)) end print("\n深拷贝表 table2:") for k, v in pairs(table2) do print(k, v, type(v)) end -- 修改 table2 嵌套表的值 table2.c[1] = 1000 print("\n修改 table2 嵌套表后:") print("table1.c[1]:", table1.c[1]) -- 输出:table1.c[1]: 10 (table1 没有被修改!) print("table2.c[1]:", table2.c[1]) -- 输出:table2.c[1]: 1000

详解:

  • deepCopy(originalTable) 函数实现了深拷贝逻辑。它在浅拷贝的基础上增加了类型判断:如果值 value 的类型是 "table",则递归调用 deepCopy(value) 来复制嵌套表,否则直接复制值。

  • 修改 table2.c[1] 不会影响 table1.c[1],因为深拷贝创建了嵌套表 c 的完全独立的副本。

选择浅拷贝还是深拷贝:

  • 浅拷贝: 速度更快,但如果表包含嵌套表或其他引用类型,修改副本的嵌套结构可能会影响原始表。适用于表结构简单,或者不需要完全独立的副本的场景。

  • 深拷贝: 速度较慢,但创建的副本与原始表完全独立,修改副本不会影响原始表。适用于需要完全隔离修改的场景,例如,处理复杂的数据结构或需要保护原始数据的场景。

5. 表的合并与连接

将多个表合并成一个表,或者将多个表的元素连接起来,是常见的表操作。

5.1 表的合并 (非破坏性合并):

将多个表的键值对合并到一个新的表中,原始表保持不变。如果多个表有相同的键,合并后的表可能会覆盖或保留其中一个表的值(取决于合并策略)。

-- 非破坏性合并表函数 (后一个表覆盖前一个表的相同键) local function mergeTables(...) local mergedTable = {} for i = 1, #arg do -- arg 是可变参数函数中存储参数的表 local currentTable = arg[i] for key, value in pairs(currentTable) do mergedTable[key] = value -- 后面的表覆盖前面的表的相同键 end end return mergedTable end local table1 = {a = 1, b = 2, c = 3} local table2 = {b = 20, d = 4, e = 5} local table3 = {c = 30, f = 6} local mergedTable = mergeTables(table1, table2, table3) print("合并后的表 mergedTable:") for k, v in pairs(mergedTable) do print(k, v) end -- 输出: -- 合并后的表 mergedTable: -- a 1 -- b 20 -- table2 覆盖了 table1 的键 'b' -- c 30 -- table3 覆盖了 table1 和 table2 的键 'c' -- d 4 -- e 5 -- f 6

详解:

  • mergeTables(...) 函数使用可变参数 ... 接收多个表作为输入。arg 是 Lua 函数内部自动创建的一个表,用于存储传递给函数的参数。

  • 循环遍历每个输入表 currentTable,将其键值对添加到 mergedTable 中。如果多个表有相同的键,后一个表的值会覆盖前一个表的值。

  • 这种合并方式是非破坏性的,原始表 table1table2table3 不会被修改。

5.2 表的连接 (数字索引表连接成字符串):

将数字索引表的元素连接成一个字符串,可以使用 table.concat 函数。

local myArray = {"Hello", " ", "Lua", "!", " ", "Tables"} -- 连接成字符串,默认分隔符为空字符串 "" local concatenatedString1 = table.concat(myArray) print("连接后的字符串1:", concatenatedString1) -- 输出:连接后的字符串1: Hello Lua! Tables -- 连接成字符串,指定分隔符为 "-" local concatenatedString2 = table.concat(myArray, "-") print("连接后的字符串2:", concatenatedString2) -- 输出:连接后的字符串2: Hello- -Lua-!- -Tables -- 连接指定范围的元素,从索引 2 到 4,分隔符为 "," local concatenatedString3 = table.concat(myArray, ",", 2, 4) print("连接指定范围的字符串3:", concatenatedString3) -- 输出:连接指定范围的字符串3: ,Lua,!

详解:

  • table.concat(myArray, sep, i, j)table.concat 函数用于连接数字索引表的元素。

    • myArray: 要连接的数字索引表。

    • sep (可选): 分隔符字符串,默认为空字符串 ""

    • i (可选): 连接的起始索引,默认为 1。

    • j (可选): 连接的结束索引,默认为 #myArray (数组长度)。

  • table.concat 返回连接后的字符串。

6. 表的过滤与筛选

根据特定条件筛选表中的元素,生成一个新的表。

6.1 基于值的过滤:

根据值的条件筛选表中的元素。

local myTable = { {name = "Alice", age = 30}, {name = "Bob", age = 25}, {name = "Charlie", age = 35}, {name = "David", age = 28} } -- 筛选年龄大于 30 的人 local function filterByAge(tableList, minAge) local filteredTable = {} for _, person in ipairs(tableList) do if person.age > minAge then table.insert(filteredTable, person) -- 使用 table.insert 添加到数字索引表 end end return filteredTable end local filteredPeople = filterByAge(myTable, 30) print("筛选后的人员:") for _, person in ipairs(filteredPeople) do print(person.name, person.age) end -- 输出: -- 筛选后的人员: -- Charlie 35

详解:

  • filterByAge(tableList, minAge) 函数接收一个表列表 tableList 和最小年龄 minAge 作为输入。

  • 遍历 tableList 中的每个元素 person

  • if person.age > minAge then ... end: 判断 person 的年龄是否大于 minAge

  • table.insert(filteredTable, person): 如果满足条件,使用 table.insertperson 添加到新的数字索引表 filteredTable 中。table.insert 默认在数组末尾插入元素,并自动维护数字索引的连续性。

6.2 基于键的过滤 (关联数组):

根据键的条件筛选关联数组表中的键值对。

local myHashTable = { name = "Alice", age = 30, city = "New York", country = "USA", occupation = "Engineer" } -- 筛选键包含 "c" 的键值对 local function filterByKey(hashTable, keyPattern) local filteredTable = {} for key, value in pairs(hashTable) do if string.find(key, keyPattern) then -- 使用 string.find 查找子字符串 filteredTable[key] = value end end return filteredTable end local filteredHash = filterByKey(myHashTable, "c") print("筛选后的哈希表:") for key, value in pairs(filteredHash) do print(key, value) end -- 输出: -- 筛选后的哈希表: -- city New York -- country USA -- occupation Engineer

详解:

  • filterByKey(hashTable, keyPattern) 函数接收一个哈希表 hashTable 和键模式 keyPattern 作为输入。

  • 遍历 hashTable 的每个键值对。

  • if string.find(key, keyPattern) then ... end: 使用 string.find 函数检查键 key 是否包含子字符串 `


发布者: 作者: 转发
评论区 (0)
U