lua知识点
一、前言
平常开发中C#用得比较多,lua没有重度使用过。这里主要记录一下lua的知识点,供自己回顾
二、知识点
2.1 数据类型
- nil:表示无效值或者没有值,它是 Lua 的一个关键字(将nil赋予一个变量相当于删除它)
- boolean:表示布尔类型,可以是 true 或 false
- number:表示数字类型,包括整数和浮点数
- string:表示字符串类型,可以是单引号或双引号包裹的文本
- table:表示 Lua 中最重要的数据结构,由键值对组成
- function:表示函数类型,可以是内置函数或自定义函数
- userdata:表示 Lua 支持的扩展类型,通常用于表示 C 语言的数据结构
- thread:表示 Lua 的协程类型,可以理解为独立的执行流程。
在lua中,boolean,number,string相当于C#里的值类型(C# 中,string 是引用类型),table则相当于引用类型
2.2 table表
2.2.1 table的构造
-- 初始化表
tab = {}
-- 指定值
tab[1]= "Lua"
-- 移除引用
tab = nil
2.2.2 table的结构
table可以分为哈希表和数组2部分,哈希表表示带有键值对的部分(类似C#的字典),数组则是没有键值对的部分。我们看看下面的代码分析
tab1 = { "apple","pear", [1] = 1, [2] = 2, nil, "watermelon"}
--1.结构如下:
-- 哈希表部分:[1] = 1, [2] = 2
-- 数组部分:"apple","pear","nil","watermelon"
--2.接着tab会把数组部分,从左到右,从1开始自增附上key转为哈希表
--数组部分转化后为:[1] = apple, [2] = "pear",[3] = nil, [4] = "watermelon"
--3.我们可以看到数组部分和哈希表部分的key会冲突,table会以后面的数组部分为准。
--则转化为table结构如下:
--tab1 = {[1] = apple, [2] = "pear",[3] = nil, [4] = "watermelon"}
2.3 ipairs和pairs的异同点
2.3.1 相同点
- ipairs和pairs都可以用来对table进行遍历
2.3.2 不同点
- ipairs:ipairs是有序遍历,从键[1]开始往上遍历,遇到nil或者键值断序了,就会停止遍历
- pairs: pairs遍历顺序是不确定的,但会遍历所有键,遇到nil会跳过,继续遍历下一个key。
好,我们看下代码和打印
tab1 = { "apple", "pear", [1] = 1, [2] = 2, nil, "watermelon" }
print("---ipairs---")
for k, v in ipairs(tab1) do
print(k, v)
end
print("---pairs---")
for k, v in pairs(tab1) do
print(k, v)
end
输出如下:
根据2.2.2的分析,我们知道tab1最终结构为:
tab1 = {[1] = apple, [2] = "pear",[3] = nil, [4] = "watermelon"}
---ipairs---
1 apple
2 pear
---pairs---
1 apple
2 pear
4 watermelon
输出结果是符合ipairs和pairs的遍历规则的
2.3 元表
元表也是一个表,我们可以为一个表设置元表
- 设置元表
local tab = {}
local metaTab = {1,2}
-- 把表metaTab设置为表tab的元表
setmetatable(tab,metaTab);
- 获取元表
-- 获取tab的元表
local targetMeta = getmetatable(tab)
2.4 元方法
2.4.1 定义
在 Lua 中,元方法(metamethod)是一种特殊的函数,它们被用来定义表的行为,元方法被存储在一个独立的metatable元表中。例如,在表中使用 + 运算符时,Lua 会查找其元表里是否有 __add 元方法,如果有,则使用该元方法实现表的加法操作。
2.4.2 常见的元方法
-
__index:用于查询表中不存在的键,当表中不存在指定键时,Lua 会自动调用 __index 元方法来返回一个默认值或者一个新的值。
-
__newindex:用于添加新的键值对,当尝试向表中添加一个不存在的键时,Lua 会自动调用 __newindex 元方法来添加新的键值对或者修改已有的键值对
-
__add、__sub、__mul、__div、__mod、__pow、__unm:用于重载运算符,当使用表参与数学运算时,Lua 会自动调用相应的元方法来实现运算。
-
__tostring:用于将表转换为字符串,当使用 tostring 函数将表转换为字符串时,Lua 会自动调用 __tostring 元方法来实现转换。
通过合理地使用元方法,我们可以为 table 定义各种不同的行为,从而实现更加灵活、高效、易用的数据结构和算法。
2.5 元表和元方法的应用
2.5.1 __index应用
我们可以使用__index元方法来为一个表增加键值对,如
local tab = {}
local metaTab = {1,2}
-- 把表metaTab设置为表tab的元表
setmetatable(tab,metaTab);
metaTab.__index = metaTab;
print(tab[1])
--输出
1
- 表的key查找规则
我们可以看到tab表是没有【1】这个key的,但是输出了1,这里就跟表的key查找规则有关了。查找规则如下:
- 比如tab[1],先查找tab里有没【1】这个key,有则输出,无则进入下一步
- 查看该表有无元表和__index元方法,没有的话返回nil,有的话,如果__index元方法指向一个方法,则根据该方法返回值,如果指向一个表,则进入下一步
- 查看该表有没【1】这个key,有则输出,无进看看该表有没有自己的元表和__index元方法(相当于又回到了第1步,直到找到或找不到为止)
因为tab没有【1】这个key,但是它有metaTab 元表和__index元方法,__index元方法指向了metaTab 元表本身,metaTab[1] = 1,所以返回了1
2.5.2 实现面向对象
我们可以使用元表和__index元方法来实现C#中类和对象的用法
-- 定义一个ClassA类
ClassA = {x = 1, y = 2}
ClassA.__index = ClassA
--定义一个New构造方法,返回对象实例
function ClassA.New(x,y)
--创建新对象
local o = {}
--设置元表位ClassA
setmetatable(o,ClassA);
--为变量赋值
o.x = x;
o.y = y;
return o;
end
--创建对象实例inst1
local inst1 = ClassA.New(3,4)
--创建对象实例inst2
local inst2 = ClassA.New(5)
--打印inst1
print("inst1.x:"..inst1.x)
print("inst1.y:"..inst1.y)
--打印inst2
print("inst2.x:"..inst2.x)
print("inst2.y:"..inst2.y)
输出如下
inst1.x:3
inst1.y:4
inst2.x:5
inst2.y:2
可以看到实现了类似C#创建对象的效果
2.5.3 __newindex应用
__newIndex 当尝试向表中添加一个不存在的键时,Lua 会自动调用 __newindex 元方法来添加新的键值对或者修改已有的键值对
我们可以用上面的例子加上__newindex方法后,来看下它的输出
ClassA = {x = 1, y = 2}
ClassA.__index = ClassA
ClassA.__newindex = ClassA
function ClassA.New(x,y)
local o = {}
setmetatable(o,ClassA);
o.x = x;
o.y = y;
return o;
end
--创建2个实例,并赋值
local inst1 = ClassA.New(3,4)
local inst2 = ClassA.New(5,6)
--继续赋值
inst1.z = 7;
inst2.z = 8;
--打印inst1
print("inst1.x:"..inst1.x)
print("inst1.y:"..inst1.y)
print("inst1.z:"..inst1.z)
--打印inst2
print("inst2.x:"..inst2.x)
print("inst2.y:"..inst2.y)
print("inst2.z:"..inst2.z)
输出
inst1.x:5
inst1.y:6
inst1.z:8
inst2.x:5
inst2.y:6
inst2.z:8
可以看到inst1和inst2的输出都一样,因为有了__newindex元方法,当对表自身没有的key进行赋值,就会赋值到__newindex元方法对应的表。因为修改到的是同一个表,所以inst1和inst2的输出是一样的。
2.6 修饰方法时,. 和 : 的区别
. 和 **:**都可以用来修饰方法,用 : 修复方法时用自动传入类self变量,如
--下面这2个方法等价
function class.func1(self) end
function class:func1( ) end
可以看看下面的例子
ClassA = { x = 1, y = 2 }
ClassA.__index = ClassA
ClassA.__newindex = ClassA
function ClassA:func1(a)
print("ClassA:func1:"..a)
print("ClassA:func1+:"..(a + self.x))
end
function ClassA.func2(a)
print("ClassA.func1:"..a)
print("ClassA.func1+:"..(a + ClassA.x))
end
ClassA:func1(3)
ClassA.func2(3)
输出
ClassA:func1:3
ClassA:func1+:4
ClassA.func1:3
ClassA.func1+:4