一、保留关键字
大小写敏感。
Chunks:语句块(匿名函数),一条或多条语句、函数、几MB的块。
-- 保留字and break do else elseifend false for function ifin local nil not ortrue repeat return then untilwhile--[[ 多行注释print(10) -- 单行注释--]]
命令行
lua [options] [script [args]]
prompt>lua -e "print(math.sin(12))" #执行prompt>lua -i -e "_PROMPT=' lua> '" #进入交互模式#获取命令行参数:arg全局变量lua>lua -e "sin=math.sin" script a b#arg内容如下:#arg[-3] = "lua"#arg[-2] = "-e"#arg[-1] = "sin=math.sin"#arg[0] = "script"#arg[1] = "a"#arg[2] = "b"
二、数据类型
8种基本数据类型:nil、boolean、number、string、userdata、function、thread和table。
变量:没有类型限制,可以是任何值。type函数可查看变量类型:
print(type("Hello world")) --> stringprint(type(10.4*3)) --> numberprint(type(print)) --> functionprint(type(type)) --> functionprint(type(true)) --> booleanprint(type(nil)) --> nilprint(type(type(X))) --> stringprint(type(a)) --> nil ('a' is not initialized)a = 10print(type(a)) --> numbera = "a string!!"print(type(a)) --> stringa = print -- yes, this is valid!a(type(a)) --> function
nil:只要一个值nil,全局变量默认值nil,当前仅当全局变量!=nil时,全局变量存在,赋值非nil时创建,赋值nil删除全局变量。
boolean:false,true。
if a then -- a为false/nil时不执行,其他值则执行。所以0也执行。print "mother fucker"end
number:只有实数类型,没有整数,支持C/C++任何长整型。
4 0.4 4.57e-3 0.3e12 5e+20
string:字符串(相当于C的数组,不是C的字符串),不可修改,自动内存管理,可以1MB长度(数据描述是Lua的设计初衷)。和number自动类型转换。注意!lua的字符串不是\0空字符结尾,而是给定的长度,所以与Lua进行交互的时候,要特别注意。
local str = 'asdfasdf' --单引号local str = "asdfasdf" --双引号local str = [[asdfasdf --可以多行,适合大型chunk如代码、数据文件。[[asdfasd]] --可以嵌套\n --原样输出,不转义]]---------------转义字符---------------\a bell\b back space -- 后退\f form feed -- 换页\n newline -- 换行\r carriage return -- 回车\t horizontal tab -- 制表\v vertical tab\\ backslash -- "\"\" double quote -- 双引号\' single quote -- 单引号\[ left square bracket -- 左中括号\] right square bracket -- 右中括号\ddd --ddd:三位十进制数字--\97 a--\10 \n--\049 1-- string和number“自适应地”自动类型转换print("10" + 1) --> 11print("10 + 1") --> 10 + 1print("-5.3e - 10" * "2") --> -1.06e-09print("hello" + 1) --> ERROR (cannot convert "hello")print(10 .. 20) --> 1020,字符串连接符,10后面必须空格防止解释错误。print(type(10)) --> numberprint(type("10")) --> stringprint(type(tostring(10))) --> stringprint(type(tonumber("10"))) --> number
function:第一类值(和number、string完全一样的使用特性),可以存储在变量,可以函数参数,可以函数返回值。极大灵活代码。
userdata:存储C数据,预定义操作只有赋值、比较。
thread:线程,设计协同操作。
table:表。
---------------表构造---------------polyline = {color="blue", --polyline.color或者polyline["polyline"]thickness=2; --可以用分号npoints=4,{x=0, y=0}, --polyline[1]{x=-10, y=0}, --polyline[2]{x=-10, y=1}, --polyline[3]{x=0, y=1}, --polyline[4]["+"] = "add", --polyline["+"]["-"] = "sub",["*"] = "mul",["/"] = "div"}
三、运算符
优先级,一元 > 二元;算术 > 关系 > 逻辑
1、算术运算符
二元运算符:+ - / ^ (加减乘除幂),注意有个幂
一元运算符:- (负值)
操作数为实数。
*2、关系运算符
< > <= >= == ~=
返回true、false。
== ~=:比较两个值,类型不同也不同,nil只和自己相等,tables、userdata、functions都是引用比较,即只有引用同一个对象才相等
a, b = {},{}c = aprint(a == c) --> trueprint(a == b) --> falseprint("2" > "15") --> true
3、逻辑运算符
and or not
认为:false和nil为假,其他为真,所以0也是真。
and和or返回不是true、false,和操作数有关,其实就可以看成“算术运算符”。
not返回true、false。
a and b -- 如果a为false,则返回a,否则返回ba or b -- 如果a为true,则返回a,否则返回bx = x or v -- 技巧:v是x的初始化值。(a and b) or c --相当于C三元运算符?:if not not a then end --技巧:返回a对应的boolean值,
4、连接运算符 ..
print("Hello " .. "World") --> Hello Worldprint(0 .. 1) --> 01 转成了字符串
四、基本语法
---------------赋值---------------a = "hello" .. "world" -- 赋值a, b = 10, 2*x -- 多赋值x, y = y, x -- 交换x、ya, b, c = 0, 1 -- c=nil,少,补nila, b = 0, 1, 3 -- 3丢弃,多,忽略a, b, c = 1 -- a=1,b=nil,c=nil,注意!!!a, b = f() -- 第一个返回值给a,第二个给b---------------局部变量---------------x = 10local i = 1 -- 在当前的chunk有效while i<=x dolocal x = i*2 --> local to the while bodyprint(x) --> 2, 4, 6, 8, ...i = i + 1endif i > 20 thenlocal x --> local to the "then" bodyx = 20print(x) --> 20elseprint(x) --> 10 (the global one)endprint(x) --> 10 (the global one)------------条件控制------------if condi then --condi可以是任意值,只有false和nil为假。then-partelseif conditions thenelseif-part.. --->多个elseifelseelse-partend;------------while循环------------while condition dostatements;end;------------repeat循环------------repeatstatements;until conditions;------------for循环-------------- for(var = exp1; var < exp2; var += exp3)-- exp1,exp2,exp3只计算一次,循环前-- 不要主动改变var,结果未知for var=exp1,exp2,exp3 do-- for var=exp1,exp2 do --exp3=1loop-partbreak --退出当前循环,退出双重循环要2个break,没有continueend------------泛型for------------for i,v in ipairs(a) do --遍历array a全部值 a={ 1, 2, 3, 4, 5, }print(v)endfor k in pairs(t) do --遍历table a全部key,a={ x=1, y=2, z=3 }print(k)endlocal i = 1------------break和return------------function foo ()while true dobreak -- 语法错误if true then break end --正确do break end -- 正确i = i + 1endreturn -- 语法错误do return end -- C的returnend
五、函数
带有词法定界(lexical scoping)的第一类值(first-class values)。
词法定界,指的是函数内部可以访问外部函数的局部变量。
a = {p = print}a.p("Hello World") --> Hello Worldprint = math.sin --> `print' now refers to the sine functiona.p(print(1)) --> 0.841470foo = function (x) return 2*x end -- 赋值函数-- table.sort(array_table, func) -- 高级函数:以其他函数作为参数table.sort(network, function (a,b) -- a在b的左边return (a.name > b.name) -- 左边的name要大于右边的nameend)Lib = {foo = function (x,y) return x + y end,}Lib = {}function Lib.goo (x,y)return x - yendlocal f = function (...)...f(...) --递归调用,报错endlocal function f (...)...f(...) --递归调用,正确end
调用
function func_name (arguments-list) --参数少,补nil;参数多,忽略。statements-list;return ret1, ret2 --多返回值end;func(a, b)func "hellow world" --只有一个参数,可以省略()func [[a multi-linemessage]] --只有一个参数,可以省略()func {1,2,3} --只有一个参数,可以省略()s, e = string.find("hello Lua users", "Lua") --多返回值(起始、结束下标)
(多)返回值
------------函数返回值作为参数------------function foo0 () end -- returns no resultsfunction foo1 () return 'a' end -- returns 1 resultfunction foo2 () return 'a','b' end -- returns 2 results-- 两种情况:-- 1、func()在最后,返回值尽量补充x,y = foo2() -- x='a', y='b'x = foo2() -- x='a', 'b' is discardedx,y,z = foo2() -- x='a', y='b', z=nilx,y,z = 10,foo2() -- x=10, y='a', z='b'-- 2、其他情况,只返回一个值-- 作为表达式调用的时候:x,y = foo2(), 20 -- x='a', y=20x,y = foo0(), 20, 30 -- x='nil', y=20, 30 is discarded,foo0返回值是nilprint(foo1()) --> aprint(foo2()) --> a bprint(foo2(), 1) --> a 1print(foo2() .. "x") --> axa = {foo1()} -- a = {'a'}a = {foo2()} -- a = {'a', 'b'}a = {foo0(), foo2(), 4} -- a[1] = nil, a[2] = 'a', a[3] = 4function foo()return foo2() -- 完整返回foo2的返回值endprint(foo()) -- 打印foo2的全部返回值print((foo())) -- 只返回foo的第一个返回值local _, x = string.find(s, p) -- _ 称为 哑元,占位用的。
参数
------------unpack解包参数-------------- unpack:把表(数组形式)以多返回值形式返回a = {"hello", "ll"}print(string.find(unpack(a))) --> 3 4-- unpack 原理:function unpack(t, i)i = i or 1if t[i] thenreturn t[i], unpack(t, i + 1)endend------------可变参数------------function func(...) --可变参数:三个点for i,v in ipairs(arg) do --arg:参数列表...arg.n --参数个数endendfunction g (a, b, ...) end --至少2个参数g(3) -- a=3, b=nil, arg={n=0}g(3, 4) -- a=3, b=4, arg={n=0}g(3, 4, 5, 8) -- a=3, b=4, arg={5, 8; n=2}------------参数传入------------w = Window {x=0, y=0, width=300, height=200,title = "Lua", background="blue",border = true}------------闭包-------------- 闭包 = 函数 + upvalues。-- 简单理解“带状态”的函数,函数引用了upvalue,这个upvalue就代表状态。function newCounter()local i = 0return function() -- 返回一个闭包(值)i = i + 1 -- i是upvaluereturn iendendc1, c2 = newCounter(), newCounter() --c1、c2是两个不同的闭包print(c1()) --> 1print(c1()) --> 2print(c2()) --> 1print(c1()) --> 3print(c2()) --> 2------------尾调用-------------- C的goto效果function f(x)return g(x) -- 尾调用,不会为g函数额外开辟栈空间g(x)return -- 不是尾调用return g(x) + 1 -- 不是尾调用return x or g(x) -- 不是尾调用return (g(x)) -- 不是尾调用return x[i].foo(x[j] + a*b, i + j) --是尾调用end
六、迭代器
iterator,简称iter,每调用一次返回被迭代对象的下一个元素。iter应该知道上一个迭代状态,也就是知道自己从哪儿来、要到哪儿去。
------------泛型for原型-------------- var-list:变量名列表,逗号隔开,多数情况1个。-- exp-list:表达式列表,逗号隔开,一般1个。for <var-list> in <exp-list> do<body>end------------泛型for解析------------function iter_create(t) -- t: 迭代常量local var = 0 -- var: 控制变量iter = function(t, var) -- iter: 迭代器,这是一个无状态的迭代器,var = var + 1 -- 因为没有用到闭包,通过参数提供迭代状态(t,var)-- 严格地说,不是迭代器,而是生成器generator-- for才完成迭代。...return a, b -- 就是for in之间的变量列表endreturn iter, t, var -- 迭代器,状态常量,控制变量-- 单状态迭代器:t一般就是被迭代对象本身:-- 多状态迭代器:t如果是{ x=t }类型,那就是个多状态的迭代器。endfor a, b in iter_create(t) do -- 泛型for的过程:-- 1、循环前调用一次iter_create(t),返回一个迭代器,并保存。-- 2、每一次迭代,iter(t, var)-- 3、如果var == nil,终止循环-- a, b: 迭代器的返回值。......end------------例子------------function create_iter (t)local i = 0local n = table.getn(t)return function () --用了闭包,有状态的迭代器i = i + 1if i <= n then return t[i] endendendt = {10, 20, 30}for element in create_iter(t) do -- 这个就叫泛型forprint(element)end
七、编译、运行、错误
1、编译
文本代码 -> 中间码(编译) -> 机器码 -> 执行。
过程其实就是正常的编译+运行过程。
不同在于Lua解释器把整个过程全部完成。而这个Lua解释器是依附于C/C++程序运行的。也就是我们说的运行时编译。
-- 编译文件function loadfile(filename)-- ->中间码->机器码,并返回编译后的chunk作为函数......if success then return chunk end -- 成功:返回编译后的chunkelse return nil, errinfo -- 失败:返回nil chunk和错误信息。end-- 编译文件 + 运行function dofile (filename) --每次调用,都会编译local f = assert(loadfile(filename))return f()endlocal f = loadfile(filename) -- 一次编译a, b = f(), f() -- 多次运行print(loadstring("i i")) --> nil [string "i i"]:1: '=' expected near 'i'local str = [[function fuck() print("shit") end]]local chunk = loadstring(str) -- 编译了代码,并未执行执行代码fuck() -- 错误,fuck未定义chunk()fuck() -- 正确-- loadstring不关心词法范围,只是用全局变量。local i = 0f = loadstring("i = i + 1") -- i是全局变量g = function () i = i + 1 end -- i是局部变量
2、require函数
require和dofile功能类似,编译并执行,但前者更优化:
1、搜索目录加载文件
2、避免重复加载
搜索目录
执行require “fuck” 时
1、确定搜索目录路径:
a、检查全局变量:LUA_PATH是否为字符串?是则确定为路径。
b、检查环境变量:LUA_PATH是否有值?是则确定为路径。
c、以上失败:确定路径为 ?;?.lua
d、假设目录路径:
?;?.lua;c:\windows\?;/usr/local/lua/?/?.lua;/usr/local/default.lua
2、查找文件
fuck
fuck.lua
c:\windows\fuck
/usr/local/lua/fuck/fuck.lua
没有找到则加载红色文字的文件。
避免重复加载
------------下面是虚拟代码来展示避免重复加载原理------------function require(filename) --filename:虚名if _LOADED[filename] == nil then -- _LOADED是真实有效的_LOADED[filename] = assert(loadfile(filename))endreturn _LOADED[filename]end------------require小技巧------------LUA_PATH = "/usr/local/lua/newrequire.lua" --全局变量,require搜索目录require("fuck")-- newrequire.lua文件内容如下if _REQUIREDNAME == "fuck" then --全局变量_REQUIREDNAME保存require的文件名-- 当其他地方require("fuck")时,会执行这里end
3、动态链接库:loadlib
动态链接库不是ANSI C的标准,但是Lua为windows、Linux、FreeBSD、Solaris和其他一些Unix平台提供了这种机制。
-- path: 库路径-- initfunc: 初始化函数名-- return: 函数chunk, errinfoloadlib(path, initfunc)print(loadlib()) -- bad arguments:nil,表示支持动态链接库,否则是不支持。local path = "/usr/local/lua/lib/libluasocket.so"local f = assert(loadlib(path, "luaopen_socket")) --返回初始化函数luaopen_socketf() -- 执行luaopen_socket
4、错误与处理
错误发生,跳出当前chunk。
pcall
assert(func(), errinfo) --func()返回nil,抛出错误,错误信息errinfofunction safeExe()errinfo = { code = 121 } --错误信息error(errinfo,stack_level) --抛出错误endlocal status, ret_or_errinfo = pcall(safeExe) -- 保护模式下执行safeExeif status == true then-- 执行成功,没有错误-- ret_or_errinfo:函数返回值else-- 执行失败,报错-- ret_or_errinfo:错误信息print(errinfo.code) --> 121endlocal status, err = pcall(function () a = 'a'+1 end) --lua自己也会生成错误信息print(err) -- stdin:1: attempt to perform arithmetic on a string value-- stdin:文件名-- 1:行号
xpcall
pcall无法获取异常发生时的栈信息,xpcall可以在错误发生时,回调函数,即可获取栈信息。
function dosmth()error("got an error.")endfunction ohshit()print(debug.traceback())endxpcall(dosmth, ohshit)
八、协同程序:Thread
线程,同时运行。
协程,一次只能一个。
协程三个状态:挂起态、运行态、终止态
1、coroutine.create创建函数,进入挂起态。
2、resume,进入运行态,从挂起位置开始运行。
3、函数遇到yield,就被挂起,进入挂起态。
4、函数执行完成,变为终止态,resume会报错。
-- 创建协程thread(进入挂起态)-- func: function,匿名函数-- co: thread,内存地址,此时被挂起local co = coroutine.create(func)-- 返回协程当前状态:挂起态suspend、终止态dead-- co: thread-- status: suspend: 挂起态,刚被创建挂起、yield()处被挂起。-- dead: 终止态,函数执行完毕。local status = coroutine.status(co)-- 唤醒协程,从挂起处运行-- co: function-- ...1: 传送 给 唤醒的yield的参数-- 第一种:创建处挂起,则传送给函数做参数-- 第二种:yield处挂起,传送给这个yield函数做参数-- bool: 执行成功true,一般都成功-- ...2: “下一次”yield传送来的参数、或者是协程函数返回值local bool, ...2 = coroutine.resume(co, ...1)-- 挂起协程,进入挂起态,等待resume唤醒-- ...1: 传递给下一次resume的参数(谁唤醒我,就给谁参数)-- ...2: 传递给唤醒这次yield的resume的参数local ...2 = coroutine.yield(...1)-----------------例子-----------------local co = coroutine.create(function (a, b, c)for i = 1, 10 doprint(i,":", a, b, c)a, b, c = coroutine.yield(a * 10, b * 10)endend)print(co) -- thread: 0x8071d98 创建成功,返回thread类型。print(coroutine.status(co)) -- suspended 刚被创建进入挂起态,等待唤醒print(coroutine.resume(co,1,2,3)) -- 1:1 2 3 参数传送给协程函数,执行到yield处又被挂起。-- true 10 20 这次resume之后第一次遇到的yield给的参数print(coroutine.status(co)) -- suspended 第一次yield,挂起。print(coroutine.resume(co, 4, 5, 6)) -- 2:4 5 6 resume参数传递给第一次yield处的返回值-- true 40 50 这次resume之后第二次遇到的yield给的参数
生产者-消费者模式(消费者驱动)
function receive ()local status, value = coroutine.resume(producer)return valueendfunction send (x)coroutine.yield(x)endproducer = coroutine.create( function ()while true dolocal x = io.read() -- produce new valuesend(x)endend)
管道和顾虑器
function receive (prod)local status, value = coroutine.resume(prod)return valueendfunction send (x)coroutine.yield(x)endfunction producer ()return coroutine.create(function ()while true dolocal x = io.read() -- produce new valuesend(x)endend)endfunction filter (prod)return coroutine.create(function ()local line = 1while true dolocal x = receive(prod) -- get new valuex = string.format("%5d %s", line, x)send(x) -- send it to consumerline = line + 1endend)endfunction consumer (prod)while true dolocal x = receive(prod) -- get new valueio.write(x, "\n") -- consume new valueendendconsumer(filter(producer()))
迭代器
function permgen (a, n)if n == 0 thencoroutine.yield(a)elsefor i=1,n do-- put i-th element as the last onea[n], a[i] = a[i], a[n]-- generate all permutations of the other elementspermgen(a, n - 1)-- restore i-th elementa[n], a[i] = a[i], a[n]endendendfunction printResult (a)for i,v in ipairs(a) doio.write(v, " ")endio.write("\n")endfunction perm (a)local n = table.getn(a)local co = coroutine.create(function () permgen(a, n) end)return function () -- iteratorlocal code, res = coroutine.resume(co)return resendend--function perm (a)-- local n = table.getn(a)-- return coroutine.wrap(function () permgen(a, n) end)--endfor p in perm{"a", "b", "c"} doprintResult(p)end
非抢占式多线程
非抢占式,线程只会主动挂起中断(主动yield),而不会被其他线程挂起。
多线程远程下载文件
通过http协议从远程主机上下载一些文件,使用Diego Nehab开发的LuaSocket库来完成。
function download (host, file)local c = assert(socket.connect(host, 80)) --创建连接local count = 0 --已读取字节数c:send("GET " .. file .. " HTTP/1.0\r\n\r\n") --http请求下载文件while true dolocal s, status = read1KB(c) --读取1KB数据count = count + string.len(s) --累计已读取数据if status == "closed" then break endendc:close()print(file, count)endfunction read1KB (connection)connection:timeout(0) -- 使任何请求都不阻塞local s, status = connection:receive(2^10)if status == "timeout" thencoroutine.yield(connection) --没有下载数据,挂起endreturn s, statusendthreads = {} -- 当前下载线程列表function get (host, file) -- 创建下载线程local co = coroutine.create(function ()download(host, file)end)table.insert(threads, co)endfunction dispatcher()while true dolocal n = table.getn(threads)if n == 0 then break endlocal connections = {}for i = 1, n dolocal status, res = coroutine.resume(threads[i])if not res then -- download函数执行完毕,没有返回值table.remove(threads, i)breakelse -- 没有数据,连接超时table.insert(connections, res)endendif table.getn(connections) == n then -- 当前所有下载连接都没有数据-- 当所有的连接都timeout,分配器调用select等待任一连接状态的改变socket.select(connections)endendend---------------------------------------host = "www.w3c.org"get(host, "/TR/html401/html40.txt")get(host, "/TR/2002/REC-xhtml1-20020801/xhtml1.pdf")get(host, "/TR/REC-html32.html")get(host,"/TR/2000/REC-DOM-Level-2-Core-20001113/DOM2-Core.txt")dispatcher() -- main loop
