了解 Lua
- 脚本语言,嵌入程序中,为应用程序提供灵活的扩展和定制功能
- 轻量级:使用C语言编写,编译后只有一百余K,可以很方便嵌入别的程序中
- 可扩展:lua 可以调用 C/C++ 提供的一些功能
- 支持面向过程编程 和 函数式编程
- 自动内存管理
- 动态语言类型。
- 变量不要类型定义,只需要为变量赋值。
变量
变量
:全局变量、局部变量、表中的域- 变量的默认值:nil
局部变量
:只有用 local 显式声明的为局部变量。作用域从声明位置开始直到所在的语句块结束全局变量
:不需要声明,赋值后即创建了。- 访问没有初始化的全局变量不会报错,得到的是nil
- 想删除一个全局变量,将其赋值为nil即可
- 尽量使用局部变量:
- 避免命名冲突
- 访问局部变量的速度比全局变量更快
数据类型
Lua 中有 8 个基本类型:
nil
- 表示一个无效值(在条件表达式中等价于false)
boolean
number
- 默认 double 双精度
string
function
- 由 C 或 Lua 编写的函数
userdata
- 表示任意存储在变量中的 C 数据结构
- userdata 是一种用户自定义数据,用于表示一种由应用程序或 C/C++ 语言库所创建的类型。
可以将任意 C/C++ 的任意数据类型的数据(通常是 struct 和 指针)存储到 Lua 变量中调用。
thread
- 执行的独立路线,用于执行协同程序
table
- 通过构造表达式来创建,比如{}来创建一个空表
print 函数
print("hello world")
print(type("hello world")) -- type :输出变量的类型
print(#"# can get len") -- #:获取字符串的长度,13
-- nil 作比较时应该加上双引号 ""
print(type(x) == nil) -- false
print(type(x) == "nil") -- true
-- false 和 nil ------> false
print(type(true)) -- boolean
print(type(false)) -- boolean
print(type(nil)) -- nil
-- 在对数字字符串进行运算操作时,会尝试将这个数字字符串转成一个数字
print("2" + 6) -- 8
多变量赋值
Lua可以对多个变量同时赋值,变量列表和值列表的各个元素用逗号分开,赋值语句右边的值会依次赋值给左边的变量
赋值语句会先计算右边所有的值后 在 进行赋值操作
,因此可以交换变量的值
x = 1
y = 2
x , y = y , x
print(x , y) -- 2 1
当变量个数 > 值的个数------按变量个数补足nil
x , y = 2
print(x , y) -- 2 nil(注意这里 y 的值变成 nil 了)
数组
-- 初始化数组
array = {}
for i = 1 , 3 do
array[i] = {}
for j = 1 , 3 do
array[i][j] = i * j
end
end
-- 访问数组
for i = 1 , 3 do
for j = 1 , 3 do
print(array[i][j])
end
end
流程控制
local f = 12
if f < 0 then
print("f < 0")
elseif f < 4 then
print("0 <= f < 4")
else
print("f >= 4")
end
迭代器
迭代器是一种对象,能够用来遍历标准模板库容器中的部分或者全部元素,每个迭代器对象代表容器中的确定的地址
迭代器是一种支持指针类型的结构,可以遍历集合的每一个元素,
无状态迭代器
无状态迭代器不保留任何状态的迭代器,在循环中可以利用无状态迭代器避免创建闭包花费额外的代价。
每一次迭代,迭代函数都是用两个变量(状态常量 和 控制变量)的值作为参数被调用
,一个无状态的迭代器只利用这两个值可以获取下一个元素。
无状态迭代器的典型的例子就是 ipairs。
-- 这里要注意两个参数的顺序 状态常量 控制变量
function test(iterMaxCount , currNum)
if currNum <= iterMaxCount then
currNum = currNum + 1
return currNum , currNum * currNum
end
end
-- 迭代函数test 状态常量 控制变量
for i , n in test, 3, 1 do
print(i , n)
end
--[[
输出:
2 4
3 9
4 16
]]
泛型 for
1: 在自己内部保存迭代函数,实际上它保存三个值:迭代函数
、状态常量
、控制变量
2: 使用默认的迭代函数:ipairs
-- 第一次迭代调用iter(a , 0),返回 1 , a[1]
-- 第二次迭代调用iter(a , 1),返回 2 , a[2]
-- ......直到得到一个nil元素
function iter( a , i)
i = i + 1 -- 下标
local value = a[i] -- 读取数组 a 中对应下标是 i 的值
if value then
return i , value
end
end
function ipairs(a)
return iter , a , 0 -- 迭代函数iter 状态常量a 控制变量初始值0
end
多状态迭代器
很多情况下,迭代器需要保存多个状态信息
,而不是简单的状态常量 和 控制变量,
最简单的方法是使用闭包
,或者将所有的状态信息封装到 table 内
,将table作为迭代器的状态常量,因为所有的信息都存储到table内,因此迭代函数通常不需要第二个参数
array = {"hello" , "you"}
function elemIter(arr)
local index = 0
local len = #arr
-- 闭包函数
return function()
index = index + 1
if index <= len then
return arr[index]
end
end
end
for element in elemIter(array) do
print(element)
end
--[[
输出:
hello
you
]]
循环
-- while 循环
a = 10
while(a < 12) do
print("a 的值为:", a)
a = a + 1
end
-- for循环的三个表达式会在循环开始前,一次性求值,以后不在求值
for i = 11 , a , 2 do
print("i 的值为:", i)
end
-- 泛型 for 循环。通过一个迭代器函数来遍历所有值
taba = {"one" , "two" , "three"}
for i , v in ipairs(taba) do
print( i , v)
end
--[[
输出:
1 one
2 two
3 three
]]
-- repeat.....until
b = 10
repeat
print("b 的值为:" , b)
b = b + 1
until( b > 13)
--[[
输出:
b 的值为: 10
b 的值为: 11
b 的值为: 12
b 的值为: 13
]]
-- break:用于退出当前循环或语句。如果使用循环嵌套,break 语句将停止 最内层 循环的执行
函数
myprint = function(param)
print("这是自定义打印函数: " , param)
end
myprint(10) -- 这是自定义打印函数: 10
function add(num1 , num2 , functionparam)
result = num1 + num2
functionparam(result)
end
add(2 ,5 , myprint) -- 这是自定义打印函数: 7
可变参数
-- 可变参数,使用 ... 表示
-- 可以通过select("#" , ...) 来获取可变参数的数量
function add(...)
local s = 0
for i , v in ipairs{...} do
s = s + v
end
print("可变参数的参数个数为" , select("#" , ...))
return s
end
print("总和为:" , add(3, 4, 5, 6, 7))
-- 可变参数可以赋值给一个变量 arg ,可以通过 #arg 获取可变参数的数量
function average(...)
local s = 0
local arg = {...}
for i , v in ipairs(arg) do
s = s + v
end
print("可变参数的参数个数为" , #arg)
return s/#arg
end
print("平均值为:" , average(3, 4, 5, 6, 7))
固定参数 + 可变参数
-- 固定参数 + 可变参数
function fwrite(fmt , ...)
return io.write(string.format(fmt , ...))
end
fwrite("hello you\n") -- hello you
fwrite("%d%d\n" , 1 , 2) -- 12
-- 使用 select 来访问变长参数,select("#" , ...) 或者 select(n , ...)
function foo(...)
for i = 1 , select("#" , ...) do
local arg = select(i , ...) -- 读取参数
print("arg: " , arg)
end
end
foo(1 ,2 ,3 ,4 )
--[[
输出:
arg: 1
arg: 2
arg: 3
arg: 4
]]
匿名函数
function testFun( tab , fun)
for k , v in pairs(tab) do
print(fun(k , v))
end
end
-- 调用testFun,第二个参数使用匿名函数的形式
tab={ key1 = "val1" , key2 = "val2"};
testFun(tab ,
function(key , val)
return key.."="..val
end
);
table
table 使用关联型数组,可以使用任意类型的值来作为数组的索引,但这个值不能是 nil
table 是不固定大小的,可以根据自己的需要进行扩容
构造器是创建和初始化表 的表达式,最简单的构造函数是 {} ,用来创建一个空表
-- 初始化表
mytable = {}
mytable[1] = "lua"
--移除引用,后续lua垃圾回收会释放对应内存
mytable = nil
lua 表中默认索引从 1 开始
tabl = { key1 = "val1" , key2 = "val2" , "val3"}
for k ,v in pairs(tabl) do
print(k .. "-" .. v .. " , " .. type(k) .. "-" .. type(v))
end
tabl.key1 = nil
--[[
输出:
1-val3
key1-val1
key2-val2
]]--
获取表的长度
可以发现使用 #
或者 table.getn
获取表的长度的时候,都会在索引中断的地方停止计数
,而导致无法正确获取table的长度
table1 = {[1] = 2 , [2] = 6 ,[3] = 34 , [26] = 5}
print("使用 # 获取table1 的长度:" , #table1) -- 3
print("使用 table.getn 获取table1 的长度:" , table.getn(table1)) -- 3
function getLen(t)
local len = 0
for k , v in pairs(t) do
len = len + 1
end
return len
end
print("使用 自定义函数 获取table1 的长度:" , getLen(table1)) --4
表之间的赋值
mytable = {}
mytable[1]="Lua"
mytable["haha"]="hello"
altertable = mytable
-- 两个table都指向同一块内存,修改会同步修改
altertable[1]="Lua new"
print(mytable[1] , altertable[1]) -- Lua new Lua new
-- 一个table赋值为nil后不影响另一个table所指向的内容
altertable = nil
print(mytable[1]) -- Lua new
table 操作
concat
mytable = {"banana" , "orange" , "apple"}
print("连接后的字符串" ,table.concat(mytable))
print(", 连接后的字符串" ,table.concat(mytable , ","))
print(", 连接部分数据后的字符串" ,table.concat(mytable , "," , 2 , 3))
--[[
输出:
连接后的字符串 bananaorangeapple
, 连接后的字符串 banana,orange,apple
, 连接部分数据后的字符串 orange,apple
]]
sort
print("排序前:")
for k , v in pairs(mytable) do
print(k , v)
end
table.sort(mytable)
print("排序后:")
for k , v in pairs(mytable) do
print(k , v)
end
--[[
输出:
1 banana
2 orange
3 apple
排序后:
1 apple
2 banana
3 orange
]]
模块
模块类似一个封装库
,把公用的代码放在一个文件里,以 API 接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度。
模块是由变量、函数等组成的 table,创建一个模块就是创建一个 table
,然后把需要导出的常量、函数放入其中,最后返回这个 table 就行。
比如我们现在定义一个模块叫做module,书写在module.lua文件中。在usemodule.lua 中调用模块module的变量和函数:
module.lua
-- 定义一个名字为 model 的模块
module = {}
module.constant = "这是一个常量"
-- 定义一个公有函数
function module.func1()
io.write("这是一个公有函数\n")
end
-- 定义一个私有函数
-- local修饰的函数。即不能从外部访问模块里的这个私有函数,必须通过模块里的 公有函数来调用,如func3
local function func2()
print("这是一个私有函数\n")
end
-- 定义一个共有函数,调用私有函数
function module.func3()
func2()
end
-- 返回封装好的 table
return module
usemodule.lua
-- 可以通过require来加载模块
require("module")
print(module.constant)
module.func3()
-- 也可以给加载的模块定义一个别名
local m = require("module")
print(m.constant)
m.func3()
动态链接库
Lua 在一个叫 loadlib
函数中提供了所有的动态链接
的功能。这个函数有两个参数:库的绝对路径
和初始化函数
loadlib 函数
加载指定的库并连接到 Lua,但是并不打开库
(也就是没有调用初始化函数),但是返回初始化函数
作为 Lua 的一个函数,这样就能直接在 Lua 中调用
他。
local path = "/usr/local/lua/lib/libluasocket.so"
local f = loadlib(path, "luaopen_socket") -- 并不打开库,而是返回一个初始化函数,这个函数可以在 lua 中调用
f() -- 真正打开库
元表
在 Lua 中对 table 可以通过访问对应的 key 来得到 value 值,但是却无法对两个 table 进行操作。
Lua 提供了元表( Metatable),允许改变 table 的行为
,每个行为关联了对应的元方法。
协同程序(coroutine)
拥有自己独立的栈、局部变量和指令指针,可以跟其他协同程序共享全局变量和其他大部分东西。
线程跟协程的区别:线程可以同时多个运行,而协程任意时刻只能运行一个,并且处于运行状态的协程只有被挂起(suspend)时才会暂停。