lua入门学习 |
非等于 ~= 连接号 .. and or not 只有 false和nil为假其余为真 a and b -- 如果 a 为 false,则返回 a,否则返回 b a or b -- 如果 a 为 true,则返回 a,否则返回 b |
表 table
{} 最简单
days = {
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday", "Friday", "Saturday"} print(days[4]) --> Wednesday 从1开始 而非是0
一个迭代器 --堆结构 先进后出
list =
nil
for line in io.lines() do list = {next=list, value=line} end
l = list
while l do print(l.value) l = l.next end
表中能嵌入表格
初始化
{x=0, y=0} <--> {[
"x"]=0, [
"y"]=0}
{ "red", "green", "blue"} <--> {[1]= "red", [2]= "green", [3]= "blue"}
,和;为域分隔符号
|
基本语法
赋值
遇到赋值语句 Lua 会先计算右边所有的值然后再执行赋值操作,所以我们可以这样
进行交换变量的值: x, y = y, x -- swap 'x' for 'y' a[i], a[j] = a[j], a[i] -- swap 'a[i]' for 'a[i]'
多值赋值经常用来交换变量,或将函数调用返回给变量:
a, b = f() f()返回两个值,第一个赋给 a,第二个赋给 b。
局部变量
使用 local 创建一个局部变量,与全局变量不同,局部变量只在被声明的那个代码块
内有效。代码块:指一个控制结构内,一个函数体,或者一个 chunk(变量被声明的那 个文件或者文本串)。
应该尽可能的使用局部变量,有两个好处:
1. 避免命名冲突 2. 访问局部变量的速度比全局变量更快
控制结构语句
控制结构的条件表达式结果可以是任何值,Lua 认为 false 和 nil 为假,其他值为真。
if 语句,有三种形式:
if conditions then
then-part end; if conditions then then-part else else-part end; if conditions then then-part elseif conditions then elseif-part .. --->多个 elseif else else-part end;
while 语句:
while condition do
statements; end;
repeat-until 语句:
repeat
statements; until conditions;
for 语句有两大类:
第一,数值 for 循环: for var=exp1,exp2,exp3 do loop-part end 如果要退出循环,使用 break 语句
第二,范型 for 循环:
-- print all values of array 'a' for i,v in ipairs(a) do print(v) end |
函数
函数有两种用途:1.完成指定的任务,这种情况下函数作为调用语句使用;2.计算并
返回值,这种情况下函数作为赋值语句的表达式使用。
function f(a, b) return a or b end
CALL PARAMETERS f(3) a=3, b=nil f(3, 4) a=3, b=4 f(3, 4, 5) a=3, b=4 (5 is discarded)
Lua 函数中,在 return 后列出要返回的值得列表即可返回多值,如
function maximum (a)
local mi = 1 -- maximum index local m = a[mi] -- maximum value for i,val in ipairs(a) do if val > m then mi = i m = val end end return m, mi end print(maximum({8,10,23,12,5})) --> 23 3
Lua 总是调整函数返回值的个数去适用调用环境,
第一,当作为表达式调用函数时,有以下几种情况: 1. 当调用作为表达式最后一个参数或者仅有一个参数时,根据变量个数函数尽可能 多地返回多个值,不足补 nil,超出舍去。 2. 其他情况下,函数调用仅返回第一个值(如果没有返回值为 nil) 第二,函数调用作为函数参数被调用时,和多值赋值是相同 第三,函数调用在表构造函数中初始化时,和多值赋值时相同。 另外,return f()这种类型的返回 f()返回的所有值 可以使用圆括号强制使调用返回一个值。 函数多值返回的特殊函数 unpack,接受一个数组作为输入参数,返回数组的所有元 素。
可变参数
Lua 函数可以接受可变数目的参数,和 C 语言类似在函数参数列表中使用三点(...) 有时候我们可能需要几个固定参数加上可变参数 function g (a, b, ...) end 有时候需要将函数的可变参数传递给另外的函数调用,可以使用前面我们说过的 unpack(arg)返回 arg 表所有的可变参数
命名参数
Lua 的函数参数是和位置相关的,调用时实参会按顺序依次传给形参 Lua 可以通过将所有的参数放在一个表中 rename{old= "temp.lua", new= "temp1.lua"} function rename (arg) return os.rename(arg.old, arg.new) end |
再论函数
Lua 中的函数是带有词法定界(lexical scoping)的第一类值(first-class values)。
第一类值指:在 Lua 中函数和其他值(数值、字符串)一样,函数可以被存放在变 量中,也可以存放在表中,可以作为函数的参数,还可以作为函数的返回值。
词法定界指:被嵌套的函数可以访问他外部函数中的变量。这一特性给 Lua 提供了
强大的编程能力。
既然函数是值,那么表达式也可以创建函数了,Lua 中我们经常这样写:
function foo (x) return 2*x end 下面是原本的函数 foo = function (x) return 2*x end table 标准库提供一个排序函数
network = {
{name = "grauna", IP = "210.26.30.34"}, {name = "arraial", IP = "210.26.30.23"}, {name = "lua", IP = "210.26.23.12"}, {name = "derain", IP = "210.26.23.20"}, } 如果我们想通过表的 name 域排序: table.sort(network, function (a,b) return (a.name > b.name) end)
将第一类值函数应用在表中是 Lua 实现面向对象和包机制的关键
|
闭包
names = {
"Peter",
"Paul",
"Mary"}
grades = {Mary = 10, Paul = 7, Peter = 8} function sortbygrade (names, grades) table.sort(names, function (n1, n2) return grades[n1] > grades[n2] -- compare the grades end) end
function newCounter()
local i = 0 return function() -- anonymous function i = i + 1 return i end end c1 = newCounter() print(c1()) --> 1 print(c1()) --> 2 c2 = newCounter() print(c2()) --> 1 print(c1()) --> 3 print(c2()) --> 2
返回一个函数就闭包?拿到引用和不拿到引用的两种情况??
|
非全局函数
Lua 中函数可以作为全局变量也可以作为局部变量
函数作为 table 的域(大部分 Lua 标准库使用这种机制来实现的比如 io.read、math.sin) 1. 表和函数放在一起 Lib = {} Lib.foo = function (x,y) return x + y end Lib.goo = function (x,y) return x - y end 2. 使用表构造函数 Lib = { foo = function (x,y) return x + y end, goo = function (x,y) return x - y end } 3. Lua 提供另一种语法方式 Lib = {} function Lib.foo (x,y) return x + y end function Lib.goo (x,y) return x - y end
当我们将函数保存在一个局部变量内时,我们得到一个局部函数,也就是说局部函
数像局部变量一样在一定范围内有效 方式一 local f = function (...) ... end local g = function (...) ... f() -- external local `f' is visible here ... end
方式二
local function f (...) ... end 声明递归局部函数的方式 local fact fact = function (n) if n == 0 then return 1 else return n*fact(n-1) end end |
正确的尾调用
尾调用是一种类似在函数结尾的 goto 调用,
当函数最后一个动作是调用另外一个函
数时,我们称这种调用尾调用。例如: function f(x) return g(x) end
例子中 f 调用 g 后不会再做任何事情,这种情况下当被调用函数 g 结束时程序不需
要返回到调用者 f;所以尾调用之后程序不需要在栈中保留关于调用者的任何信息 由于尾调用不需要使用栈空间,那么尾调用递归的层次可以无限制的。
Lua 中类似 return g(...)这种格式的调用是尾调用。但是 g 和 g 的参数都可以是复杂
表达式,因为 Lua 会在调用之前计算表达式的值。例如下面的调用是尾调用: return x[i].foo(x[j] + a*b, i + j) |
迭代器与泛型 for
使用闭包来简单实现
function list_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}
iter = list_iter(t) -- creates the iterator while true do local element = iter() -- calls the iterator if element == nil then break end print(element)
end
例子二: 范式for
t = {10, 20, 30}
for element in list_iter(t) do print(element) end
范性 for 的语义
范性 for 的执行过程:
首先,初始化,计算 in 后面表达式的值,表达式应该返回范性 for 需要的三个值: 迭代函数,状态常量和控制变量;与多值赋值一样,如果表达式返回的结果个数不足三 个会自动用 nil 补足,多出部分会被忽略。 第二,将状态常量和控制变量作为参数调用迭代函数(注意:对于 for 结构来说, 状态常量没有用处,仅仅在初始化时获取他的值并传递给迭代函数)。 第三,将迭代函数返回的值赋给变量列表。 第四,如果返回的第一个值为 nil 循环结束,否则执行循环体。 第五,回到第二步再次调用迭代函数。 更精确的来说: for var_1, ..., var_n in explist do block end 等价于 do local _f, _s, _var = explist while true do local var_1, ... , var_n = _f(_s, _var) _var = var_1 if _var == nil then break end block end end 如果我们的迭代函数是 f,状态常量是 s,控制变量的初始值是 a0,那么控制变量将 循环:a1=f(s,a0)、a2=f(s,a1)、……,直到 ai=nil。
无状态的迭代器
多状态的迭代器
将 table作为迭代器的状态常量
真正的迭代器
函数作为参数
local count = 0 allwords(function (w) if w == "hello" then count = count + 1 end end) print(count) |
编译· 运行· 调试
function dofile (filename)
local f = assert(loadfile(filename)) return f() end
loadstring 与 loadfile 相似,只不过它不是从文件里读入 chunk,而是从一个串中读入。
例如: f = loadstring( "i = i + 1") f 将是一个函数,调用时执行 i=i+1。 i = 0 f(); print(i) --> 1 f(); print(i) --> 2
调用别的包里内容:
-- file `foo.lua'
function foo (x) print(x) end
f = loadfile("foo.lua")后,foo 被编译了但还没有被定义,如果要定
义他必须运行 chunk: f() -- defines `foo' foo( "ok") --> ok
如果你想快捷的调用 dostring(比如加载并运行),可以这样
loadstring(s)()
大概与 f = function () i = i + 1 end 等价,但是第二段代码速度更快因为它只需要编译
一次,第一段代码每次调用 loadstring 都会重新编译,还有一个重要区别:loadstring 编 译的时候不关心词法范围: local i = 0 f = loadstring( "i = i + 1") g = function () i = i + 1 end 这个例子中,和想象的一样 g 使用局部变量 i,然而 f 使用全局变量 i;loadstring 总 是在全局环境中编译他的串
require 函数
Lua 提供高级的 require 函数来加载运行库。粗略的说 require 和 dofile 完成同样的功
能但有两点不同: 1. require 会搜索目录加载文件 2. require 会判断是否文件已经加载避免重复加载同一文件。由于上述特征, require 在 Lua 中是加载库的更好的函数。
异常和错误处理
如果在 Lua 中需要处理错误,需要使用 pcall 函数封装你的代码。 第一步:将这段代码封装在一个函数内 function foo () ... if unexpected_condition then error() end ... print(a[i]) -- potential error: `a' may not be a table ... end 第二步:使用 pcall 调用这个函数 if pcall(foo) then -- no errors while running `foo' ... else -- `foo' raised an error: take appropriate actions ... end 当然也可以用匿名函数的方式调用 pcall: if pcall(function () ... end) then ... else ... |
协同程序
协同程序(coroutine)与多线程情况下的线程比较类似:有自己的堆栈,自己的局
部变量,有自己的指令指针,但是和其他协同程序共享全局变量等很多信息。线程和协 同程序的主要不同在于:在多处理器情况下,从概念上来讲多线程程序同时运行多个线 程;而协同程序是通过协作来完成,在任一指定时刻只有一个协同程序在运行,并且这 个正在运行的协同程序只有在明确的被要求挂起的时候才会被挂起
协同的基础
协同有三个状态:挂起态、运行态、停止态。当我们创建一个协同程序时他开始的
状态为挂起态,也就是说我们创建协同程序的时候不会自动运行,可以使用 status 函数 检查协同的状态: co = coroutine.create( function () print( "hi") end) print(co) --> thread: 0x8071d98 print(coroutine.status(co)) --> suspended 函数 coroutine.resume 可以使程序由挂起状态变为运行态: coroutine.resume(co) --> hi 这个例子中,协同体仅仅打印出"hi"之后便进入终止状态: print(coroutine.status(co)) --> dead
管道和过滤器
协同最有代表性的作用是用来描述生产者-消费者问题。我们假定有一个函数在不断 的生产值(比如从文件中读取),另一个函数不断的消费这些值(比如写到另一文件中), 这两个函数如下:
function producer ()
while true do local x = io.read() -- produce new value send(x) -- send to consumer end end
function consumer ()
while true do local x = receive() -- receive from producer io.write(x, "\n") -- consume new value end end
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 可以调用: p = producer() f = filter(p) consumer(f) 或者: consumer(filter(producer()))
用作迭代器的协同
非抢占式多线程
|
完整示例
马尔可夫链算法
|
第二篇 tables 与 objects
|
数据结构
table 是 Lua 中唯一的数据结构,其他语言所提供的其他数据结构比如:arrays、 records、lists、queues、sets 等,Lua 都是通过 table 来实现,并且在 lua 中 table 很好的实现了这些数据结构。
数组
通常我们初始化数组的时候就间接的定义了数组的大小,比如下面的代码: a = {} -- new array for i=1, 1000 do a[i] = 0 end squares = {1, 4, 9, 16, 25, 36, 49, 64, 81} 这样的语句中数组的大小可以任意的大,甚至几百万。 阵和多维数组 Lua 中主要有两种表示矩阵的方法,第一种是用数组的数组表示。也就是说一个表 的元素是另一个表。例如,可以使用下面代码创建一个 n 行 m 列的矩阵: mt = {} -- create the matrix for i=1,N do mt[i] = {} -- create a new row for j=1,M do mt[i][j] = 0 end end
链表
字符串缓冲
这个问题并不是 Lua 特有的:其它的采用垃圾收集算法的并且字符串不可变的语言
也都存在这个问题。Java 是最著名的例子,Java 专门提供 StringBuffer 来改善这种情况。 |
Metatables and Metamethods
默认创建一个不带 metatable 的新表
t = {} print(getmetatable(t)) --> nil 可以使用 setmetatable 函数设置或者改变一个表的 metatable t1 = {} setmetatable(t, t1) assert(getmetatable(t) == t1) 任何一个表都可以是其他一个表的 metatable,一组相关的表可以共享一个 metatable (描述他们共同的行为)。一个表也可以是自身的 metatable(描述其私有行为)。
算术运算的 Metamethods
首先,我们实现一个原型和一
个构造函数,他们共享一个 metatable: -- create a namespace Window = {} -- create the prototype with default values Window.prototype = {x=0, y=0, width=100, height=100, } -- create a metatable Window.mt = {} -- declare the constructor function function Window.new (o) setmetatable(o, Window.mt) return o end |
环境
为了简化操作,
Lua
将环境本身存储在一个全局变量
_G
中,(
_G._G
等
于 _G )。例如,下面代码打印在当前环境中所有的全局变量的名字: for n in pairs(_G) do print(n) end 这一章我们将讨论一些如何操纵环境的有用的技术。
使用动态名字访问全局变量
|
Packages
Lua
并没有提供明确的机制来实现
packages
。然而,我们通过语言提供的基本的机
制很容易实现他。主要的思想是:像标准库一样,使用表来描述 package
基本方法
complex = {} function complex.new (r, i) return {r=r, i=i} end -- defines a constant `i' complex.i = complex.new(0, 1)
私有成员(
Privacy
)
我们可以将
package
内的所有函数都声明为局部的,最后将他们放在最终的表中。按照这种方法,上面的
complex package
修改如下:
local function
checkComplex (c)
if not ((type(c) == "table" ) and tonumber(c.r) and tonumber(c.i)) then error( "bad complex number" , 3) end
end
local function new (r, i) return {r=r, i=i} end local function add (c1, c2) checkComplex(c1); checkComplex(c2); return new(c1.r + c2.r, c1.i + c2.i) end ... complex = { new = new, add = add, sub = sub, mul = mul, div = div, }
包与文件
require "four";
|
面向对象程序设计
Lua 中的表不仅在某种意义上是一种对象。像对象一样,表也有状态(成员变量); 也有与对象的值独立的本性,特别是拥有两个不同值的对象( table )代表两个不同的对 象;一个对象在不同的时候也可以有不同的值,但他始终是一个对象;与对象类似,表 的生命周期与其由什么创建、在哪创建没有关系。对象有他们的成员函数,表也有: Account = {balance = 0} function Account.withdraw (v) Account.balance = Account.balance - v end 这个定义创建了一个新的函数,并且保存在 Account 对象的 withdraw 域内,下面我 们可以这样调用: Account.withdraw(100.00)
一个灵活的方法是:定义方法的时候带上一个额外的参数,来表示方法作用的对象。
这个参数经常为 self 或者 this : function Account.withdraw (self, v) self.balance = self.balance - v end 现在,当我们调用这个方法的时候不需要指定他操作的对象了: a1 = Account; Account = nil ... a1.withdraw(a1, 100.00) -- OK
。
Lua
也提供了通
过使用冒号操作符来隐藏这个参数的声明。我们可以重写上面的代码: function Account:withdraw (v) self.balance = self.balance - v end 调用方法如下: a:withdraw(100.00)
类
Lua 不存在类的概念,每个对象定义他自己的行为并拥有自己的形状 在 Lua 中,使用前面章节我们介绍过的继承的思想,很容易实现 prototypes. 更明确 的来说,如果我们有两个对象 a 和 b ,我们想让 b 作为 a 的 prototype 只需要: setmetatable(a, {__index = b})
继承
假定我们有一个基类
Account
:
Account = {balance = 0} function Account:new (o) o = o or {} setmetatable(o, self) self.__index = self return o end function Account:deposit (v) self.balance = self.balance + v end function Account:withdraw (v) if v > self.balance then error "insufficient funds" end self.balance = self.balance - v end 我们打算从基类派生出一个子类 SpecialAccount ,这个子类允许客户取款超过它的 存款余额限制,我们从一个空类开始,从基类继承所有操作: SpecialAccount = Account:new()
到现在为止,
SpecialAccount
仅仅是
Account
的一个实例。现在奇妙的事情发生了:
s = SpecialAccount:new{limit=1000.00} SpecialAccount 从 Account 继承了 new 方法,当 new 执行的时候, self 参数指向 SpecialAccount 。所以, s 的 metatable 是 SpecialAccount , __index 也是 SpecialAccount 。 这样, s 继承了 SpecialAccount ,后者继承了 Account 。当我们执行: s:deposit(100.00) Lua 在 s 中找不到 deposit 域,他会到 SpecialAccount 中查找,在 SpecialAccount 中 找不到,会到 Account 中查找。使得 SpecialAccount 特殊之处在于,它可以重定义从父 类中继承来的方法: function SpecialAccount:withdraw (v) if v - self.balance >= self:getLimit() then error "insufficient funds" end self.balance = self.balance - v end function SpecialAccount:getLimit () return self.limit or 0 end
多重继承
实现的关键在于:将函数用作
__index
。记住,当一个表的
metatable
存在一个
__index
函数时,如果 Lua 调用一个原始表中不存在的函数, Lua 将调用这个 __index 指定的函数。这样可以用 __index 实现在多个父类中查找子类不存在的域。
Single-Method
的对象实现方法
一个保存状态的迭代子函数就是一个
single-method
对象。
function
newObject (value) return function (action, v) if action == "get" then return value elseif action == "set" then value = v else error( "invalid action" ) end end end 使用起来很简单: d = newObject(0) print(d( "get" )) --> 0 d( "set" , 10) print(d( "get" )) --> 10 |
数学库
Table
库
另一个有用的函数是
table.sort
。他有两个参数:存放元素的
array
和排序函数。排序
函数有两个参数并且如果在 array 中排序后第一个参数在第二个参数前面,排序函数必 须返回 true 。如果未提供排序函数, sort 使用默认的小于操作符进行比较。
String
库
记住:
Lua
中的字符串是恒定不变的。
String.sub
函数以及
Lua
中其他的字符串操作
函数都不会改变字符串的值,而是返回一个新的字符串。
下面的表列出了
Lua
支持的所有字符类:
. 任意字符 %a 字母 %c 控制字符 %d 数字 %l 小写字母 %p 标点字符 %s 空白符 %u 大写字母 %w 字母和数字 %x 十六进制数字 %z 代表 0 的字符 上面字符类的大写形式表示小写所代表的集合的补集。例如, '%A' 非字母的字符: print(string.gsub( "hello, up-down!" , "%A" , "." )) --> hello..up.down. 4 可以使用修饰符来修饰模式增强模式的表达能力, Lua 中的模式修饰符有四个: + 匹配前一字符 1 次或多次 * 匹配前一字符 0 次或多次 - 匹配前一字符 0 次或多次 ? 匹配前一字符 0 次或 1 次
Capture
3
是这样一种机制:可以使用模式串的一部分匹配目标串的一部分。将你想捕
获的模式用圆括号括起来,就指定了一个 capture 。 在 string.find 使用 captures 的时候,函数会返回捕获的值作为额外的结果。这常被用 来将一个目标串拆分成多个: pair = "name = Anna" _, _, key, value = string.find(pair, "(%a+)%s*=%s*(%a+)" ) print(key, value) --> name Anna %d+' 匹配一个或多个数字(整数): i, j = string.find( "the number 1298 is even" , "%d+" ) print(i,j) --> 12 15 |
IO
库
简单模式和完全模式
获取或者设置文件句柄后进行的操作 --
|