一、保留关键字
大小写敏感。
Chunks:语句块(匿名函数),一条或多条语句、函数、几MB的块。
-- 保留字
and break do else elseif
end false for function if
in local nil not or
true repeat return then until
while
--[[ 多行注释
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")) --> string
print(type(10.4*3)) --> number
print(type(print)) --> function
print(type(type)) --> function
print(type(true)) --> boolean
print(type(nil)) --> nil
print(type(type(X))) --> string
print(type(a)) --> nil ('a' is not initialized)
a = 10
print(type(a)) --> number
a = "a string!!"
print(type(a)) --> string
a = 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) --> 11
print("10 + 1") --> 10 + 1
print("-5.3e - 10" * "2") --> -1.06e-09
print("hello" + 1) --> ERROR (cannot convert "hello")
print(10 .. 20) --> 1020,字符串连接符,10后面必须空格防止解释错误。
print(type(10)) --> number
print(type("10")) --> string
print(type(tostring(10))) --> string
print(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 = a
print(a == c) --> true
print(a == b) --> false
print("2" > "15") --> true
3、逻辑运算符
and or not
认为:false和nil为假,其他为真,所以0也是真。
and和or返回不是true、false,和操作数有关,其实就可以看成“算术运算符”。
not返回true、false。
a and b -- 如果a为false,则返回a,否则返回b
a or b -- 如果a为true,则返回a,否则返回b
x = x or v -- 技巧:v是x的初始化值。
(a and b) or c --相当于C三元运算符?:
if not not a then end --技巧:返回a对应的boolean值,
4、连接运算符 ..
print("Hello " .. "World") --> Hello World
print(0 .. 1) --> 01 转成了字符串
四、基本语法
---------------赋值---------------
a = "hello" .. "world" -- 赋值
a, b = 10, 2*x -- 多赋值
x, y = y, x -- 交换x、y
a, b, c = 0, 1 -- c=nil,少,补nil
a, b = 0, 1, 3 -- 3丢弃,多,忽略
a, b, c = 1 -- a=1,b=nil,c=nil,注意!!!
a, b = f() -- 第一个返回值给a,第二个给b
---------------局部变量---------------
x = 10
local i = 1 -- 在当前的chunk有效
while i<=x do
local x = i*2 --> local to the while body
print(x) --> 2, 4, 6, 8, ...
i = i + 1
end
if i > 20 then
local x --> local to the "then" body
x = 20
print(x) --> 20
else
print(x) --> 10 (the global one)
end
print(x) --> 10 (the global one)
------------条件控制------------
if condi then --condi可以是任意值,只有false和nil为假。
then-part
elseif conditions then
elseif-part
.. --->多个elseif
else
else-part
end;
------------while循环------------
while condition do
statements;
end;
------------repeat循环------------
repeat
statements;
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=1
loop-part
break --退出当前循环,退出双重循环要2个break,没有continue
end
------------泛型for------------
for i,v in ipairs(a) do --遍历array a全部值 a={ 1, 2, 3, 4, 5, }
print(v)
end
for k in pairs(t) do --遍历table a全部key,a={ x=1, y=2, z=3 }
print(k)
end
local i = 1
------------break和return------------
function foo ()
while true do
break -- 语法错误
if true then break end --正确
do break end -- 正确
i = i + 1
end
return -- 语法错误
do return end -- C的return
end
五、函数
带有词法定界(lexical scoping)的第一类值(first-class values)。
词法定界,指的是函数内部可以访问外部函数的局部变量。
a = {p = print}
a.p("Hello World") --> Hello World
print = math.sin --> `print' now refers to the sine function
a.p(print(1)) --> 0.841470
foo = 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要大于右边的name
end)
Lib = {
foo = function (x,y) return x + y end,
}
Lib = {}
function Lib.goo (x,y)
return x - y
end
local f = function (...)
...
f(...) --递归调用,报错
end
local 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 results
function foo1 () return 'a' end -- returns 1 result
function foo2 () return 'a','b' end -- returns 2 results
-- 两种情况:
-- 1、func()在最后,返回值尽量补充
x,y = foo2() -- x='a', y='b'
x = foo2() -- x='a', 'b' is discarded
x,y,z = foo2() -- x='a', y='b', z=nil
x,y,z = 10,foo2() -- x=10, y='a', z='b'
-- 2、其他情况,只返回一个值
-- 作为表达式调用的时候:
x,y = foo2(), 20 -- x='a', y=20
x,y = foo0(), 20, 30 -- x='nil', y=20, 30 is discarded,foo0返回值是nil
print(foo1()) --> a
print(foo2()) --> a b
print(foo2(), 1) --> a 1
print(foo2() .. "x") --> ax
a = {foo1()} -- a = {'a'}
a = {foo2()} -- a = {'a', 'b'}
a = {foo0(), foo2(), 4} -- a[1] = nil, a[2] = 'a', a[3] = 4
function foo()
return foo2() -- 完整返回foo2的返回值
end
print(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 1
if t[i] then
return t[i], unpack(t, i + 1)
end
end
------------可变参数------------
function func(...) --可变参数:三个点
for i,v in ipairs(arg) do --arg:参数列表
...
arg.n --参数个数
end
end
function 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 = 0
return function() -- 返回一个闭包(值)
i = i + 1 -- i是upvalue
return i
end
end
c1, c2 = newCounter(), newCounter() --c1、c2是两个不同的闭包
print(c1()) --> 1
print(c1()) --> 2
print(c2()) --> 1
print(c1()) --> 3
print(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之间的变量列表
end
return iter, t, var -- 迭代器,状态常量,控制变量
-- 单状态迭代器:t一般就是被迭代对象本身:
-- 多状态迭代器:t如果是{ x=t }类型,那就是个多状态的迭代器。
end
for 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 = 0
local n = table.getn(t)
return function () --用了闭包,有状态的迭代器
i = i + 1
if i <= n then return t[i] end
end
end
t = {10, 20, 30}
for element in create_iter(t) do -- 这个就叫泛型for
print(element)
end
七、编译、运行、错误
1、编译
文本代码 -> 中间码(编译) -> 机器码 -> 执行。
过程其实就是正常的编译+运行过程。
不同在于Lua解释器把整个过程全部完成。而这个Lua解释器是依附于C/C++程序运行的。也就是我们说的运行时编译。
-- 编译文件
function loadfile(filename)
-- ->中间码->机器码,并返回编译后的chunk作为函数
......
if success then return chunk end -- 成功:返回编译后的chunk
else return nil, errinfo -- 失败:返回nil chunk和错误信息。
end
-- 编译文件 + 运行
function dofile (filename) --每次调用,都会编译
local f = assert(loadfile(filename))
return f()
end
local 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 = 0
f = 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))
end
return _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, errinfo
loadlib(path, initfunc)
print(loadlib()) -- bad arguments:nil,表示支持动态链接库,否则是不支持。
local path = "/usr/local/lua/lib/libluasocket.so"
local f = assert(loadlib(path, "luaopen_socket")) --返回初始化函数luaopen_socket
f() -- 执行luaopen_socket
4、错误与处理
错误发生,跳出当前chunk。
pcall
assert(func(), errinfo) --func()返回nil,抛出错误,错误信息errinfo
function safeExe()
errinfo = { code = 121 } --错误信息
error(errinfo,stack_level) --抛出错误
end
local status, ret_or_errinfo = pcall(safeExe) -- 保护模式下执行safeExe
if status == true then
-- 执行成功,没有错误
-- ret_or_errinfo:函数返回值
else
-- 执行失败,报错
-- ret_or_errinfo:错误信息
print(errinfo.code) --> 121
end
local 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.")
end
function ohshit()
print(debug.traceback())
end
xpcall(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 do
print(i,":", a, b, c)
a, b, c = coroutine.yield(a * 10, b * 10)
end
end)
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 value
end
function send (x)
coroutine.yield(x)
end
producer = coroutine.create( function ()
while true do
local x = io.read() -- produce new value
send(x)
end
end)
管道和顾虑器
function receive (prod)
local status, value = coroutine.resume(prod)
return value
end
function send (x)
coroutine.yield(x)
end
function producer ()
return coroutine.create(function ()
while true do
local x = io.read() -- produce new value
send(x)
end
end)
end
function filter (prod)
return coroutine.create(function ()
local line = 1
while true do
local x = receive(prod) -- get new value
x = string.format("%5d %s", line, x)
send(x) -- send it to consumer
line = line + 1
end
end)
end
function consumer (prod)
while true do
local x = receive(prod) -- get new value
io.write(x, "\n") -- consume new value
end
end
consumer(filter(producer()))
迭代器
function permgen (a, n)
if n == 0 then
coroutine.yield(a)
else
for i=1,n do
-- put i-th element as the last one
a[n], a[i] = a[i], a[n]
-- generate all permutations of the other elements
permgen(a, n - 1)
-- restore i-th element
a[n], a[i] = a[i], a[n]
end
end
end
function printResult (a)
for i,v in ipairs(a) do
io.write(v, " ")
end
io.write("\n")
end
function perm (a)
local n = table.getn(a)
local co = coroutine.create(function () permgen(a, n) end)
return function () -- iterator
local code, res = coroutine.resume(co)
return res
end
end
--function perm (a)
-- local n = table.getn(a)
-- return coroutine.wrap(function () permgen(a, n) end)
--end
for p in perm{"a", "b", "c"} do
printResult(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 do
local s, status = read1KB(c) --读取1KB数据
count = count + string.len(s) --累计已读取数据
if status == "closed" then break end
end
c:close()
print(file, count)
end
function read1KB (connection)
connection:timeout(0) -- 使任何请求都不阻塞
local s, status = connection:receive(2^10)
if status == "timeout" then
coroutine.yield(connection) --没有下载数据,挂起
end
return s, status
end
threads = {} -- 当前下载线程列表
function get (host, file) -- 创建下载线程
local co = coroutine.create(function ()
download(host, file)
end)
table.insert(threads, co)
end
function dispatcher()
while true do
local n = table.getn(threads)
if n == 0 then break end
local connections = {}
for i = 1, n do
local status, res = coroutine.resume(threads[i])
if not res then -- download函数执行完毕,没有返回值
table.remove(threads, i)
break
else -- 没有数据,连接超时
table.insert(connections, res)
end
end
if table.getn(connections) == n then -- 当前所有下载连接都没有数据
-- 当所有的连接都timeout,分配器调用select等待任一连接状态的改变
socket.select(connections)
end
end
end
---------------------------------------
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