脚本语言Lua简单入门学习笔记
2017-01-18 16:49
701 查看
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 次 Capture3是这样一种机制:可以使用模式串的一部分匹配目标串的一部分。将你想捕 获的模式用圆括号括起来,就指定了一个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 库 简单模式和完全模式 获取或者设置文件句柄后进行的操作 -- |
相关文章推荐
- Lua脚本语言最简单入门
- Lua脚本语言入门学习其应用教程
- Lua脚本语言学习笔记
- Lua游戏脚本语言入门学习指导
- eLua学习第三课:Lua脚本语言的基础入门教程
- 脚本语言lua笔记(2)快速入门
- 【学习笔记】系列七:Lua 语言 15 分钟快速入门
- Lua游戏脚本语言入门学习指导 .
- Lua脚本语言入门笔记
- lua脚本语言学习笔记(二)---LUA函数\数组\迭代器
- Lua嵌入式脚本语言入门学习指导
- Lua入门系列----pil学习笔记之Type and Values (2)
- Lua入门系列----pil学习笔记之Type and Values (2)
- Linux操作系统下C语言编程入门学习笔记(...20090601(第一遍学完待整理)...)
- Lua入门系列----pil学习笔记之Getting Start
- LUA脚本语言入门
- ROR 学习笔记系列二 基于ROR的简单Hello World程序入门
- 【转】Lua脚本语言入门
- Lua入门系列----pil学习笔记之 Type and Values (1)
- Lua脚本语言入门(目前魔兽使用的可以写在宏内的语言)