Lua中table、模块、元表和元方法

文章目录

  • [一、 table](#一、 table)
    • [1. 数组](#1. 数组)
    • [2. map](#2. map)
    • [3. 混合结构](#3. 混合结构)
    • [4. table操作函数](#4. table操作函数)
    • [5. 迭代器](#5. 迭代器)
  • [二、 模块](#二、 模块)
    • [1. 定义一个模块](#1. 定义一个模块)
    • [2. 使用一个模块](#2. 使用一个模块)
    • [3. 再看模块](#3. 再看模块)
  • [三、 元表和元方法](#三、 元表和元方法)
    • [1. 重要函数](#1. 重要函数)
    • [2. _ _index元方法](#2. _ _index元方法)
    • [3. _ _newindex元方法](#3. _ _newindex元方法)
    • [4. 运算符元方法](#4. 运算符元方法)
    • [5. _ _tostring元方法](#5. _ _tostring元方法)
    • [6. _ _cal元方法](#6. _ _cal元方法)
    • [7. 元表单独定义](#7. 元表单独定义)

一、 table

1. 数组

使用 table 可以定义一维、二维、多维数组。
不过,需要注意:

  • Lua 中的数组索引是从 1开始的;
  • 无需声明数组长度,可以随时增加元素;
  • 同一数组中的元素可以是任意类型。
c 复制代码
--声明一个空的数组
Arr = {}
--初始化一个3*2的数组
for i=1,3 do
    Arr[i]={}
    for j=1,2 do
        Arr[i][j]=i*j
    end
end

for i=1,3,1 do
    for j=1,2,1 do
        print(Arr[i][j])
    end
end

Arr2={"hello",1,false,{"world",10,20}}
for i=1,4 do
    print(Arr2[i])
end

2. map

使用 table 也可以定义出类似 map 的 key-value 数据结构。其可以定义 table 时直接指定key-value,也可单独指定 key-value。而访问时,一般都是通过 table 的 key 直接访问,也可以数组索引方式来访问,此时的 key 即为索引。

c 复制代码
Map = {name="张三",age=18,id=1}
--利用[]来访问元素并赋值,如果存在则更新,不存在则插入新的key-value
Map["id"]=2 --修改已有元素
print(Map["id"])

Map["gender"]="male" --原先不存在gender则添加新元素
print(Map["gender"])

--利用.来访问元素并赋值,如果存在则更新,不存在则插入新的key
print(Map.age)
Map.phone="123456"
print(Map.phone)
c 复制代码
a = "xxx"
b = 3
c = 5

-- 定义一个map,其key为表达式
arr = {
	["emp_"..a] = true,
	[b + c] = "Hello",
	["hi"] = 123
}

print(arr.emp_xxx)
print(arr[8])
print(arr.hi)

3. 混合结构

Lua 允许将数组与 key-value 混合在同一个 table 中进行定义。key-value 不会占用数组的数字索引值。

c 复制代码
Emp = {"北京", name="张三", age=23, "上海", depart="销售部", "广州", "深圳"}
--下标    1                           2                       3       4
--key-value不会占用数组的下标

print(Emp[1])
print(Emp[2])
print(Emp[3])
print(Emp[4])

4. table操作函数

A、table.concat()

  • 函数: table.concat (table , sep , start , end):
  • 解析: 该函数用于将指定的 table 数组元素进行字符串连接。连接从 start 索引位置到 end索引位置的所有数组元素, 元素间使用指定的分隔符 sep 隔开。如果 table 是一个混合结构,那么这个连接与 key-value 无关,仅是连接数组元素。
c 复制代码
Emp = {"北京", name="张三", age=23, "上海", depart="销售部", "广州", "深圳"}


print(table.concat(Emp, ",")) --把数组从头到尾连接,每个数组元素之间用','连接
print(table.concat(Emp,"-",2,3))--连接下标为2-3的数组元素,每个数组元素之间用'-'连接

B、 table.unpack()

  • 函数 table.unpack (table , i , j)
  • 解析 拆包。该函数返回指定 table 的数组中的从第 i 个元素到第 j 个元素值。i 与 j 是可选的,默认 i 为 1,j 为数组的最后一个元素。Lua5.1 不支持该函数。
c 复制代码
Cities = {"bj北京", "sh上海", "gz广州", "sz深圳", "tj天津", "gz广州"}
print(table.unpack(Cities,2,4)) --返回下标为2,3,4的元素

C、 table.pack()

  • 函数 table.pack (...)
  • 解析 打包。该函数的参数是一个可变参,其可将指定的参数打包为一个 table 返回。这个返回的 table 中具有一个属性 n,用于表示该 table 包含的元素个数。Lua5.1 不支持该函数。
c 复制代码
local t = table.pack(1, 2, 3, "hello", true)

-- 输出表内容
for i = 1, t.n do
    print(i, t[i])
end
c 复制代码
local function printArgs(...)
    local args = table.pack(...)
    print("Received", args.n, "arguments:")
    for i = 1, args.n do
        print("  ", i, args[i])
    end
end

printArgs(10, "twenty", false, {1, 2, 3})

D、table.maxn()

  • 函数 table.maxn(table)
  • 解析 该函数返回指定 table 的数组中的最大索引值,即数组包含元素的个数。

在5.4版本中被废弃了

E、 table.insert()

  • 函数 table.insert (table, pos, value):
  • 解析 该函数用于在指定 table 的数组部分指定位置 pos 插入值为 value 的一个元素。其后的元素会被后移。pos 参数可选,默认为数组部分末尾。
c 复制代码
Cities = {"北京", "上海", "广州", "深圳", "天津", "广州"}
table.insert(Cities,2,"太原") --在下标为2的位置插入
table.insert(Cities,"寿阳") --不指明下标,则在最后插入
print(table.concat(Cities,","))

F、 table.remove ()

  • 函数 table.remove (table , pos)
  • 解析 该函数用于删除并返回指定 table 中数组部分位于 pos 位置的元素。其后的元素会被前移。pos 参数可选,默认删除数组中的最后一个元素。
c 复制代码
Cities = {"北京", "上海", "广州", "深圳", "天津", "广州"}
table.remove(Cities, 2) --删除下标为2的元素
table.remove(Cities) --不指明下标,默认删除最后一个元素
print(table.concat(Cities,","))

G、table.sort()

  • 函数 table.sort(table [,fun(a,b)])
  • 解析 该函数用于对指定的 table 的数组元素进行升序排序,也可按照指定函数 fun(a,b)中指定的规则进行排序。fun(a,b)是一个用于比较 a 与 b 的函数,a 与 b 分别代表数组中的两个相邻元素。
  • 注意
    • 如果 arr 中的元素既有字符串又有数值型,那么对其进行排序会报错。
    • 如果数组中多个元素相同,则其相同的多个元素的排序结果不确定,即这些元素的索引谁排前谁排后,不确定。
    • 如果数组元素中包含 nil,则排序会报错。
c 复制代码
Cities = {"bj北京", "sh上海", "gz广州", "sz深圳", "tj天津", "gz广州"}
 table.sort(Cities, function(a,b)
 						return a>b
 				   end
           )
--自定义排序规则 从大到小
print(table.concat(Cities, ","))

table.sort(Cities)
-- 默认从小到大
print(table.concat(Cities, ","))

5. 迭代器

Lua 提供了两个迭代器 pairs(table)与 ipairs(table)。这两个迭代器通常会应用于泛型 for循环中,用于遍历指定的 table。这两个迭代器的不同是:

  • ipairs(table):仅会迭代指定 table 中的数组元素。
  • pairs(table):会迭代整个 table 元素,无论是数组元素,还是 key-value。并且是先遍历完数组,再遍历key-value

和C++的范围for和go里的for range类似

c 复制代码
Emp = {"北京", name="张三", age=23, "上海", depart="销售部", "广州", "深圳"}


-- 遍历emp中的所有数组元素
for i, v in ipairs(Emp) do
	print(i, v)
end

-- 遍历emp中的所有元素
for k, v in pairs(Emp) do
	print(k, v)
end

二、 模块

模块是Lua中特有的一种数据结构。从 Lua 5.1 开始,Lua 加入了标准的模块管理机制,可以把一些公用的代码放在一个文件里,以 API 接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度。

模块文件主要由 table 组成。在 table 中添加相应的变量、函数,最后文件返回该 table即可。如果其它文件中需要使用该模块,只需通过 require 将该模块导入即可。

1. 定义一个模块

模块是一个 lua 文件,其中会包含一个 table。一般情况下该文件名与该 table 名称相同,但其并不是必须的。

此处我在同级目录下建立了一个rectangle.lua文件

c 复制代码
-- 声明一个模块
rectangle = {}

-- 为模块添加一个变量
rectangle.pi = 3.14

-- 为模块添加函数(求周长)
function rectangle.perimeter(a,b)
	return (a+b) * 2
end

-- 以匿名函数方式为模块添加一个函数(求面积)
rectangle.area = function(a, b)
	return a*b;
end

-- ========== 定义与模块无关的内容 =============

-- 定义一个全局变量
goldenRatio = 0.618

-- 定义一个局部函数(求圆的面积)
local function circularArea(r)
	return rectangle.pi * r * r
end

-- 定义一个全局函数(求矩形中最大圆的面积)
function maxCircularArea(a, b)
	local r = math.min(a, b)
	return circularArea(r)
end


return rectangle

2. 使用一个模块

这里要用到一个函数 require("文件路径"),其中文件名是不能写.lua 扩展名的。该函数可以将指定的 lua 文件静态导入(合并为一个文件)。不过需要注意的是,该函数的使用可以省略小括号,写为 require "文件路径"。require()函数是有返回值的,返回的就是模块文件最后 return 的 table。可以使用一个变量接收该 table 值作为模块的别名,就可以使用别名来访问模块了。

c 复制代码
-- 导入一个模块
rect = require "rectangle"

-- 访问模块的属性,调用模块的函数
print(rectangle.pi)
print(rectangle.perimeter(3, 5))
print(rectangle.area(3, 5))

print(rect.pi)
print(rect.perimeter(3, 5))
print(rect.area(3, 5))

-- 访问模块中与模块无关的内容
print(goldenRatio)
print(maxCircularArea(3, 5))

3. 再看模块

模块文件中一般定义的变量与函数都是模块 table 相关内容,但也可以定义其它与 table无关的内容。这些全局变量与函数就是普通的全局变量与函数,与模块无关,但会随着模块的导入而同时导入。所以在使用时可以直接使用,而无需也不能添加模块名称。

三、 元表和元方法

元表,即 Lua 中普通 table 的元数据表,而元方法则是元表中定义的普通表的默认行为。Lua 中的每个普通 table 都可为其定义一个元表,用于扩展该普通 table 的行为功能。

例如,对于 table 与数值相加的行为,Lua 中是没有定义的,但用户可通过为其指定元表来扩展这种行为;

再如,用户访问不存在的 table 元素,Lua 默认返回的是 nil,但用户可能并不知道发生了什么。此时可以通过为该 table 指定元表来扩展该行为:给用户提示信息,并返回用户指定的值。

类似于C++里的运算符重载

1. 重要函数

元表中有两个重要函数:

  • setmetatable(table,metatable):将 metatable 指定为普通表 table 的元表。
  • getmetatable(table):获取指定普通表 table 的元表。

2. _ _index元方法

当用户在对 table 进行读取访问时,如果访问的数组索引或 key 不存在,那么系统就会自动调用元表的_ index 元方法。该重写的方法可以是一个函数,也可以是另一个表。如果重写的 _index 元方法是函数,且有返回值,则直接返回;如果没有返回值,则返回 nil。

c 复制代码
emp = {"北京", nil, name="张三", age=23, "上海", depart="销售部", "广州", "深圳"}

print(emp.x)

-- 声明一个元表
meta = {};

-- 将原始表与元表相关联
setmetatable(emp, meta)

-- 有返回值的情况
--~ meta.__index = function(tab, key)
--~ 	return "通过【"..key.."】访问的值不存在"
--~ end

-- 无返回值的情况
meta.__index = function(tab, key)
	print("通过【"..key.."】访问的值不存在")
end

print(emp.x)
print(emp[2])

3. _ _newindex元方法

当用户为 table 中一个不存在的索引或 key 赋值时,就会自动调用元表的_ newindex 元方法。该重写的方法可以是一个函数,也可以是另一个表。如果重写的 _newindex 元方法是函数,且有返回值,则直接返回;如果没有返回值,则返回 nil。

c 复制代码
emp = {"北京", name="张三", age=23, "上海", depart="销售部", "广州", "深圳"}
print(emp[5])
-- 声明一个元表
meta = {};

-- 将原始表与元表相关联
setmetatable(emp, meta)

-- 再定义一个普通表
other = {}

other[5] = "天津"
other[6] = "西安"

-- 指定元表为另一个普通表
meta.__index = other

-- 在原始表中若找不到,则会到元表指定的普通表中查找
print(emp[6])
c 复制代码
emp = {"北京", name="张三", age=23, "上海", depart="销售部", "广州", "深圳"}

print(emp[5])

-- 声明一个元表
meta = {};

-- 将原始表与元表相关联
setmetatable(emp, meta)

-- 无返回值的情况
function meta.__newindex(tab, key, value)
	print("新增的key为"..key..", value为"..value)
	-- 将新增的key-value写入到原始表
	rawset(tab, key, value)
end

emp.x = "天津"

print(emp.x)
c 复制代码
emp = {"北京", name="张三", age=23, "上海", depart="销售部", "广州", "深圳"}

-- 声明一个元表
meta = {};

-- 将原始表与元表相关联
setmetatable(emp, meta)

-- 再定义一个普通表
other = {}

-- 元表指定的另一个普通表的作用是,暂存新增加的数据
meta.__newindex = other

emp.x = "天津"

print(emp.x)
print(other.x)

4. 运算符元方法

如果要为一个表扩展加号(+)、减号(-)、等于(==)、小于(<)等运算功能,则可重写相应的元方法。

例如,如果要为一个 table 扩展加号(+)运算功能,则可重写该 table 元表的_ add 元方法,而具体的运算规则,则是定义在该重写的元方法中的。这样,当一个 table 在进行加法(+)运算时,就会自动调用其元表的 _add 元方法。

c 复制代码
emp = {"北京", name="张三", age=23, "上海", depart="销售部", "广州", 12, "深圳"}

-- 声明一个元表
meta = {
	__add = function(tab, num)
		-- 遍历tab中的所有元素
		for k, v in pairs(tab) do
			-- 若value为数值类型,则做算术加法
			if type(v) == "number" then
				tab[k] = v + num
			-- 若value为string,则做字符串拼接
			elseif type(v) == "string" then
				tab[k] = v..num
			end
		end

		-- 返回变化过的table
		return tab
	end,  -- 注意,这里必须要添加一个逗号

	__tostring = function(tab)
		str = ""
		-- 字符串拼接
		for k, v in pairs(empsum) do
			str = str.." "..k..":"..v
		end

		return str
	end
};

-- 将原始表与元表相关联
setmetatable(emp, meta)

empsum = emp + 5

print(emp)
print(empsum)

不止+法,其它也可以:

5. _ _tostring元方法

直接输出一个 table,其输出的内容为类型与 table 的存放地址。如果想让其输出 table中的内容,可重写_ _tostring 元方法。

c 复制代码
	__tostring = function(tab)
		str = ""
		-- 字符串拼接
		for k, v in pairs(empsum) do
			str = str.." "..k..":"..v
		end

		return str
	end

调用下面这两条语句之后,本来应该输出table的地址的,重写_ _tostring之后,就会按照自己写的函数进行输出了

c 复制代码
print(emp)
print(empsum)

6. _ _cal元方法

当将一个 table 以函数形式来使用时,系统会自动调用重写的_ _call 元方法。该用法主要是可以简化对 table 的相关操作,将对 table 的操作与函数直接相结合

类似于C++里的仿函数

c 复制代码
emp = {"北京", name="张三", age=23, "上海", depart="销售部", 59, "广州", "深圳"}

-- 将原始表与匿名元表相关联
setmetatable(emp, {
	__call = function(tab, num, str)
		-- 遍历table
		for k, v in pairs(tab) do
			if type(v) == "number" then
				tab[k] = v + num
			elseif type(v) == "string" then
				tab[k] = v..str
			end
		end

		return tab
	end
})

newemp = emp(5, "-hello")

for k, v in pairs(newemp) do
	print(k..":"..v)
end

7. 元表单独定义

为了便于管理与复用,可以将元素单独定义为一个文件。该文件中仅可定义一个元表,且一般文件名与元表名称相同。若一个文件要使用其它文件中定义的元表,只需使用 require "元表文件名"即可将元表导入使用。

如果用户想扩展该元表而又不想修改元表文件,则可在用户自己文件中重写其相应功能的元方法即可。

新创建一个meta.lua文件

c 复制代码
-- 声明一个元表
meta = {
	__add = function(tab, num)
		-- 遍历tab中的所有元素
		for k, v in pairs(tab) do
			-- 若value为数值类型,则做算术加法
			if type(v) == "number" then
				tab[k] = v + num
			-- 若value为string,则做字符串拼接
			elseif type(v) == "string" then
				tab[k] = v..num
			end
		end

		-- 返回变化过的table
		return tab
	end,  -- 注意,这里必须要添加一个逗号

	__tostring = function(tab)
		str = ""
		-- 字符串拼接
		for k, v in pairs(empsum) do
			str = str.." "..k..":"..v
		end

		return str
	end
};

在另一个文件中导入元表直接使用

c 复制代码
-- 导入元表
require "meta"

emp = {"北京", name="张三", age=23, "上海", depart="销售部", "广州", 12, "深圳"}

-- 将原始表与元表相关联
setmetatable(emp, meta)

empsum = emp + 5

print(emp)
print(empsum)


meta.__index = function(tab, key)
	print("通过【"..key.."】访问的值不存在")
	return "这个真没有"
end

print(emp.x)
相关推荐
悟能不能悟6 小时前
怎么使用postman批量的给api做测试
测试工具·lua·postman
wzfj123459 小时前
vscode通过remote-ssh快速浏览远程pc的文件
vscode
ayaya_mana12 小时前
VS Code 远程开发:SSH连接与远程资源管理器的配置
linux·ide·windows·vscode·远程资源管理
吞掉星星的鲸鱼12 小时前
VScode安装codex
ide·vscode·编辑器
啊湘14 小时前
VSCODE英文界面切换为中文(适用CURSOR等使用)
ide·vscode·编辑器·bug·cursor
叶庭云16 小时前
一文理解在 VSCode 中成功使用 Claude Code 插件
vscode·插件·api key·vibe coding·claude code·base url·coding agent
zhaqonianzhu1 天前
【vsc】cpptools占用内存过大
vscode
智慧地球(AI·Earth)1 天前
Codex配置问题解析:wire_api格式不匹配导致的“Reconnecting...”循环
开发语言·人工智能·vscode·codex·claude code
markvivv1 天前
在 Kylin Linux Advanced Server for Kunpeng V10 上构建 VSCode 1.106
linux·vscode·kylin
zhangfeng11332 天前
Kiro python环境的设置 中文语言包设置,通用vscode ,因为kiro是vscode基础上做的
开发语言·vscode·python