lua语法
数据类型
Lua 是动态类型语言,变量不要类型定义,只需要为变量赋值。 值可以存储在变量中,作为参数传递或结果返回。
nil
--[[
可以使用 type(vale) 来返回vale的值
nil 作比较时应该加上双引号 "
]]--
print(type(nil));
a={key1="val1",key2="val2",key3="val3",key4="val4"}
--table变量类似map吧
for k,v in pairs(a) do
print(k.."-"..v);
end
a.key1=nil --[[删除]]
for k,v in pairs(a) do
print(k.."-"..v);
end
--上面为false,下面为true
print(type(nil)==nil)
print(type(nil)=="nil")
--[[
因为type(nil)==nil 结果为 false 的原因是 type(X) 实质是返回的 "nil" 字符串,是一个 string 类型:
]]--
boolen
-- boolen
if false or nil then --把false和nil都看成false
print("false or nil")
else
print("true")
end
if 0 then
print("数字0 是true") --c++中约定俗成0为false
else
print("数字0 是false")
end
number
Lua 默认只有一种 number 类型 -- double(双精度)类型(默认类型可以修改 luaconf.h 里的定义)
string
字符串由一对双引号或单引号来表示。
字符串是一种基本的数据类型,用于存储文本数据
也可以用 2 个方括号 "[[]]" 来表示"一块"字符串
在对一个数字字符串上进行算术操作时,Lua 会尝试将这个数字字符串转成一个数字,链接字符串的操作是用 ".."
可以用"#"来计算字符串长度,放在字符串前面
print([[
man!
what can I say!
monba out!]])
print("2"+6) --8.00
print("2"+"6") --8.00
print("-2e2"*"6")-- -1200.0
print("abc".."def") -- abcdef
print(#"123") --3
可以使用 string.len函数或 utf8.len 函数,包含中文的一般用 utf8.len,string.len 函数用于计算只包含 ASCII 字符串的长度
字符串截取使用 sub() 方法。
string.sub() 用于截取字符串,原型为:
string.sub(s, i [, j])
s:要截取的字符串。
i:截取开始位置。
j:截取结束位置,默认为 -1,最后一个字符。
Lua 提供了 string.format() 函数来生成具有特定格式的字符串, 函数的第一个参数是格式 , 之后是对应格式中每个代号的各种数据。
由于格式字符串的存在, 使得产生的长字符串可读性大大提高了。这个函数的格式很像 C 语言中的 printf()。
以下实例演示了如何对字符串进行格式化操作:
格式字符串可能包含以下的转义码:
%c - 接受一个数字, 并将其转化为ASCII码表中对应的字符
%d, %i - 接受一个数字并将其转化为有符号的整数格式
%o - 接受一个数字并将其转化为八进制数格式
%u - 接受一个数字并将其转化为无符号整数格式
%x - 接受一个数字并将其转化为十六进制数格式, 使用小写字母
%X - 接受一个数字并将其转化为十六进制数格式, 使用大写字母
%e - 接受一个数字并将其转化为科学记数法格式, 使用小写字母e
%E - 接受一个数字并将其转化为科学记数法格式, 使用大写字母E
%f - 接受一个数字并将其转化为浮点数格式
%g(%G) - 接受一个数字并将其转化为%e(%E, 对应%G)及%f中较短的一种格式
%q - 接受一个字符串并将其转化为可安全被Lua编译器读入的格式
%s - 接受一个字符串并按照给定的参数格式化该字符串
为进一步细化格式, 可以在%号后添加参数. 参数将以如下的顺序读入:
(1) 符号: 一个+号表示其后的数字转义符将让正数显示正号. 默认情况下只有负数显示符号.
(2) 占位符: 一个0, 在后面指定了字串宽度时占位用. 不填时的默认占位符是空格.
(3) 对齐标识: 在指定了字串宽度时, 默认为右对齐, 增加-号可以改为左对齐.
(4) 宽度数值
(5) 小数位数/字串裁切: 在宽度数值后增加的小数部分n, 若后接f(浮点数转义符, 如%6.3f)则设定该浮点数的小数只保留n位, 若后接s(字符串转义符, 如%5.3s)则设定该字符串只显示前n位.
匹配模式
Lua 中的匹配模式直接用常规的字符串来描述。 它用于模式匹配函数 string.find, string.gmatch, string.gsub, string.match。
你还可以在模式串中使用字符类。
字符类指可以匹配一个特定字符集合内任何字符的模式项。
s = "Deadline is 30/05/1999, firm"
date = "%d%d/%d%d/%d%d%d%d"
print(string.sub(s, string.find(s, date)))
*--> 30/05/1999*
下面的表列出了Lua支持的所有字符类:
单个字符(除 ^$()%.[]*+-? 外): 与该字符自身配对
.(点): 与任何字符配对
%a: 与任何字母配对
%c: 与任何控制符配对(例如\n)
%d: 与任何数字配对
%l: 与任何小写字母配对
%p: 与任何标点(punctuation)配对
%s: 与空白字符配对
%u: 与任何大写字母配对
%w: 与任何字母/数字配对
%x: 与任何十六进制数配对
%z: 与任何代表0的字符配对
%x(此处x是非字母非数字字符): 与字符x配对. 主要用来处理表达式中有功能的字符(^$()%.[]*+-?)的配对问题, 例如%%与%配对
[数个字符类]: 与任何[]中包含的字符类配对. 例如[%w]与任何字母/数字, 或下划线符号()配对
[^数个字符类]: 与任何不包含在[]中的字符类配对. 例如%s与任何非空白字符配对
当上述的字符类用大写书写时, 表示与非此字符类的任何字符配对. 例如, %S表示与任何非空白字符配对
table
在 Lua 里,table 的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。也可以在表里添加一些数据,直接初始化表:
--创建一个空的table
local a={}
--初始化
local tbl2={"a","b","c","d"}
--[[
不同于其他语言的数组把 0 作为数组的初始索引,
在 Lua 里表的默认初始索引一般以 1 开始。
tbl2[0] 会返回 "nil",没添加的key也会对应"nil"
table长度不固定,有新数据添加时table长度会自动增长
]]--
--[[
Lua 中的表(table)其实是一个"关联数组"(类似map),
数组的索引可以是数字或者是字符串。
]]--
a["abc"]="cba"
key=10
a[key]=11
a[key]=a[key]+2
for k,v in pairs(a) do
print(k..":"..v)
end
function(函数)
在 Lua 中,函数是被看作是"第一类值(First-Class Value)",函数可以存在变量里:
1.存储在 global variable,local variable,和 table field 中;
2.作为 function parameter 传递
3.作为 function return value。
function f(n)
if n == 0 then
return 1
else
return n*f(n-1)
end
end
print(f(3))
f2=f
print(f2(5))
--function 可以以匿名函数(anonymous function)的方式通过参数传递:
function test(tab,fun)
for k,v in pairs(tab) do
print(fun(k,v));
end
end
tab={key1="val1",key2="val2"};
test(tab,
function(key,val)
return key.."="..val
end
);
thread(线程)
在 Lua 里,最主要的线程是协同程序(coroutine)。它跟线程(thread)差不多,拥有自己独立的栈、局部变量和指令指针,可以跟其他协同程序共享全局变量和其他大部分东西。
线程跟协程的区别:线程可以同时多个运行,而协程任意时刻只能运行一个,并且处于运行状态的协程只有被挂起(suspend)时才会暂停。
userdata(自定义类型)
userdata 是一种用户自定义数据,用于表示一种由应用程序或 C/C++ 语言库所创建的类型,可以将任意 C/C++ 的任意数据类型的数据(通常是 struct 和 指针)存储到 Lua 变量中调用。
lua 变量
变量在使用前,需要在代码中进行声明,即创建该变量。
编译程序执行代码之前编译器需要知道如何给语句变量开辟存储区,用于存储变量的值。
Lua 变量有三种类型:全局变量、局部变量、表中的域。
Lua 中的变量全是全局变量,哪怕是语句块或是函数里,除非用 local 显式声明为局部变量。
局部变量的作用域为从声明位置开始到所在语句块结束。
变量的默认值均为 nil。
赋值语句
Lua 可以对多个变量同时赋值,变量列表和值列表的各个元素用逗号分开,赋值语句右边的值会依次赋给左边的变量。
x=1
a,b=10,2*x;
print(a.." "..b);
// 10 2
local c={1,2,3,4,5}
c[1],c[2]=c[2],c[1]
print(c[1]);
// 2
遇到赋值语句Lua会先计算右边所有的值然后再执行赋值操作,所以我们可以这样进行交换变量的值
当变量个数和值的个数不一致时,Lua会一直以变量个数为基础采取以下策略:
注意:如果要对多个变量赋值必须依次对每个变量赋值。
a. 变量个数 > 值的个数 按变量个数补足nil
b. 变量个数 < 值的个数 多余的值会被忽略
多值赋值经常用来交换变量,或将函数调用返回给变量:
a,b=f()
f()返回两个值,第一个赋给a,第二个赋给b。
应该尽可能的使用局部变量,有两个好处:
避免命名冲突。
访问局部变量的速度比全局变量更快。
索引
t[i]
t.i -- 当索引为字符串类型时的一种简化写法
gettable_event(t,i)
-- 采用索引访问本质上是一个类似这样的函数调用
lua中的 or 和and
在lua里 or 和 and 并部表示取对应的‘|’和‘&’ 运算
表示 一个运算符
or 为若前为 true 则用前 否则用后
and为若前为true则用后 否则为false
local a=1
local b=2
local c=false and 2
print(c)
lua流程控制 分支语句
if(布尔表达式)
then
--[ 在布尔表达式为 true 时执行的语句 --]
end
在布尔表达式为 true 时会if中的代码块会被执行,在布尔表达式为 false 时,紧跟在 if 语句 end 之后的代码会被执行。
Lua认为false和nil为假,true 和非nil为真。要注意的是Lua中 0 为 true。
if(布尔表达式)
then
--[ 布尔表达式为 true 时执行该语句块 --]
else
--[ 布尔表达式为 false 时执行该语句块 --]
end
lua循环
Lua 支持以下循环控制语句:
while(condition)
do
statements
end
for var=exp1,exp2,exp3 do
<执行体>
end
var 从 exp1 变化到 exp2,每次变化以 exp3 为步长递增 var,并执行一次 "执行体"。exp3 是可选的,如果不指定,默认为1。
for i=10,1,-1 do //10到1
print(i)
end
泛型for循环
泛型 for 循环通过一个迭代器函数来遍历所有值,类似 java 中的 foreach 语句。
Lua 编程语言中泛型 for 循环语法格式:
a = {"one", "two", "three"}
for i, v in ipairs(a) do
print(i, v)
end
i是数组索引值,v是对应索引的数组元素值。ipairs是Lua提供的一个迭代器函数,用来迭代数组。
repeat
statements
until( condition )
--类似do while(ture)
语法格式:
goto Label
label:
:: Label ::
53才新加的内容 goto
local a=1
::label:: print(a)
a=a+1
if a<3 then
goto label
end
// 1 2
lua 函数
在Lua中,函数是对语句和表达式进行抽象的主要方法。既可以用来处理一些特殊的工作,也可以用来计算一些值。
Lua 提供了许多的内建函数,你可以很方便的在程序中调用它们,如print()函数可以将传入的参数打印在控制台上。
Lua 函数主要有两种用途:
1.完成指定的任务,这种情况下函数作为调用语句使用;
2.计算并返回值,这种情况下函数作为赋值语句的表达式使用。
参数多了会丢弃,少了会补空
optional_function_scope function function_name( argument1, argument2, argument3..., argumentn)
function_body
return result_params_comma_separated
end
(全局/局部函数) 函数命 (参数)
函数体
return 返回值,多个值以逗号隔开
end
lua函数不支持重载,默认调用最后一个函数
变长参数
function F7(...)
arg={...} --先用。。。引出来,用table用
for i=1,#arg do
print(arg[i])
end
end
F7(1,2,3,"123",true)
在lua里闭包的体现是函数的嵌套,在函数里返回一个函数改变变量的生命周期
function F9(x)
--闭包,改变传入参数的生命周期
return function(y)
return x+y
end
end
f10=F9(-6)
print(f10(5))
--- -1
表 table
table.insert(list, [pos, ]value)
.remove则是删除
在表 list 中的 pos 位置插入元素 value,并将原来在 pos 位置以及在 pos 位置后面的元素往后移。pos 为可选参数,默认值是 #list+1,所以 table.insert(list, value) 会将元素 value 插入到表 list 的末尾处!
local tbl = {1, 2, 3, 4, 5}
table.insert(tbl, 3, 666)
print(unpack(tbl))
-- 1 2 666 3 4 5
table.insert(tbl, 999)
print(unpack(tbl))
-- 1 2 666 3 4 5 999
--[[
数组,数组的遍历,自定义索引
lua里面只有table这种数组的数据类型
]]
a={1,2,3,4,5,"123456",7,nil,nil}
--lua索引从1开始
--#是通用的获取唱多的关键字
-- 5.4改了nil放在前面不会为空,最后的nil才忽略
print(#a)
for i=1,#a do
print(a[i])
end
b={{1,2,3},{2,5,6}}
--一样遍历
aa={[-1]=1,[0]=2,3,4,5}
print(#aa) --输出的是3
--只能从1开始算
pair和ipair
--不用#而是要用迭代器来遍历table
a={[0]=1,2,[-1]=3,4,5,[5]=6}
for i,k in ipairs(a) do
print("ipairs遍历的键值"..i.." "..k)
end
--[[
ipairs遍历的键值1 2
ipairs遍历的键值2 4
ipairs遍历的键值3 5
顺序遍历
只能从key为1开始往后遍历而且中间断序了后面也无法遍历
]]
for i,k in pairs(a) do
print("pairs遍历的键值"..i.." "..k)
end
--[[
pairs遍历的键值1 2
pairs遍历的键值2 4
pairs遍历的键值3 5
pairs遍历的键值0 1
pairs遍历的键值5 6
pairs遍历的键值-1 3
能把所有的值都表示出
]]
Lua 迭代器
迭代器(iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址。
在 Lua 中迭代器是一种支持指针类型的结构,它可以遍历集合的每一个元素。
泛型 for 在自己内部保存迭代函数,实际上它保存三个值:迭代函数、状态常量、控制变量。
泛型 for 迭代器提供了集合的 key/value 对,语法格式如下:
for k, v in pairs(t) do
print(k, v)
end
上面代码中,k, v为变量列表;pairs(t)为表达式列表。
以上实例中我们使用了 Lua 默认提供的迭代函数 ipairs。
下面我们看看泛型 for 的执行过程:
首先,初始化,计算 in 后面表达式的值,表达式应该返回泛型 for 需要的三个值:迭代函数、状态常量、控制变量;与多值赋值一样,如果表达式返回的结果个数不足三个会自动用 nil 补足,多出部分会被忽略。
第二,将状态常量和控制变量作为参数调用迭代函数(注意:对于 for 结构来说,状态常量没有用处,仅仅在初始化时获取他的值并传递给迭代函数)。
第三,将迭代函数返回的值赋给变量列表。
第四,如果返回的第一个值为nil循环结束,否则执行循环体。
第五,回到第二步再次调用迭代函数
在Lua中我们常常使用函数来描述迭代器,每次调用该函数就返回集合的下一个元素。Lua 的迭代器包含以下两种类型:
无状态的迭代器
多状态的迭代器
无状态的迭代器
无状态的迭代器是指不保留任何状态的迭代器,因此在循环中我们可以利用无状态迭代器避免创建闭包花费额外的代价。
每一次迭代,迭代函数都是用两个变量(状态常量和控制变量)的值作为参数被调用,一个无状态的迭代器只利用这两个值可以获取下一个元素。
这种无状态迭代器的典型的简单的例子是 ipairs,它遍历数组的每一个元素,元素的索引需要是数值。
多状态的迭代器
很多情况下,迭代器需要保存多个状态信息而不是简单的状态常量和控制变量,最简单的方法是使用闭包,还有一种方法就是将所有的状态信息封装到 table 内,将 table 作为迭代器的状态常量,因为这种情况下可以将所有的信息存放在 table 内,所以迭代函数通常不需要第二个参数。
以下列出了 Table 操作常用的方法:
Lua 模块与包
模块类似于一个封装库,从 Lua 5.1 开始,Lua 加入了标准的模块管理机制,可以把一些公用的代码放在一个文件里,以 API 接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度。
Lua 的模块是由变量、函数等已知元素组成的 table,因此创建一个模块很简单,就是创建一个 table,然后把需要导出的常量、函数放入其中,最后返回这个 table 就行。以下为创建自定义模块 module.lua,文件代码格式如下
-- 文件名为 module.lua
-- 定义一个名为 module 的模块
module = {}
-- 定义一个常量
module.constant = "这是一个常量"
-- 定义一个函数
function module.func1()
io.write("这是一个公有函数!\n")
end
local function func2()
print("这是一个私有函数!")
end
function module.func3()
func2()
end
return module
由上可知,模块的结构就是一个 table 的结构,因此可以像操作调用 table 里的元素那样来操作调用模块里的常量或函数。
上面的 func2 声明为程序块的局部变量,即表示一个私有函数,因此是不能从外部访问模块里的这个私有函数,必须通过模块里的公有函数来调用.
require 函数
Lua提供了一个名为require的函数用来加载模块。要加载一个模块,只需要简单地调用就可以了。例如:
require("<模块名>")
或者
require "<模块名>"
执行 require 后会返回一个由模块常量或函数组成的 table,并且还会定义一个包含该 table 的全局变量。
test_module.lua 文件
*-- test_module.lua 文件*
*-- module 模块为上文提到到 module.lua*
require("module")
print(module.constant)
module.func3()
加载机制
对于自定义的模块,模块文件不是放在哪个文件目录都行,函数 require 有它自己的文件路径加载策略,它会尝试从 Lua 文件或 C 程序库中加载模块。
require 用于搜索 Lua 文件的路径是存放在全局变量 package.path 中,当 Lua 启动后,会以环境变量 LUA_PATH 的值来初始这个环境变量。如果没有找到该环境变量,则使用一个编译时定义的默认路径来初始化。
当然,如果没有 LUA_PATH 这个环境变量,也可以自定义设置,在当前用户根目录下打开 .profile 文件(没有则创建,打开 .bashrc 文件也可以),例如把 "~/lua/" 路径加入 LUA_PATH 环境变量里:
#LUA_PATH
export LUA_PATH="~/lua/?.lua;;"
文件路径以 ";" 号分隔,最后的 2 个 ";;" 表示新加的路径后面加上原来的默认路径。
接着,更新环境变量参数,使之立即生效。
source ~/.profile
这时假设 package.path 的值是:
/Users/dengjoe/lua/?.lua;./?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;/usr/local/lib/lua/5.1/?.lua;/usr/local/lib/lua/5.1/?/init.lua
那么调用 require("module") 时就会尝试打开以下文件目录去搜索目标。
/Users/dengjoe/lua/module.lua;
./module.lua
/usr/local/share/lua/5.1/module.lua
/usr/local/share/lua/5.1/module/init.lua
/usr/local/lib/lua/5.1/module.lua
/usr/local/lib/lua/5.1/module/init.lua
如果找过目标文件,则会调用 package.loadfile 来加载模块。否则,就会去找 C 程序库。
搜索的文件路径是从全局变量 package.cpath 获取,而这个变量则是通过环境变量 LUA_CPATH 来初始。
搜索的策略跟上面的一样,只不过现在换成搜索的是 so 或 dll 类型的文件。如果找得到,那么 require 就会通过 package.loadlib 来加载它。
C 包
Lua和C是很容易结合的,使用 C 为 Lua 写包。
与Lua中写包不同,C包在使用以前必须首先加载并连接,在大多数系统中最容易的实现方式是通过动态连接库机制。
Lua在一个叫loadlib的函数内提供了所有的动态连接的功能。这个函数有两个参数:库的绝对路径和初始化函数。所以典型的调用的例子如下:
local path = "/usr/local/lua/lib/libluasocket.so" local f = loadlib(path, "luaopen_socket")
loadlib 函数加载指定的库并且连接到 Lua,然而它并不打开库(也就是说没有调用初始化函数),反之他返回初始化函数作为 Lua 的一个函数,这样我们就可以直接在Lua中调用他。
如果加载动态库或者查找初始化函数时出错,loadlib 将返回 nil 和错误信息。我们可以修改前面一段代码,使其检测错误然后调用初始化函数:
local path = "/usr/local/lua/lib/libluasocket.so" -- 或者 path = "C:\windows\luasocket.dll",这是 Window 平台下 local f = assert(loadlib(path, "luaopen_socket")) f() -- 真正打开库
一般情况下我们期望二进制的发布库包含一个与前面代码段相似的 stub 文件,安装二进制库的时候可以随便放在某个目录,只需要修改 stub 文件对应二进制库的实际路径即可。
将 stub 文件所在的目录加入到 LUA_PATH,这样设定后就可以使用 require 函数加载 C 库了。
Lua 元表(Metatable)
在 Lua table 中我们可以访问对应的 key 来得到 value 值,但是却无法对两个 table 进行操作(比如相加)。
因此 Lua 提供了元表(Metatable),允许我们改变 table 的行为,每个行为关联了对应的元方法。
例如,使用元表我们可以定义 Lua 如何计算两个 table 的相加操作 a+b。
当 Lua 试图对两个表进行相加时,先检查两者之一是否有元表,之后检查是否有一个叫 add 的字段,若找到,则调用对应的值。 add 等即时字段,其对应的值(往往是一个函数或是 table)就是"元方法"。
有两个很重要的函数来处理元表:
setmetatable(table,metatable): 对指定 table 设置元表(metatable),如果元表(metatable)中存在 __metatable 键值,setmetatable 会失败。
getmetatable(table): 返回对象的元表(metatable)。
以下实例演示了如何对指定的表设置元表:
mytable = {} -- 普通表
mymetatable = {} -- 元表
setmetatable(mytable,mymetatable)
--把 mymetatable 设为 mytable 的元表
以上代码也可以直接写成一行:
mytable = setmetatable({},{})
以下为返回对象元表:
getmetatable(mytable) -- 这会返回 mymetatable
__index 元方法
Lua 元表(Metatable) | 菜鸟教程 (runoob.com)
这是 metatable 最常用的键。
当你通过键来访问 table 的时候,如果这个键没有值,那么Lua就会寻找该table的metatable(假定有metatable)中的index 键。如果index包含一个表格,Lua会在表格中查找相应的键。
我们可以在使用 lua 命令进入交互模式查看:
$ lua
Lua 5.3.0 Copyright (C) 1994-2015 Lua.org, PUC-Rio
\> other = { foo = 3 }
\> t = setmetatable({}, { __index = other })
\> t.foo
3
\> t.bar
nil
如果__index包含一个函数的话,Lua就会调用那个函数,table和键会作为参数传递给函数。
index 元方法查看表中元素是否存在,如果不存在,返回结果为 nil;如果存在则由 index 返回结果。
实例
mytable = setmetatable({key1 = "value1"}, {
__index = function(mytable, key)
if key == "key2" then
return "metatablevalue"
else
return nil
end
end
})
print(mytable.key1,mytable.key2)
实例输出结果为:
value1 metatablevalue
实例解析:
mytable 表赋值为 {key1 = "value1"}。
mytable 设置了元表,元方法为 __index。
在mytable表中查找 key1,如果找到,返回该元素,找不到则继续。
在mytable表中查找 key2,如果找到,返回 metatablevalue,找不到则继续。
判断元表有没有index方法,如果index方法是一个函数,则调用该函数。
元方法中查看是否传入 "key2" 键的参数(mytable.key2已设置),如果传入 "key2" 参数返回 "metatablevalue",否则返回 mytable 对应的键值。
我们可以将以上代码简单写成:
mytable = setmetatable({key1 = "value1"}, { __index = { key2 = "metatablevalue" } })
print(mytable.key1,mytable.key2)
总结
Lua 查找一个表元素时的规则,其实就是如下 3 个步骤:
1.在表中查找,如果找到,返回该元素,找不到则继续
2.判断该表是否有元表,如果没有元表,返回 nil,有元表则继续。
3.判断元表有没有 index 方法,如果 index 方法为 nil,则返回 nil;如果 index 方法是一个表,则重复 1、2、3;如果 index 方法是一个函数,则返回该函数的返回值。
__newindex 元方法
newindex 元方法用来对表更新,index则用来对表访问 。
当你给表的一个缺少的索引赋值,解释器就会查找__newindex 元方法:如果存在则调用这个函数而不进行赋值操作。
add 键包含在元表中,并进行相加操作。 表中对应的操作列表如下:(*注意:是两个下划线)
__call 元方法
__call 元方法在 Lua 调用一个值时调用。以下实例演示了计算表中元素的和
__tostring 元方法
__tostring 元方法用于修改表的输出行为。以下实例我们自定义了表的输出内容:
lua继承/封装实例
girl={money=200}
function girl.goToMarket(girl,someMoney)
girl.money=girl.money-someMoney
end
girl.goToMarket(girl,100)
print(girl.money)
boy={money=200}
function boy:goToMarket(someMoney)
self.money=self.money-someMoney
end
boy:goToMarket(100)
print(boy.money)
LUA Callback函数理解
local m={}
local list={name="hello",age=12}
m.call_back_fun_print=function(printID)
print(printID.."->"..list[printID])
end
m.add_list=function(key,value,call_back_fun)
key=tostring(key)
list[key]=value
call_back_fun(key)
end
m.add_list("address","beijing",m.call_back_fun_print)
return m
--[[
address->beijing
回调函数就就是把函数当参数输入直接用了就这
]]
Lua 协同程序(coroutine)
Lua 协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。
协同程序可以理解为一种特殊的线程,可以暂停和恢复其执行,从而允许非抢占式的多任务处理。
协同是非常强大的功能,但是用起来也很复杂。
基本语法
同程序由 coroutine 模块提供支持。
使用协同程序,你可以在函数中使用 coroutine.create 创建一个新的协同程序对象,并使用 coroutine.resume 启动它的执行。协同程序可以通过调用 coroutine.yield 来主动暂停自己的执行,并将控制权交还给调用者。
function foo()
print("协同程序 foo 开始执行")
local value=coroutine.yield("暂停 foo 的执行")
print("协同程序 foo 恢复执行,传入的值为: " .. tostring(value))
print("协同程序 foo 结束执行")
end
--创建协同程序
local co=coroutine.create(foo)
--启动协同程序
local status,result=coroutine.resume(co)
print(result) --输出暂停 foo 的执行
--协同程序 foo 恢复执行,传入的值为: 42
status,result=coroutine.resume(co,42)
print(result)
以上实例中,我们定义了一个名为 foo 的函数作为协同程序。在函数中,我们使用 coroutine.yield 暂停了协同程序的执行,并返回了一个值
。在主程序中,我们使用 coroutine.create 创建了一个协同程序对象,并使用 coroutine.resume 启动了它的执行。
在第一次调用 coroutine.resume 后,协同程序执行到 coroutine.yield 处暂停,并将值返回给主程序。然后,我们再次调用 coroutine.resume,并传入一个值作为协同程序恢复执行时的参数。
需要注意的是,协同程序的状态可以通过 coroutine.status 函数获取,通过检查状态可以确定协同程序的执行情况(如运行中、已挂起、已结束等)。
线程和协同程序区别
线程与协同程序的主要区别在于,一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行。
在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只有在明确的被要求挂起的时候才会被挂起。
协同程序有点类似同步的多线程,在等待同一个线程锁的几个线程有点类似协同。
主要区别归纳如下:
调度方式:线程通常由操作系统的调度器进行抢占式调度,操作系统会在不同线程之间切换执行权。而协同程序是非抢占式调度的,它们由程序员显式地控制执行权的转移。
并发性:线程是并发执行的,多个线程可以同时运行在多个处理器核心上,或者通过时间片轮转在单个核心上切换执行。协同程序则是协作式的,只有一个协同程序处于运行状态,其他协同程序必须等待当前协同程序主动放弃执行权。
内存占用:线程通常需要独立的堆栈和上下文环境,因此线程的创建和销毁会带来额外的开销。而协同程序可以共享相同的堆栈和上下文,因此创建和销毁协同程序的开销较小。
数据共享:线程之间可以共享内存空间,但需要注意线程安全性和同步问题。协同程序通常通过参数传递和返回值来进行数据共享,不同协同程序之间的数据隔离性较好。
调试和错误处理:线程通常在调试和错误处理方面更复杂,因为多个线程之间的交互和并发执行可能导致难以调试的问题。协同程序则在调试和错误处理方面相对简单,因为它们是由程序员显式地控制执行流程的。
总体而言,线程适用于需要并发执行的场景,例如在多核处理器上利用并行性加快任务的执行速度。而协同程序适用于需要协作和协调的场景,例如状态机、事件驱动编程或协作式任务处理。选择使用线程还是协同程序取决于具体的应用需求和编程模型。
Lua 文件 I/O
Lua I/O 库用于读取和处理文件。分为简单模式(和C一样)、完全模式。
简单模式(simple model)拥有一个当前输入文件和一个当前输出文件,并且提供针对这些文件相关的操作。
完全模式(complete model) 使用外部的文件句柄来实现。它以一种面对对象的形式,将所有的文件操作定义为文件句柄的方法
简单模式在做一些简单的文件操作时较为合适。但是在进行一些高级的文件操作的时候,简单模式就显得力不从心。例如同时读取多个文件这样的操作,使用完全模式则较为合适。
打开文件操作语句如下:
file = io.open (filename [, mode])
mode 的值有:
简单模式
简单模式使用标准的 I/O 或使用一个当前输入文件和一个当前输出文件。
以下为 file.lua 文件代码,操作的文件为test.lua(如果没有你需要创建该文件),代码如下
-- 以只读方式打开文件
file = io.open("test.lua", "r")
-- 设置默认输入文件为 test.lua
io.input(file)
-- 输出文件第一行
print(io.read())
-- 关闭打开的文件
io.close(file)
-- 以附加的方式打开只写文件
file = io.open("test.lua", "a")
-- 设置默认输出文件为 test.lua
io.output(file)
-- 在文件最后一行添加 Lua 注释
io.write("-- test.lua 文件末尾注释")
-- 关闭打开的文件
io.close(file)
执行以上代码,你会发现,输出了 test.lua 文件的第一行信息,并在该文件最后一行添加了 lua 的注释。如我这边输出的是:
-- test.lua 文件
在以上实例中我们使用了 io."x" 方法,其中 io.read() 中我们没有带参数,参数可以是下表中的一个:
其他的 io 方法有:
io.tmpfile():返回一个临时文件句柄,该文件以更新模式打开,程序结束时自动删除
io.type(file): 检测obj是否一个可用的文件句柄
io.flush(): 向文件写入缓冲中的所有数据
io.lines(optional file name): 返回一个迭代函数,每次调用将获得文件中的一行内容,当到文件尾时,将返回 nil,但不关闭文件。
完全模式
通常我们需要在同一时间处理多个文件。我们需要使用 file:function_name 来代替 io.function_name 方法。以下实例演示了如何同时处理同一个文件:
read 的参数与简单模式一致。
其他方法:
file:seek(optional whence, optional offset): 设置和获取当前文件位置,成功则返回最终的文件位置(按字节),失败则返回nil加错误信息。参数 whence 值可以是:
"set": 从文件头开始
"cur": 从当前位置开始[默认]
"end": 从文件尾开始
offset:默认为0
不带参数file:seek()则返回当前位置,file:seek("set")则定位到文件头,file:seek("end")则定位到文件尾并返回文件大小
file:flush(): 向文件写入缓冲中的所有数据
io.lines(optional file name): 打开指定的文件 filename 为读模式并返回一个迭代函数,每次调用将获得文件中的一行内容,当到文件尾时,将返回 nil,并自动关闭文件。 若不带参数时io.lines() <=> io.input():lines(); 读取默认输入设备的内容,但结束时不关闭文件,如:
for line in io.lines("main.lua") do print(line) end
实例
-- 以只读方式打开文件
file = io.open("test.lua", "r")
-- 输出文件第一行
print(file:read())
-- 关闭打开的文件
file:close()
-- 以附加的方式打开只写文件
file = io.open("test.lua", "a")
-- 在文件最后一行添加 Lua 注释
file:write("--test")
-- 关闭打开的文件
file:close()
错误处理
我们可以使用两个函数:assert 和 error 来处理错误。实例如下:
error (message [, level])
pcall 和 xpcall、debug
Lua中处理错误,可以使用函数pcall(protected call)来包装需要执行的代码。
pcall接收一个函数和要传递给后者的参数,并执行,执行结果:有错误、无错误;返回值true或者或false, errorinfo。
语法格式如下
if pcall(function_name, ….) then
-- 没有错误
else
-- 一些错误
end
pcall以一种"保护模式"来调用第一个参数,因此pcall可以捕获函数执行中的任何错误。
通常在错误发生时,希望落得更多的调试信息,而不只是发生错误的位置。但pcall返回时,它已经销毁了调用桟的部分内容。
Lua提供了xpcall函数,xpcall接收第二个参数——一个错误处理函数,当错误发生时,Lua会在调用桟展开(unwind)前调用错误处理函数,于是就可以在这个函数中使用debug库来获取关于错误的额外信息了。
debug库提供了两个通用的错误处理函数:
debug.debug:提供一个Lua提示符,让用户来检查错误的原因
debug.traceback:根据调用桟来构建一个扩展的错误消息
lua 中 . 和 : 的区别
首先在lua中使用“:”定义的函数会自动传入一个名为self的变量,这个变量是隐含的,self同c++中的this一样,表示当前对象的指针:而“.”定义的函数中没有self。
function class:func2( ) end
function class.func1(self) end
--这时候这两个函数等价
function MainScene:ctor()
self:ceshi(1,2,3)
end
function MainScene:ceshi(a,b,c)
print(a,b,c)
end
输出:1 2 3
--分析:默认传self,默认接受self。
function MainScene:ctor()
self:ceshi(1,2,3)
end
function MainScene.ceshi(a,b,c) --注意是.
print(a,b,c)
end
输出: nil 1 2
分析:调用ceshi函数时用的:默认传递self为第一个参数,但是函数声明的时候用的 . 所以不会有默认隐藏的self去接收,此时传递参数为 self 1 2 3 但是ceshi函数只接收3个参数所以3被抛弃。
function MainScene:ctor()
self.ceshi(1,2,3) --注意是.
end
function MainScene:ceshi(a,b,c)
print(self)
print(a,b,c)
end
输出:1
2 3 nil
函数调用时用的 . 不会传递self 只传递1 2 3,而ceshi函数声明时用的 : 默认有一个self在第一位去接收,ceshi函数能接收4个参数,即 self a b c ,所以c没有接收参数为nil。
在lua中模拟面向对象编程中,可以用 . 来表示类方法,而 : 可以用来表示成员方法。
Lua 垃圾回收
Lua 采用了自动内存管理。 这意味着你不用操心新创建的对象需要的内存如何分配出来, 也不用考虑在对象不再被使用后怎样释放它们所占用的内存。
Lua 运行了一个垃圾收集器来收集所有死对象 (即在 Lua 中不可能再访问到的对象)来完成自动内存管理的工作。 Lua 中所有用到的内存,如:字符串、表、用户数据、函数、线程、 内部结构等,都服从自动管理。
Lua 实现了一个增量标记-扫描收集器。 它使用这两个数字来控制垃圾收集循环: 垃圾收集器间歇率和垃圾收集器步进倍率。 这两个数字都使用百分数为单位 (例如:值 100 在内部表示 1 )。
垃圾收集器间歇率控制着收集器需要在开启新的循环前要等待多久。 增大这个值会减少收集器的积极性。 当这个值比 100 小的时候,收集器在开启新的循环前不会有等待。 设置这个值为 200 就会让收集器等到总内存使用量达到 之前的两倍时才开始新的循环。
垃圾收集器步进倍率控制着收集器运作速度相对于内存分配速度的倍率。 增大这个值不仅会让收集器更加积极,还会增加每个增量步骤的长度。 不要把这个值设得小于 100 , 那样的话收集器就工作的太慢了以至于永远都干不完一个循环。 默认值是 200 ,这表示收集器以内存分配的"两倍"速工作。
如果你把步进倍率设为一个非常大的数字 (比你的程序可能用到的字节数还大 10% ), 收集器的行为就像一个 stop-the-world 收集器。 接着你若把间歇率设为 200 , 收集器的行为就和过去的 Lua 版本一样了: 每次 Lua 使用的内存翻倍时,就做一次完整的收集。
垃圾回收器函数
Lua 提供了以下函数collectgarbage ([opt [, arg]])用来控制自动内存管理:
collectgarbage("collect"): 做一次完整的垃圾收集循环。通过参数 opt 它提供了一组不同的功能:
collectgarbage("count"): 以 K 字节数为单位返回 Lua 使用的总内存数。 这个值有小数部分,所以只需要乘上 1024 就能得到 Lua 使用的准确字节数(除非溢出)。
collectgarbage("restart"): 重启垃圾收集器的自动运行。
collectgarbage("setpause"): 将 arg 设为收集器的 间歇率。 返回 间歇率 的前一个值。
collectgarbage("setstepmul"): 返回 步进倍率 的前一个值。
collectgarbage("step"): 单步运行垃圾收集器。 步长"大小"由 arg 控制。 传入 0 时,收集器步进(不可分割的)一步。 传入非 0 值, 收集器收集相当于 Lua 分配这些多(K 字节)内存的工作。 如果收集器结束一个循环将返回 true 。
collectgarbage("stop"): 停止垃圾收集器的运行。 在调用重启前,收集器只会因显式的调用运行。
AssetBundle
AB:用于特定平台的资产压缩包,有点类似压缩
相对Resources下的资源AB更好管理资源
1.减小包体大小 压缩资源,减少初始包的大小
2热更新.,资源和脚本的更新
tolua是Unity静态绑定lua的一个解决方案,它通过C#提供的反射信息分析代码并生成包装的类(Wrap.cs文件)。 它是一个用来简化在C#中集成Lua的插件,可以自动生成用于在Lua中访问Unity的绑定代码,并把C#中的常量、变量、函数、属性、类以及枚举暴露给Lua。 它是从cstolua衍变而来。从它的名字可以看出,它是集成了原来的ToLua代码通过二次封装写了一个C#与tolua C 的一个中间层。
创建虚拟机——绑定数据——调用Lua代码
【Unity游戏开发】tolua之wrap文件的原理与使用 - 马三小伙儿 - 博客园 (cnblogs.com)
热更新方案有哪些?以及具体热更流程
1、整包:存放在上SteamingAssets里 ——策略:完整更新资源放在包里 ——优点:首次更新少 ——缺点:安装包下载时间长,首次安装久 2、分包 ——策略:少部分资源放在包里,大部分更新资源存放在更新资源器中 ——优点:安装包小,安装时间短,下载快 ——缺点:首次更新下载解压缩包时间旧 3、适用性 ——海外游戏大部分是使用分包策略,平台规定 ——国内游戏大部分是使用整包策略 4、文件可读写路径 ——Application.streamingAssestsPath 只读目录 ——Application.persistentDatapath 可读写目录 ——资源服务器地址URL 5、【从资源服务器】下载单个文件或多个文件 ——NetWorking.UnityWebRequest获取URL , HTTP GET , 连接资源服务器 ——获取到downloadHander的文件数据Data,完成后会回调方法,将文件Data作为参数传出 6、检查是否初次安装
如何解析版本文件?如何加载AB包资源?具体流程是怎么样的?
1.解析版本文件列表 ——File.ReadAllLines(读取文件列表资源路径URL) ——获取资源名称,获取AB包名称,获取依赖项,字典容器存储 ——获取Lua文件 2.加载资源 ——异步加载资源AB包,AssetBundleRequest请求,AssetBundle.LoadFromFileAsync ——先检查依赖项,再异步加载AB包依赖项 ——加载成功后都有对应的回调方法,将资源作为参数传入
资源如何打包?依赖项列表如何生成?
1.查找指定文件夹ABResource里的资源文件 ——Directory.GetFile(资源路径) ——新建AssetBundleBuild对象 ——获取资源名称,并赋值对应AB名称 ——获取各个资源的依赖项:通过UnityEditor.AssetDataBase类获取各个资源的依赖项 2.使用Unity自带的BuildPipeline进行构建AB包 ——BuildPipeLine.BuildAssetBundles(输出AB包路径) ——File.WriteAllLines(将依赖项写入文件里)
补充
lua引用dkjson库的方法_lua dkjson-CSDN博客
动态类型语言
1、编译型语言 c++/c
需通过编译器(compiler)将源代码编译成机器码,之后才能执行的语言。一般需经过编译(compile)、链接(linker)这两个步骤。编译是把源代码编译成机器码,链接是把各个模块的机器码和依赖库串连起来生成可执行文件。
优点:编译器一般会有预编译的过程对代码进行优化。因为编译只做一次,运行时不需要编译,所以编译型语言的程序执行效率高。可以脱离语言环境独立运行。
缺点:编译之后如果需要修改就需要整个模块重新编译。编译的时候根据对应的运行环境生成机器码,不同的操作系统之间移植就会有问题,需要根据运行的操作系统环境编译不同的可执行文件。
2、解释型语言 JavaScript,Python
解释性语言的程序不需要编译,相比编译型语言省了道工序,解释性语言在运行程序的时候才逐行翻译。
优点:有良好的平台兼容性,在任何环境中都可以运行,前提是安装了解释器(虚拟机)。灵活,修改代码的时候直接修改就可以,可以快速部署,不用停机维护。
缺点:每次运行的时候都要解释一遍,性能上不如编译型语言。
3、混合型语言
既然编译型和解释型各有缺点就会有人想到把两种类型整合起来,取其精华去其糟粕。就出现了半编译型语言。比如 C#,C# 在编译的时候不是直接编译成机器码而是中间码,.NET 平台提供了中间语言运行库运行中间码,中间语言运行库类似于 Java 虚拟机。.net 在编译成 IL 代码后,保存在 dll 中,首次运行时由 JIT 在编译成机器码缓存在内存中,下次直接执行(博友回复指出)。我个人认为抛开一切的偏见 C# 是这个星球上最好的编程语言。可惜微软的政策限制了 C# 的推广。
Java 先生成字节码再在 Java 虚拟机中解释执行。
严格来说混合型语言属于解释型语言。C# 更接近编译型语言。