lua是动态语言类型,变量不需要类型定义,只需要为变量赋值
lua有8个基本类型:
数据类型 | 描述 |
---|---|
nil | 这个最简单,只有值nil属于该类,表示一个无效值(在条件表达式中相当于false)。 |
boolean | 包含两个值:false和true。 |
number | 表示双精度类型的实浮点数 |
string | 字符串由一对双引号或单引号来表示 |
function | 由 C 或 Lua 编写的函数 |
userdata | 表示任意存储在变量中的C数据结构 |
thread | 表示执行的独立线路,用于执行协同程序 |
table | Lua 中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字、字符串或表类型。在 Lua 里,table 的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。 |
表示没有任何有效值,例如打印一个没有赋值的变量,便会输出一个 nil 值
对于全局变量和 table,nil 还有一个"删除"作用,给全局变量或者 table 表里的变量赋一个 nil 值,等同于把它们删掉
type(X) 实质是返回的 “nil” 字符串,是一个 string 类型,所以nil作比较时应该加上双引号 "
> type(X)
nil
> type(X)==nil
false
> type(X)=="nil"
true
boolean 类型只有两个可选值:true(真) 和 false(假),Lua 把 false 和 nil 看作是 false,其他的都为 true
Lua 默认只有一种 number 类型 – double
字符串由一对双引号或单引号来表示。也可以用 2 个方括号 “[[]]” 来表示"一块"字符串。
在对一个数字字符串上进行算术操作时,Lua 会尝试将这个数字字符串转成一个数字,字符串连接使用的是 …
使用 # 来计算字符串的长度,放在字符串前面
> print("2 + 6")
2 + 6
> print(157 .. 428)
157428> len = "www.runoob.com"
> print(#len)
14
> print(#"www.runoob.com")
14
在 Lua 里,table 的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。也可以在表里添加一些数据
其实是一个"关联数组"(associative arrays),数组的索引可以是数字或者是字符串。
a = {}
a["key"] = "value"
key = 10
a[key] = 22
for k, v in pairs(a) doprint(k .. " : " .. v)
end
--[[执行结果为
key : value
10 : 22
]]--
在 Lua 里表的默认初始索引一般以 1 开始。
在 Lua 中,函数是被看作是"第一类值(First-Class Value)",函数可以存在变量里:
function factorial1(n)if n == 0 thenreturn 1elsereturn n * factorial1(n - 1)end
end
print(factorial1(5))
factorial2 = factorial1
print(factorial2(5))
function 可以以匿名函数的方式通过参数传递:
在 Lua 里,最主要的线程是协同程序(coroutine)。它跟线程(thread)差不多,拥有自己独立的栈、局部变量和指令指针,可以跟其他协同程序共享全局变量和其他大部分东西。
线程跟协程的区别:线程可以同时多个运行,而协程任意时刻只能运行一个,并且处于运行状态的协程只有被挂起(suspend)时才会暂停。
userdata 是一种用户自定义数据,用于表示一种由应用程序或 C/C++ 语言库所创建的类型,可以将任意 C/C++ 的任意数据类型的数据(通常是 struct 和 指针)存储到 Lua 变量中调用。
Lua 变量有三种类型:全局变量、局部变量、表中的域。
Lua 中的变量默认为全局变量且默认值均为 nil,哪怕是语句块或是函数里,除非用 local 显式声明为局部变量。
Lua 可以对多个变量同时赋值,变量列表和值列表的各个元素用逗号分开,赋值语句右边的值会依次赋给左边的变量。
a, b = 10, 2*x --> a=10; b=2*x
遇到赋值语句Lua会先计算右边所有的值然后再执行赋值操作,所以我们可以这样进行交换变量的值:
x, y = y, x -- swap 'x' for 'y'
但变量个数大于值的个数时,会用nil补足;若小于值的个数则多余的值会被忽略
for var=exp1,exp2,exp3 do <执行体>
end
-- var 从 exp1 变化到 exp2,每次变化以 exp3 为步长递增 var,并执行一次 "执行体"。exp3 是可选的,如果不指定,默认为1。
三个表达式都只会在循环开始前执行一次求值,之后不再求值
泛型:
a = {"one", "two", "three"}
for i, v in ipairs(a) doprint(i, v)
end
在lua中pairs与ipairs两个迭代器的用法相近,但有一点是不一样的:
pairs可以遍历表中所有的key,并且除了迭代器本身以及遍历表本身还可以返回nil;
但是ipairs则不能返回nil,只能返回数字0,如果遇到nil则退出。它只能遍历到表中出现的第一个不是整数的key
repeatstatements
until( condition )
条件进行判断前循环体都会执行一次。
如果条件判断为 false,循环会重新开始执行,直到为 true 才会停止执行
if(布尔表达式)
then--[ 在布尔表达式为 true 时执行的语句 --]
else--[ 布尔表达式为 false 时执行该语句块 --]
end
函数定义格式:
optional_function_scope function function_name( argument1, argument2, argument3..., argumentn)function_bodyreturn result_params_comma_separated
end
function maximum (a)local mi = 1 -- 最大值索引local m = a[mi] -- 最大值for i,val in ipairs(a) doif val > m thenmi = im = valendendreturn m, mi
endprint(maximum({8,10,23,12,5}))
--输出23 3
Lua 函数可以接受可变数目的参数,在函数参数列表中使用三点 … 表示函数有可变的参数。
function add(...)
local s = 0 for i, v in ipairs{...} do --> {...} 表示一个由所有变长参数构成的数组 s = s + v end return s
end
print(add(3,4,5,6,7)) --->25
通常在遍历变长参数的时候只需要使用 {…},然而变长参数可能会包含一些 nil,那么就可以用 select 函数来访问变长参数了:select(‘#’, …) 或者 select(n, …)
来获取可变参数的数量:
function test(...)print("总共传入 " .. select("#",...) .. " 个数")
endprint(test(10,5,3,4,5,6))
-- 总共传入 6 个数function f(...)a = select(3,...) -->从第三个位置开始,变量 a 对应右边变量列表的第一个参数print (a)print (select(3,...)) -->打印所有列表参数
endf(0,1,2,3,4,5)
--[[ 输出结果:
2
2 3 4 5--]]
Lua 语言中字符串可以使用以下三种方式来表示:
string1 = "Lua"
print("\"字符串 1 是\"",string1)
string2 = 'runoob.com'
print("字符串 2 是",string2)string3 = [["Lua 教程"]]
print("字符串 3 是",string3)
--[["字符串 1 是" Lua
字符串 2 是 runoob.com
字符串 3 是 "Lua 教程"--]]
方法 | 用途 |
---|---|
string.upper(argument) | 全部转为大写字母 |
string.lower(argument) | 全部转为小写字母 |
string.gsub(mainString,findString,replaceString,num) | mainString 为要操作的字符串, findString 为被替换的字符,replaceString 要替换的字符,num 替换次数(可以忽略,则全部替换) |
string.find (str, substr, [init, [plain]]) | 在一个指定字符串 str 中搜索指定内容 substr,如果找到了匹配子串,就会返回这个子串的起始索引和结束索引,不存在则返回 nil。 |
string.sub(s, i [, j]) | 用于截取字符串, **s:**要截取的字符串。 **i:**截取开始位置。 **j:**截取结束位置,默认为 -1,最后一个字符。 |
string.rep(string, n) | 返回字符串string的n个拷贝,> string.rep(“abcd”,2) 输出abcdabcd |
string.len(arg) | 计算字符串长度 |
Lua 数组的索引键值可以使用整数表示,数组的大小不是固定的。
其逻辑结构是线性表。一维数组可以用for循环出数组中的元素
array = {"Lua", "Tutorial"}for i= 0, 2 doprint(array[i])
end--以上代码执行输出结果为:nil -- 因为lua下标从1开始,所以0下标元素为nil
Lua
Tutorial
我们还能指定负数为索引
array = {}for i= -2, 2 doarray[i] = i *2
endfor i = -2,2 doprint(array[i])
end
一维数组的索引键对应一个数组
-- 初始化数组
array = {}
for i=1,3 doarray[i] = {}for j=1,3 doarray[i][j] = i*jend
end
Lua table 使用关联型数组,你可以用任意类型的值来作数组的索引,但这个值不能是 nil。
Lua table 是不固定大小的,你可以根据自己需要进行扩容。
-- 初始化表
mytable = {}-- 指定值
mytable[1]= "Lua"-- 移除引用
mytable = nil
-- lua 垃圾回收会释放内存
方法 | 用途 |
---|---|
table.insert (table, [pos,] value) | 在table的数组部分指定位置(pos)插入值为value的一个元素. pos参数可选, 默认为数组部分末尾. |
table.remove (table [, pos]) | 返回table数组部分位于pos位置的元素. 其后的元素会被前移. pos参数可选, 默认为table长度, 即从最后一个元素删起。 |
table.sort (table [, comp]) | 对给定的table进行升序排序。 |
table.concat (table [, sep [, start [, end]]]): | concat是concatenate(连锁, 连接)的缩写. table.concat()函数列出参数中指定table的数组部分从start位置到end位置的所有元素, 元素间以指定的分隔符(sep)隔开。 |
当我们获取 table 的长度的时候无论是使用 # 还是 table.getn 其都会在索引中断的地方停止计数,而导致无法正确取得 table 的长度。
可以使用以下方法来代替:
function table_leng(t) local leng=0 for k, v in pairs(t) doleng=leng+1 end return leng; end
Lua无法对两个table进行操作(比如相加),因此lua提供了metatable,允许我们改变table的行为。
任何表变量都可以作为另一个表变量的元表,当我们在表中进行一些特定操作的时候会执行元表当中的内容
当 Lua 试图对两个表进行相加时,先检查两者之一是否有元表,之后检查是否有一个叫 __add 的字段,若找到,则调用对应的值。 __add 等即时字段,其对应的值(往往是一个函数或是 table)就是"元方法"。
有两个重要函数来处理元表:
代码示例:
myTable1 = {} -- 普通表
meta1 = {} -- 元表
setmetatable(myTable1,meta1) -- 把 meta1 设为 myTable1 的元表
getmetatable(myTable1) -- meta1
meta2 = {-- 当要被当做字符串使用时,会默认调用该方法__tostring = function(t)return t.nameend
}
myTable2 ={name = "Name"
}
setmetatable(myTable2, meta2)
print(myTable2)
__call 元方法会在表被当作函数使用的时候调用
meta3 = {-- 注意第一个参数是表本身,类似于 : 调用__call = function(a,b)print(a)print(b)print("__call被调用")end
}
myTable3 = {}
setmetatable(myTable3, meta3)myTable3(1)
--[[输出结果
table: 00A89640
1
__call被调用
--]]
当你通过键来访问 table 的时候,如果这个键没有值,那么Lua就会寻找该table的metatable(假定有metatable)中的index 键。
meta = {__index = {age = 1}
}
-- 注意!如果表找不到某一属性是去 元表的__index 查找
-- 也可以在外面写 meta.__index = {age = 1}myTable = {}
setmetatable(myTable, meta)
print(myTable.age)
总结
Lua 查找一个表元素时的规则,其实就是如下 3 个步骤:
__newindex 元方法用来对表更新,__index则用来对表访问 。
当你给表的一个缺少的索引赋值,解释器就会查找__newindex 元方法:如果存在则将这个值赋值到newindex所指向的表中,不会修改自己
meta = {}
meta.__newindex = {}
myTable = {}
setmetatable(myTable, meta)myTable.age = 1
print(myTable.age)
-- 输出 nil
此外,可以使用 rawset
和 rawget
来设置和获取当前表的变量,而不在元表中获取
print(rawget(myTable, "age")) -- 输出nilrawset(myTable, "age", 2)
print(myTable.age) -- 输出 2print(rawget(myTable, "age")) -- 输出2
模式 | 描述 |
---|---|
__add | 对应的运算符 ‘+’. |
__sub | 对应的运算符 ‘-’. |
__mul | 对应的运算符 ‘*’. |
__div | 对应的运算符 ‘/’. |
__mod | 对应的运算符 ‘%’. |
__unm | 对应的运算符 ‘-’. |
__concat | 对应的运算符 ‘…’. |
__eq | 对应的运算符 ‘==’. |
__lt | 对应的运算符 ‘<’. |
__le | 对应的运算符 ‘<=’. |
meta = {__add = function(t1,t2)return t1.age + t2.ageend, -- 注意用 , 隔开 __sub = function(t1,t2)return t1.age - t2.ageend
}myTable1 = {age = 1}
setmetatable(myTable1, meta)
myTable2 = {age = 2}
setmetatable(myTable2, meta)
注意如果要重载比较预算符的话,要两个表的元表都相同才是能做比较
可以把一些公用的代码放在一个文件里,以 API 接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度。
Lua 的模块是由变量、函数等已知元素组成的 table,因此创建一个模块很简单,就是创建一个 table,然后把需要导出的常量、函数放入其中,最后返回这个 table 就行。
创建自定义模块 module.lua
-- 文件名为 module.lua
-- 定义一个名为 module 的模块
module = {}-- 定义一个常量
module.constant = "这是一个常量"-- 定义一个函数
function module.func1()io.write("这是一个公有函数!\n")
endlocal function func2()print("这是一个私有函数!")
endfunction module.func3()func2()
endreturn module
func2 声明为程序块的局部变量,即表示一个私有函数
Lua提供了一个名为require的函数用来加载模块。 require("<模块名>")
可以直接使用module.constant
,也可以使用别名
-- test_module2.lua 文件
-- module 模块为上文提到到 module.lua
-- 别名变量 m
local m = require("module")print(m.constant)m.func3()
对于自定义的模块,模块文件不是放在哪个文件目录都行,函数 require 有它自己的文件路径加载策略,它会尝试从 Lua 文件或 C 程序库中加载模块。
Lua和C是很容易结合的,与Lua中写包不同,C包在使用以前必须首先加载并连接,在大多数系统中最容易的实现方式是通过动态连接库机制。
loadlib 函数加载指定的库并且连接到 Lua,然而它并不打开库(也就是说没有调用初始化函数),反之他返回初始化函数作为 Lua 的一个函数,这样我们就可以直接在Lua中调用他。
local path = "/usr/local/lua/lib/libluasocket.so"
-- 或者 path = "C:\\windows\\luasocket.dll",这是 Window 平台下
local f = assert(loadlib(path, "luaopen_socket"))
f() -- 真正打开库
Lua 协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。
线程与协同程序的主要区别在于,一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行。
在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只有在明确的被要求挂起的时候才会被挂起。
协同程序有点类似同步的多线程,在等待同一个线程锁的几个线程有点类似协同。
方法 | 描述 |
---|---|
coroutine.create() | 创建 coroutine,返回 coroutine, 参数是一个函数,当和 resume 配合使用的时候就唤醒函数调用 |
coroutine.resume() | 重启 coroutine,和 create 配合使用 |
coroutine.yield() | 挂起 coroutine,将 coroutine 设置为挂起状态,这个和 resume 配合使用能有很多有用的效果 |
coroutine.status() | 查看 coroutine 的状态 注:coroutine 的状态有三种:dead,suspended,running |
coroutine.wrap() | 创建 coroutine,返回一个函数,一旦你调用这个函数,就进入 coroutine,和 create 功能重复 |
coroutine.running() | 返回正在跑的 coroutine,一个 coroutine 就是一个线程,当使用running的时候,就是返回一个 corouting 的线程号 |
coroutine.running就可以看出来,coroutine在底层实现就是一个线程。
当create一个coroutine的时候就是在新线程中注册了一个事件。
当使用resume触发事件的时候,create的coroutine函数就被执行了,当遇到yield的时候就代表挂起当前线程,等候再次resume触发事件。
co = coroutine.create(function (a)local r = coroutine.yield(a+1) -- yield()返回a+1给调用它的resume()函数,即2print("r=" ..r) -- r的值是第2次resume()传进来的,100
end)
status, r = coroutine.resume(co, 1) -- resume()返回两个值,一个是自身的状态true,一个是yield的返回值2
coroutine.resume(co, 100) --resume()返回true------------输出结果
r=100 --只会打印一次
调用resume,将协同程序唤醒,resume操作成功返回true,否则返回false;
resume处于主程中,它将外部状态(数据)传入到协同程序内部;而yield则将内部的状态(数据)返回到主程中。
更详细的一个实例
function foo (a)print("foo 函数输出", a)return coroutine.yield(2 * a) -- 返回 2*a 的值
endco = coroutine.create(function (a , b)print("第一次协同程序执行输出", a, b) -- co-body 1 10local r = foo(a + 1)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, "r")) -- true 11 -9
print("---分割线---")
print("main", coroutine.resume(co, "x", "y")) -- true 10 end
print("---分割线---")
print("main", coroutine.resume(co, "x", "y")) -- cannot resume dead coroutine
print("---分割线---")
输出结果
第一次协同程序执行输出 1 10
foo 函数输出 2
main true 4
--分割线----
第二次协同程序执行输出 r
main true 11 -9
---分割线---
第三次协同程序执行输出 x y
main true 10 结束协同程序
---分割线---
main false cannot resume dead coroutine
---分割线---
迭代器(iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址。
t = {[0] = 1, 2, [-1] = 3, 4, 5, [5] = 6}-- ipairs遍历
-- ipairs遍历还是从1开始往后遍历, <= 0的值都无法遍历,而且如果不连续中断,也无法遍历后面的值
for k, v in ipairs(t) doprint("ipairs遍历"..k.."-"..v)
end
-- 只能得到 2,4,5-- pairs遍历
-- 能够把所有的键都找到,通过键也就能得到值
for k, v in pairs(t) doprint("ipairs遍历"..k.."-"..v)
end
上面代码中,i,k, v分别为变量列表的索引、键、值;pairs(t)为表达式列表。
在Lua中我们常常使用函数来描述迭代器,每次调用该函数就返回集合的下一个元素。Lua 的迭代器包含以下两种类型:
无状态的迭代器是指不保留任何状态的迭代器,因此在循环中我们可以利用无状态迭代器避免创建闭包花费额外的代价。
每一次迭代,迭代函数都是用两个变量(状态常量和控制变量)的值作为参数被调用,一个无状态的迭代器只利用这两个值可以获取下一个元素。
这种无状态迭代器的典型的简单的例子是 ipairs,它遍历数组的每一个元素,元素的索引需要是数值。
-- 函数定义
function square(iteratorMaxCount,currentNumber)if currentNumber
迭代的状态包括被遍历的表(循环过程中不会改变的状态常量)和当前的索引下标(控制变量)
function iter (a, i)i = i + 1local v = a[i]if v thenreturn i, vend
endfunction ipairs (a)return iter, a, 0
end
当 Lua 调用 ipairs(a) 开始循环时,他获取三个值:迭代函数 iter、状态常量 a、控制变量初始值 0;
然后 Lua 调用 iter(a,0) 返回 1, a[1](除非 a[1]=nil);
第二次迭代调用 iter(a,1) 返回 2, a[2]……直到第一个 nil 元素。
迭代器需要保存多个状态信息而不是简单的状态常量和控制变量
一种方法就是将所有的状态信息封装到 table 内,将 table 作为迭代器的状态常量,因为这种情况下可以将所有的信息存放在 table 内,所以迭代函数通常不需要第二个参数。
array = {"Google", "Runoob"}function elementIterator (collection)local index = 0local count = #collection-- 闭包函数return function ()index = index + 1if index <= countthen-- 返回迭代器的当前元素return collection[index]endend
endfor element in elementIterator(array)
doprint(element)
end--输出
Google
Runoob
Lua 采用了自动内存管理。 这意味着你不用操心新创建的对象需要的内存如何分配出来, 也不用考虑在对象不再被使用后怎样释放它们所占用的内存。
Lua 运行了一个垃圾收集器来收集所有死对象 (即在 Lua 中不可能再访问到的对象)来完成自动内存管理的工作。 Lua 中所有用到的内存,都服从自动管理。
Lua 实现了一个增量标记-扫描收集器。 它使用这两个数字来控制垃圾收集循环: 垃圾收集器间歇率和垃圾收集器步进倍率。 这两个数字都使用百分数为单位 (例如:值 100 在内部表示 1 )。
**collectgarbage ([opt [, arg]])**用来控制自动内存管理
collectgarbage(“collect”): 做一次完整的垃圾收集循环。通过参数 opt 它提供了一组不同的功能:
collectgarbage(“count”): 以 K 字节数为单位返回 Lua 使用的总内存数。 这个值有小数部分,所以只需要乘上 1024 就能得到 Lua 使用的准确字节数(除非溢出)
collectgarbage(“setpause”): 将 arg 设为收集器的 间歇率。 返回 间歇率 的前一个值。
collectgarbage(“setstepmul”): 返回 步进倍率 的前一个值。
collectgarbage(“setpause”, 200) : 内存增大 2 倍(200/100)时自动释放一次内存 (200 是默认值)。
collectgarbage(“setstepmul”, 200) :收集器单步收集的速度相对于内存分配速度的倍率,设置 200 的倍率等于 2 倍(200/100)。(200 是默认值)