https://blog.csdn.net/wujie_03/article/details/73775955 //深入理解lua
《lua设计与实现》 一书
https://blog.csdn.net/weixin_42973416/article/details/103294010 //深入理解Table
https://tencent.github.io/xLua/public/v1/guide/index.html //XLua
设计目的:
嵌入应用程序,为应用程序提供灵活的扩展和定制功能
Lua 特性
<1. 自动内存管理;只提供了一种通用类型的表(table),用它可以实现数组,哈希表,集合,对象;
<2. 语言内置模式匹配;闭包(closure);函数也可以看做一个值;
闭包: 就是能够读取其他函数内部变量的函数.
<3. 通过闭包和table可以很方便地支持面向对象编程所需要的一些关键机制,比如数据抽象,虚函数,继承和重载等。
Lua 基本语法
单行注释: - - 多行注释: - -[ [ 多行注释 - - ] ]
标示符: 最好不要使用下划线加大写字母的标示符,因为Lua的保留字也是这样的 .
一般约定,以下划线开头连接一串大写字母的名字(比如 _VERSION)被保留用于 Lua 内部全局变量。
关键词
if false or nil then
print("至少有一个是 true")
else
print("false 和 nil 都为 false!")
end
全局变量
1. 在默认情况下,变量总是认为是全局的。
2. 全局变量不需要声明,给一个变量赋值后即创建了这个全局变量,访问一个没有初始化的全局变量也不会出错,只不过得到的结果是:nil。在条件表达式中相当于false。
3.删除一个全局变量,只需要将变量赋值为nil,变量b就好像从没被使用过一样。换句话说, 当且仅当一个变量不等于nil时,这个变量即存在。
4.动态类型语言,不要类型定义,只需要为变量赋值。 值可以存储在变量中,作为参数传递或结果返回。
Lua 数据类型
nil 、boolean 、 number 、 string 、userdata 、 function 、thread 和 table。
nil(空)
<1. 相当于null , 没有任何有效值,它只有一个值 -- nil。例如打印一个没有赋值的变量,便会输出一个 nil 值:
<2. 对于全局变量和 table,nil 还有一个"删除"作用,给全局变量或者 table 表里的变量赋一个 nil 值,等同于把它们删掉
<3. nil 作比较时应该加上双引号. nil是字符串nil,所以在比较变量值是否是nil时,要使用“nil”来比较。 type(X)=="nil" true
<4. Lua 把 false 和 nil 看作是"假",其他的都为"真": if(0)也是true;
string(字符串)
1. 单引号、双引号、 [[ ]]一块字符串 ==》都表示字符串
2.对一个数字字符串上进行算术操作时,Lua 会尝试将这个数字字符串转成一个数字 : print("2" + 6) 8
3. 字符串连接使用的是 .. ,而不是++
4. 计算字符串长度,用#: print(#len) // print(#"www.runoob.com")
table(表)
<1. Lua 里表的默认初始索引一般以 1 开始。不自定义key时,使用默认索引;
<2. table 的创建是通过"构造表达式"来完成,最简单构造表达式是{},空表。
<3. Lua 中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字或者是字符串。
<4. value: 相当于字典的值。
<5. (1). 在赋值的过程中,如果table中没有此key,则自动添加此key, 没有赋值时,自动赋值为nil;
(2) .在根据key找value时,如果没有此key,则输出nil.
function(函数)
1. 在 Lua 中,函数是被看作是"第一类值(First-Class Value)";
第一类值 指函数可以储存到变量中或table中,可以作为实参传递,可以作为实参传递给其他函数、可以作其他函数的返回值。
词法域 指一个函数可以嵌套到另一个函数中,内部函数可以访问外部函数的变量;
Lua中,一个closure闭包就是一个函数加上该函数所需访问的所有局部变量。c2是创建了一个新的闭包对象。函数实际上是持有某函数的变量。
function newCounter()
local i = 0
return function()
i = i + 1
return i
end
end
c1 = newCounter()
print(c1()) --> 1
print(c1()) --> 2
c2 = newCounter()
print(c2()) --> 1
print(c2()) --> 2
表的输出顺序:
function testFun(tab)
for k ,v in pairs(tab) do
print(k.."="..v)
end
end
tab={key1="val1",key2="val2",key3="val3",key4="val4"}
testFun(tab)
--key1=val1
--key3=val3
--key4=val4
--key2=val2
<1. 每次遍历,出现的顺序是随机的: 和插入时的位置冲突有关系。测试:数组只是起存储作用,散列表起插入时出现散列位置冲突。
thread(线程)
userdata(自定义类型)
userdata 是一种用户自定义数据,用于表示一种由应用程序或 C/C++ 语言库所创建的类型,可以将任意 C/C++ 的任意数据类型的数据(通常是 struct 和 指针)存储到 Lua 变量中调用。
Lua 变量
<1. Lua 变量有三种类型:全局变量、局部变量、表中的域。
<2. Lua 中的变量全是全局变量,那怕是语句块或是函数里,除非用 local 显式声明为局部变量。
<3. 局部变量的作用域为从声明位置开始到所在语句块结束。
<4. 变量的默认值均为 nil。
赋值语句
lua可以对多个变量同时赋值,变量列表和值列表用‘,’号隔开。Lua 对多个变量同时赋值,不会进行变量传递,仅做值传递:
a, b = 10, 2 //可以用来交换变量的值
a, b = 0, 1
a, b = a+1, a+1
print(a,b) --> 1 1
注意: 当变量个数和值的个数不一致时,Lua会一直以变量个数为基础采取以下策略。
a. 变量个数 > 值的个数 按变量个数补足nil
b. 变量个数 < 值的个数 多余的值会被忽略
索引
对 table 的索引使用方括号 []。 Lua 也提供了 . 操作(当key是字符串类型时才能这样用)。
Lua 循环
<1. 数值for循环
var 从 exp1 变化到 exp2,每次变化以 exp3 为步长递增 ,并执行一次 "执行体"。exp3 是可选的,如果不指定,默认为1。
for var=exp1,exp2,exp3 do
<执行体>
end
--for的三个表达式在循环开始前一次性求值,以后不再进行求值。
--比如上面的f(x)只会在循环开始前执行一次,其结果用在后面的循环中。
-- exp3 是可选的,如果不指定,默认为1。
function f(x)
print("function")
return x*2
end
for i=1,f(5) do print(i)
end
lua中没有continue语句,因此需要通过下边方式,来防止break直接跳出for循环;repeat相当于while
for i = 10, 1, -1 do --从10开始,-1操作,>=1
repeat
if i == 5 then
print("continue")
break
end
print(i, "loop ")
until true
end
<2. 泛型for循环
类似于foreach ,ipairs是迭代器;
注意: 迭代器 ipairs(a) ,如果遇到a中一个元素是nil,则跳出迭代器,停止输出;
--打印数组a的所有值
a = {"one", "two", "three"}
for i, v in ipairs(a) do
print(i, v)
end
<3. while\repeat
while(condition)
do
//....
end
repeat
//....
until( condition )
迭代器 ?? 有状态和多状态迭代器一般能用在哪里
<1. 泛型for迭代器
原理: 主要控制迭代函数、状态常量、控制变量三个参数。
for k, v in pairs(t) do
print(k, v)
end
注意: 迭代器 ipairs(a) ,如果遇到a中一个元素是nil,则跳出迭代器,停止输出;
<2. 无状态迭代器
指不保留任何状态的迭代器,因此在循环中我们可以利用无状态迭代器避免创建闭包花费额外的代价。
每一次迭代,迭代函数都是用两个变量(状态常量和控制变量)的值作为参数被调用,一个无状态的迭代器只利用这两个值可以获取下一个元素。
function square(iteratorMaxCount,currentNumber)
if currentNumber<iteratorMaxCount then
currentNumber = currentNumber+1
return currentNumber, currentNumber*currentNumber
end
end
--传入3,0到pairs(t)(表达式列表)中。
--当 Lua 调用 ipairs(a) 时,他获取三个值:迭代函数 square、状态常量iteratorMaxCount、控制变量初始值 0;
for i,n in square,3,1
do
print(i,n)
end
==>
>lua -e "io.stdout:setvbuf 'no'" "LuaTest.lua"
2 4
3 9
>Exit code: 0
<2. 多状态迭代器
很多情况下,迭代器需要保存多个状态信息而不是简单的状态常量和控制变量,最简单的方法是使用闭包
Lua 流程控制
注意:false和nil为Lua里认为是假的。true和非nil为真。
要注意Lua中0为true.
if else
if(布尔表达式) then
--[ 布尔表达式为 true 时执行该语句块 --]
else
--[ 布尔表达式为 false 时执行该语句块 --]
end
a=10
b=12
if a==b then
print("1")
elseif a~=b then //elseif 要连一块
print("2")
else
print("3")
end
Lua 函数
optional_function_scope function function_name( argument1, argument2, argumentn)//可选参数、默认全局函数 函数名字
function_body
return result_params_comma_separated //函数返回值,可以是多个返回值
end
函数作参数传递给函数:
返回多个值的情况:
s, e = string.find("www.runoob.com", "runoob") //在第一个字符串中的位置
print(s, e)
function maximum (a)
local mi = 1 -- 最大值对应的索引
local m = a[mi] -- 最大值
for i,val in ipairs(a) do
if val > m then
mi = i
m = val
end
end
return m, mi
end
print(maximum({8,10,23,12,5}))
可变参数
<1. {...} 表示一个由所有变长参数构成的数组
<2. 有时候我们可能需要几个固定参数加上可变参数,固定参数必须放在变长参数之前:,逗号隔开;
如果有固定参数,那么固定参数放在可变参数的前边,使用逗号隔开
function add(...)
local arg={...} -->也可以把可变参数赋值给一个变量
print(select("#",...)) -->获取可变参数的数量
local s=0
for i,v in pairs{...} do --{...}表示由可变参数组成的数组
s=s+v
end
return s
end
print(add(3,4,5,6,7))
- select('#', …) 返回可变参数的长度
- select(n, …)返回第 n 个可变 的实参
Lua 运算符
算数运算 、关系运算 、逻辑运算 、其他运算
Lua中设定 A 的值为10,B 的值为 20:
Lua中的不等于是~=。
if ( a and b ) 、if ( a or b ) 、if ( not( a and b) )
其他运算符: .. 连接两个字符串 #返回字符串或者表的长度。#"Hello" 返回 5 x^2是x的2次方
Lua 字符串
Lua 数组
默认索引是从1开始,但是可以自定义设置任意的值作为key.
多维数组:
Lua 迭代器 ???
泛型for迭代器: 保存三个值: 迭代函数 、 状态常量 、控制变量。
应用: 可以自定义迭代过程,做一些事情;
if currentNumber<iteratorMaxCount then
currentNumber = currentNumber+1
return currentNumber, currentNumber*currentNumber
end
end
--传入3,0到pairs(t)(表达式列表)中。
--当 Lua 调用 ipairs(a) 时,他获取三个值:迭代函数 square、状态常量iteratorMaxCount、控制变量初始值 0;
for i,n in square,3,1
do
print(i,n)
end
泛型for的执行过程:
Lua table(表)
table的作用: table 是 Lua 的一种数据结构用来帮助我们创建不同的数据类型,如:数组、字典等。
Lua也是通过table来解决模块(module)、包(package)和对象(Object)的。 例如string.format表示 使用"format"来索引 table string。
<1. Lua表移除引用: mytable=nil.
-- 初始化表
mytable = {}
-- 指定值
mytable[1]= "Lua"
-- 移除引用
mytable = nil
-- lua 垃圾回收会释放内存
<2. 移除引用操作: 当我们为 table a 并设置元素,然后将 a 赋值给 b,则 a 与 b 都指向同一个内存。如果 a 设置为 nil (是把引用置为nil ),则 b 同样能访问 table 的元素。如果没有指定的变量指向a,Lua的垃圾回收机制会清理相对应的内存.
<3. table的常用操作:
table.concat() //连接表中的元素或者指定的元素,连接其他字符到连接中。
table.insert (table, [pos,] value): //把value插入到表中的pos位置中,pos是可选参数,默认是尾部。
table.remove (table [, pos]) //删除pos位置,默认从尾部删除
table.sort (table [, comp]) //对表进行升序排序,字符串元素首字母,数字大小
“注意:当我们获取 table 的长度的时候无论是使用 # 还是 table.getn 其都会在索引中断的地方停止计数,而导致无法正确取得 table 的长度”。
下图验证后没问题啊:
当我们获取 table 的长度的时候无论是使用 # 还是 table.getn 其都会在索引中断的地方停止计数,而导致无法正确取得 table 的长度。
--连接:
tab={"a","b","c"}
--[[
--直接把表中的元素连接 abc
print("链接表元素: ",table.concat(tab))
--指定分隔符 a:b:c
print("指定的符号连接表元素: ",table.concat(tab,":"))
--b:c
print("指定索引和指定字符来连接表元素 : ", table.concat(tab,":",2,3))
--]]
--插入:自动向后排,删除时自动向前排
--向pos处插入元素【pos默认是末尾】
table.insert(tab,"wyj")
print(tab[4])
--向索引为1的元素处插入一个元素
table.insert(tab,1,"wyj")
print(#tab.." "..tab[1]..": "..tab[5])
--remove和insert用法相同,默认remove的是最后一个元素
table.remove(tab,5)
print(tab[5])
--排序
for k,v in ipairs(tab) do
print("排序 before: "..k,v)
end
table.sort(tab)
for k,v in ipairs(tab) do
print("排序 ago : "..k,v)
end
Lua 模块与包
模块管理机制: 可以把一些公用的代码放在一个文件里,以 API 接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度,Lua 的模块是由变量、函数等已知元素组成的 table,因此创建一个模块就是创建一个 table,然后把需要导出的常量、函数放入其中,最后返回这个 table 就行。
定义和加载:
--可以使用文件名直接加载或者给加载的文件定义一个别名
--返回的是一个table,所以像table一样来调用
local m= require("module")
print(m.constant)
m.func3()
加载机制: ??
注意:
<1
function module.fun1() --公有方法可以点的形式定义,否则在其他lua脚本里访问不到
print("这是一个公有的函数")
end
<2.module={} 模块名字和该Lua脚本名字必须一致,不然加载包名加载不到;
<3. function module.fun3() 这种全局函数外部才能访问的到。
Lua 元表(Metatable)
元表的意义:
在 Lua table 中我们可以访问对应的key来得到value值,但是却无法对两个 table 进行操作。
元表: 对表与表之间的进行操作(加减等)。__是两个下划线。
- setmetatable(table,metatable): 对指定 table 设置元表(metatable),如果元表(metatable)中存在 __metatable 键值,setmetatable 会失败。
- getmetatable(table): 返回对象的元表(metatable)。
_index: 用来对表访问
_index 值是一个表时、元表是表:
_index 是元表最常用的键,通过普通键来访问table时,如果这个键没有值,那么Lua就会寻找table的metatable(假设有元表)中的_index键。如果_index包含一个表格中查找相应的键。
other = { foo = 3 }
t = setmetatable({}, { __index = other })
print(t.foo)
print(t.bar)
_index 值是一个方法时、元表是方法:
_index包含一个函数,Lua就会调用那个函数,table和键会作为参数传递给函数。
mytable = setmetatable({key1 = "value1"}, {
__index = function(mytable, key)
if key == "key2" then
return "metatablevalue"
else
return nil
end
end
})
--表中存在值时,直接返回,不再在元表中查找,否则还需要在元表中查一下
print(mytable.key1,mytable.key2)
总结
Lua 查找一个表元素时的规则,其实就是如下 3 个步骤:
- 1.在表中查找,如果找到,返回该元素,找不到则继续
- 2.判断该表是否有元表,如果没有元表,返回 nil,有元表则继续。
- 3.判断元表有没有 __index 方法,如果 __index 方法为 nil,则返回 nil;如果 __index 方法是一个表,则重复 1、2、3;如果 __index 方法是一个函数,则返回该函数的返回值。
_newindex:元方法用来对表更新
元表中的__newindex的作用
--调用时机:当我们对普通表中的一个新的索引修改时才会起作用、或者添加新的键值对时。
--如果后边跟的时一个表,则添加新键值对时,则会把这个key和value 直接添加到这个后边跟的表中。
--如果后边跟的是一个函数,调用后边跟的函数,则:
__newindex=function(table,key,value) --普通表 修改的key 修改成的值
后边的函数通过 rawset(table,key,value) ,来加入到这个普通表中。
end
mymetatable = {}
mytable = setmetatable({key1 = "value1"}, { __newindex = mymetatable })
--添加一个新值
mytable.newkey = "新值2"
print(mytable.newkey,mymetatable.newkey)
--修改一个值
mytable.key1 = "新值1"
print(mytable.key1,mymetatable.key1)
为表添加操作符
function table_maxn(t)
local mn = 0
for k, v in pairs(t) do
if mn < k then
mn = k
end
end
return mn
end
-- 两表相加操作
mytable = setmetatable({ 10, 11, 12 }, {
__add = function(mytable, newtable)
for i = 1, table_maxn(newtable) do
table.insert(mytable, table_maxn(mytable)+1,newtable[i])
end
return mytable
end
})
secondtable = {13,14,15}
--检查当前元表中 是否有__add键,所对应的值
--mytable = mytable + secondtable
for k,v in ipairs(mytable) do
print(k,v)
end
__call 元方法 ???
--定义这个函数后,就可以把表当函数来使用时。
function table_maxn(t)
local mn = 0
for k, v in pairs(t) do
if mn < k then
mn = k
end
end
return mn
end
-- 定义元方法__call
mytable = setmetatable({10}, {
__call = function(mytable, newtable)
sum = 0
for i = 1, table_maxn(mytable) do
sum = sum + mytable[i]
end
for i = 1, table_maxn(newtable) do
sum = sum + newtable[i]
end
return sum
end
})
newtable = {10,20,30}
print(mytable(newtable))
__tostring 元方法
__tostring 元方法用于修改表的输出行为。即对表操作后的输出。
mytable = setmetatable({ 10, 20, 30 }, {
__tostring = function(mytable)
sum = 0
for k, v in pairs(mytable) do
sum = sum + v
end
return "表所有元素的和为 " .. sum
end
})
print(mytable)
Lua 协同程序(coroutine)
Lua 协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。
创建协程:
--执行到creat时,就创建一个协程
co = coroutine.create(
function(i)
print(i.." "..coroutine.status(co)) --正在运行协程Running
end
)
print(coroutine.status(co)) -- 创建的协程是suspended
coroutine.resume(co, 99) -- 传入值
print(coroutine.status(co)) -- 协程执行完后dead
<1 . coroutine.yield() --从for循环中return出去了 .
<2. 协程中有yield return 挂起,所以是suspended,如果没有yield return则运行结束dead状态。
<3. 协同程序唤醒,resume唤醒成功返回true,否则返回false;
<4. 此处注意:resume的参数中,除了第一个参数,剩下的参数将作为yield的参数传递进去,即r=h)
<5. resume和yield的配合强大之处在于,resume处于主程中,它将外部状态(数据)传入到协同程序内部;而yield则将内部的状态(数据)返回到主程中。
co2 = coroutine.create(
function()
for i=1,10 do
print(i)
if i == 3 then
print(coroutine.status(co2)) --running
print(coroutine.running()) --thread
end
coroutine.yield() --从for循环中return出去了
end
end
)
coroutine.resume(co2) --1
coroutine.resume(co2) --2
coroutine.resume(co2) --3
print(coroutine.status(co2)) -- 协程中有yield return 挂起,所以是suspended,如果没有yield return则运行结束dead状态。
print(coroutine.running())
此处注意:resume的参数中,除了第一个参数,剩下的参数将作为yield的参数传递进去,即r=h)
co = coroutine.create(function (a , b)
print("第一次协同程序执行输出", a, b) -- co-body 1 10
local r = coroutine.yield(2 * a)
print("第二次协同程序执行输出", r)
local r, s = coroutine.yield(a + b, a - b) -- a,b的值为第一次调用协同程序时传入
print("第三次协同程序执行输出", r, s)
return b, "结束协同程序" -- b的值为第二次调用协同程序时传入
end)
print("main", coroutine.resume(co, 1, 10)) -- true, 4
print("--分割线----")
print("main", coroutine.resume(co, "h")) -- true 11 -9 此处注意:resume的参数中,除了第一个参数,剩下的参数将作为yield的参数传递进去,即r=h)
yield返回;
print("---分割线---")
print("main", coroutine.resume(co, "x", "y")) -- true 10 end
Lua 文件 I/O
- 简单模式(simple model)拥有一个当前输入文件和一个当前输出文件,并且提供针对这些文件相关的操作。
- 完全模式(complete model) 使用外部的文件句柄来实现。它以一种面对对象的形式,将所有的文件操作定义为文件句柄的方法
-- --------------------- 简单模式-----------------
-- 以只读方式打开文件
file = io.open("test.lua", "r")
-- 设置默认输入文件为 test.lua
io.input(file)
-- 输出文件第一行
print(io.read())
io.close(file)
-- 以附加方式打开文件
file=io.open("test.lua","a")
io.output(file) --在该文件打开状态下,即被占用时,则无法打开
io.write("我是以附加模式打开的,来添加一行到末尾")
io.close()
--------------完全模式-------
--[[
--以面向对象的方式打开
file=io.open("test.lua","r")
file:seek("set",6) --定位到从开头开始第10个字节,貌似EOF换行符占2个字节
print(file:read("*a"))
file:close()
file=io.open("test.lua","a")
file:write("test lua file")
file:close()
--]]
Lua 错误处理 ??
Lua调试 ??
Lua 垃圾回收
Lua 中所有用到的内存,如:字符串、表、用户数据、函数、线程、 内部结构等,都服从自动管理。
回收机制:增量标记-扫描收集器。垃圾收集器间歇率和垃圾收集器步进倍率两个参数来控制(值100在内部表示1);
垃圾收集器间歇率 : 控制着收集器需要在开启新的循环前要等待多久。 增大这个值会减少收集器的积极性。(一般设置为100以下,不要等待,如果设置为200,则在内存使用量达到之前的2倍时,进行回收)
垃圾收集器步进倍率 : 控制着收集器运作速度相对于内存分配速度的倍率。 默认值是 200 ,这表示收集器以内存分配的"两倍"速工作。
Lua 面向对象
创建对象
创建对象是为类的实例分配内存的过程。每个类都有属于自己的内存并共享公共数据。
r = Rectangle:new(nil,10,20)
self : 因为self就是代表当前的这个表,系统会自动赋值
访问属性
我们可以使用点号(.)来访问类的属性:
访问成员函数
我们可以使用冒号 : 来访问类的成员函数:
Lua编程的一些细节:
1. 调用Lua函数
<1. 嵌套函数不执行,即函数内部的函数,AFun() { aFun(){} } 不调用aFun不会被执行。
<2. 函数的申明必须在调用之前;
local function test()
local try = function()
--print(a+b)
print("666")
end
local catch = function(errors,...)
print(errors)
end
--嵌套函数不自己执行,在xpcall中调用了之后才执行;
local ok, errors = xpcall(try, debug.traceback)
if not ok then
catch(errors)
else
print("wyj=>no error")
end
end
test()
Lua了解:
使用IDEA来打开Lua工程,;安装步骤https://blog.csdn.net/qq_35433081/article/details/104521854