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

发布于:2025-05-29 ⋅ 阅读:(23) ⋅ 点赞:(0)


一、 table

1. 数组

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

  • Lua 中的数组索引是从 1开始的;
  • 无需声明数组长度,可以随时增加元素;
  • 同一数组中的元素可以是任意类型。
--声明一个空的数组
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 即为索引。

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)

在这里插入图片描述

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 不会占用数组的数字索引值。

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 无关,仅是连接数组元素。
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 不支持该函数。
Cities = {"bj北京", "sh上海", "gz广州", "sz深圳", "tj天津", "gz广州"}
print(table.unpack(Cities,2,4)) --返回下标为234的元素

在这里插入图片描述

C、 table.pack()

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

-- 输出表内容
for i = 1, t.n do
    print(i, t[i])
end

在这里插入图片描述

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 参数可选,默认为数组部分末尾。
Cities = {"北京", "上海", "广州", "深圳", "天津", "广州"}
table.insert(Cities,2,"太原") --在下标为2的位置插入
table.insert(Cities,"寿阳") --不指明下标,则在最后插入
print(table.concat(Cities,","))

在这里插入图片描述

F、 table.remove ()

  • 函数 table.remove (table , pos)
  • 解析 该函数用于删除并返回指定 table 中数组部分位于 pos 位置的元素。其后的元素会被前移。pos 参数可选,默认删除数组中的最后一个元素。
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,则排序会报错。
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类似

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文件

-- 声明一个模块
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 值作为模块的别名,就可以使用别名来访问模块了。

-- 导入一个模块
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。

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。

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

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

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

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

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

-- 在原始表中若找不到,则会到元表指定的普通表中查找
print(emp[6])

在这里插入图片描述

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)

在这里插入图片描述

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 元方法。

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 元方法。

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

		return str
	end

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

print(emp)
print(empsum)

在这里插入图片描述

6. _ _cal元方法

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

类似于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文件

-- 声明一个元表
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
};

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

-- 导入元表
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)

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到