Lua笔记 第一至第八章
转自: http://www.cnblogs.com/summericeyl/archive/2011/06/02/2068507.html
- Lua特性: 可扩展性, 简单, 高效率, 与平台无关
- 可通过新类型和函数来扩展其功能.
1.1 Chunks
- Chunk是一系列语句
- lua交互运行, CTRL-Z退出
- lua -la -lb
- 先运行a, 然后运行b(-l选项会调用require)
- -i选项要求Lua运行指定Chunk后进入交互模式
- dofile函数, 连接外部Chunk
- lua -la -lb
1.2 全局变量
- 赋值即创建, 未初始化的值为nil
1.3 词法约定
- 保留字: and break do else elseif end false for function if in local nil not or repeat return then true until while
- Lua 大小写敏感
- 单行注释: --
- 多行注释: --[ [ --]]
1.4 命令行方式
- lua [options] [script [args]]
- -e 直接将命令传入Lua
- lua -e "print(math.sin(12))"
- -l: 加载一个文件
- -i: 进入交互模式
- _PROMPT 设置一个标量作为交互模式的提示符
lua -i -e "_PROMPT=' lua> '"
- LUA_INIT环境变量: @filename 表示加载指定文件. 如无@, 则假定filename为Lua代码文件并且运行它
- 脚本名索引为0, 参数从1开始增加.
lua -e "sin=math.sin" script a b arg[-3] = "lua" arg[-2] = "-e" arg[-1] = "sin=math.sin" arg[0] = "script" arg[1] = "a" arg[2] = "b"
- -e 直接将命令传入Lua
第2章 类型和值
- 八个基本类型: nil, boolean, number, string, userdata, function, thread, table
2.1 Nil
- 全局变量未被赋值之前
2.2 Booleans
- Lua所有的值都可以作为条件, 除了false和nil为假, 其他都为真
2.3 Number
- 没有整数
2.4 Strings
- 可使用单引号或双引号表示字符串
- 转义符序列: \b 后退 \f 换页 \n 换行 \r 回车 \t 制表 ...
- \[ \] 左中/左右括号
- 还可以使用 [ [...]] 表示字符串, 这样可以多行, 可以嵌套也不会解释转义序列
- 运行时, Lua自动在 string 和 numbers 之间自动进行类型转换, 使用算术操作符, string自动转换成数字, 或者其他需要字符串的时候, 数字转换成字符串
- string转换成数字函数 tonumber(), 相反则为 tostring
2.5 Functions
- 函数可以作为函数的参数, 也可以作为函数的返回值.
- Lua可以调用lua或者C实现的函数, 标准库包括 string库, table库, I/O库, OS库, 算术库, debug库
2.6 Userdata and Threads
- userdata可以将C数据存放在Lua变量中, userdata除了赋值和相等比较外没有预定义的操作.
- userdata用来描述应用程序或者使用C实现的库创建的新类型.
第3章 表达式
3.1 算术表达式
- 二元: + - * / ^(加减乘除幂)
- 一元: - 负值
3.2 关系运算符
- < > <= >= == ~=
- 回 false和true
- nil只能和自己相等
- 通过引用比较tables, userdata, functions, 即仅当两者表示同一个对象时相等
- 比较字符串按字母顺序比较
- 注: "2" < "15" 返回 false 字母顺序
3.3 逻辑运算发
- and or not
- 只有 false 和 nil为假, 其他为真, 0也为真
- and 的优先级比 or高
3.4 连接运算符
- .. ---- 字符串连接
3.5 优先级
- ^
- not -
- * /
- + -
- ..
- < > <= >= ~= ==
- and
- or
- 除了^和..之外, 所有二元运算符都是左连接的
3.6 表的结构
- 最简单的构造函数是{}, 用来创建一个空表, 可以直接初始化数组
days = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}
- 第一个元素索引为1
- 即"Sunday" = days[1]
- 第一个元素索引为1
- 元素可以为表达式
- 初始化一个表为record
a = {x = 0, y = 0} <--> a={}; a.x=0; a.y=0
- 不管何种方式创建table, 我们都可以向表中添加或者删除任何类型的域, 构造函数仅仅影响表的初始化
- 给元素赋值nil可以移除该元素
- 每次调用构造函数, Lua都会创建一个新的table, 可以使用table构造一个list
- 在同一个构造函数中可以混合列表风格和record风格进行初始化
- 用[expression]显示的表示将被初始化的索引
opnames = {["+"] = "add", ["-"] = "sub", ["*"] = "mul", ["/"] = "div"} {x = 0, y = 0} <--> {["x"] = 0, ["y"] = 0} {"red", "green", "blue"} <--> {[1]= "red", [2] = "green", [3] = "blue"}
- 如果想从下标0开始
days = { [0] = "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}
- 构造函数中域分隔符可用"," 或者";"表示, 一般";"分割不同类型的表元素
- 用[expression]显示的表示将被初始化的索引
第4章 基本语法
4.1 赋值语句
- 可同时对多个变量同时赋值
a, b = 10, 2*x <--> a = 10; b = 2*x
- 先计算右边所有的值, 然后再进行赋值操作, 所以可以进行交换
x, y = y, x a[i], a[j] = a[j], a[i]
- 当变量个数和值的个数不一致
- a. 变量个数 > 值的个数 按变量个数补足nil
- b. 变量个数 < 值的个数 多余的值会被忽略
- 先计算右边所有的值, 然后再进行赋值操作, 所以可以进行交换
- 函数返回值
- a, b = f()
- f()如果返回两个值, 第一个赋值给a, 第二个赋值给b
- a, b = f()
4.2 局部变量和代码块(block)
- local 创建一个局部变量, 与全局变量不同, 局部变量只在被声明的那个代码块内有效.
- 代码块: 一个控制结构内, 一个函数体, 或者一个chunk(变量被声明的那个文件或者文本串)
- 尽可能使用局部变量: 1. 避免命名冲突, 2. 访问局部变量比全局变量快
4.3 控制结构语句
- if语句, 有三种形式:
if condition then then-part end; if condition 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 statement; end;
- repeat-until 语句
repeat statements; until conditions
- for 语句
- 两大类:
- 第一, 数值 for 循环
for var = exp1, exp2, exp3 do loop-part end
- exp1(初始值), exp2(终止值), exp3表示进行的计算, 默认exp3 step = 1
- 控制变量var是局部变量自动被声明, 并且只在循环内有效
- 退出循环用break语句
- 第二, 范型for循环
for i, v in ipairs(a) do print(v) end
- 范型for遍历迭代子函数返回的每一个值.
- 第一, 数值 for 循环
4.4 break和return语句
- break 退出当前循环
- return 函数返回结果.
- break和return只能出现在block的结尾一句
- 如果要在中间使用, 可以使用 do..end来实现
第5章 函数
- 语法:
function func_name (arguments-list) statements-list; end;
- 当函数只有一个参数并且这个参数是字符串或者表结构的时候, ()可有可无
print "Hello world" dofile "a.lue" -- 打印多行 -- print [[a multi-line message]] f{x = 10, y = 20} type{}
- 面向对象调用函数的语法, 如 o:foo(x)和o.foo(o,x) 是等价的
5.1 多返回值
- Lua 函数中, 在return后列出要返回的值列表即可返回多值, 如: return m, m1
- 注意返回值的几个情况:
- 赋值语句, 如果调用函数表达式作为最后一个参数或者仅有一个参数, 根据变量个数函数尽可能返回多个值, 不足补nil
- 其他情况, 函数调用仅返回第一个值
- 例如 foo2() 返回 'a', 'b'
- x, y = foo2(), 20 -- x = 'a', y =20
- 另外, 函数调用表达式作为函数参数被调用的时候, 和多值赋值是相同的
print(foo2()) --> a b print(foo2(), 1) --> a 1 print(foo2().."x") --> ax
- 函数调用表达式在表构造函数中初始化时, 和多值赋值时相同
a = {foo2()} -- a = {'a', 'b'} a = {foo2(), 4} -- a = {'a', 4}
- return f()表示返回"f()的返回值", 使用圆括号强制返回一个值
print(foo2()) --> a b print((foo2())) --> a
- 一个return语句如果使用圆括号将返回值括起来也将导致返回一个值
- 函数多值返回的特殊函数unpack, 接受一个数组作为输入参数, 返回数组的所有元素
- Lua中如果想调用可变参数的可变函数只需要: f(unpack(a))
5.2 可变参数
- Lua 和C语言类似在函数参数列表中使用三点(...)表示函数有可变的参数
- Lua将函数的参数放在一个叫arg的表中, 除了参数之外, arg表中还有一个域n表示参数的个数
- 需要几个固定参数加上可变参数
function g(a, b, ...) end 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 = {n=2}
- 哑元: 下划线
local _, x = string.find(s, p)
- 函数 string.format: 文本格式化, 类似C的sprintf函数
5.3 命名函数
第6章 再论函数
- 函数可以没有名字, 匿名的.函数名实际上是一个指向函数的变量
a = {p = print} a.p("Hello world") print = math.sin a.p(print(1)) sin = a.p sin(10, 20)
- 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" }, } -- 进行排序: -- table.sort(network, function(a, b) return (a.name > b.name) end)
6.1 闭包
- 当一个函数内部嵌套另一个函数定义时, 内部的函数体可以访问外部的函数的局部变量
names = {"Peter", "Paul", "Mary"} grades = {Mary = 10, Paul = 7, Peter = 8} table.sort(names, function (n1, n2) return grades[n1] > grades[n2] end)
- 创建函数实现该功能:
function sortbygrade(names, grades) table.sort(names, function(n1, n2) return grades[n1] > grades[n2] end) end
- 在函数内部的匿名函数访问的变量为外部的局部变量或者upvalue
6.2 非全局函数
- 表和函数放在一起
Lib = {} Lib.foo = function(x, y) return x+y end Lib.goo = function(x, y) return x-y end
- 使用表构造函数
Lib = { Lib.foo = function(x, y) return x+y end Lib.goo = function(x, y) return x-y end }
- 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() ... end
- 方式二
local function f(...) ... end
- 方式一:
- 使用非递归局部函数是要先定义而后使用
6.3 正确的尾调用
- 函数的最后一个动作是调用另外一个函数时, 我们称这种调用为尾调用
function f(x) return g(x) end
- 尾调用不需要使用栈空间, 所以尾调用递归的层次可以无限制的
- 可以将尾调用理解成一种goto
- 见状态机, 房间四个门的例子, 适合于游戏中寻路
第7章 迭代器与范型
- 闭包是一个内部函数, 它可以访问一个或者多个外部函数的外部局部变量. 每次闭包的成功调用后这些外部局部变量都保存他们的值(状态)
- 一个典型的闭包的结构包含两个函数: 一个是闭包自己, 另一个是工厂(创建闭包的函数)
- 我们为list写一个简单的迭代器
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
- list_iter是一个工厂, 每次调用都会调用一个新的闭包(迭代器本身). 闭包保存内部局部变量(t, i, n)
- 当list中没有值时, 返回nil
t = {10, 20, 30} iter = list_iter(t) while true do local element = iter() if element == nil then break end print(element) end
- 用于范性for语句
t = {10, 20, 30} for element in list_iter(t) do print(element) end
- 这里为迭代循环处理所有的蒲记(bookkeeping): 首先调用迭代工厂; 内部保留迭代函数, 因为我们不需要iter变量; 然后在每一个新的迭代处调用迭代器函数, 当迭代器返回nil时循环结束
7.2 范型 for 的语义
for <var-list> in <exp-list> do <body> end
- var-list 是以一个或多个逗号分隔的变量名列表. <exp-list> 是以一个或多个逗号分隔的表达式列表, 通常情况下exp-list只有一个值: 迭代工厂的调用
for k,v in pairs(t) do print(k, v) end
- 也可以只有一个变量
for line in io.lines() do io.write(line, '\n') end
- 我们称第一个变量为控制变量, 其值为nil时循环结束
- 下面我们看看范型for的执行过程
- 首先, 初始化, 计算in后面表达式的值, 表达式应该返回范性for需要的三个值: 迭代函数, 状态常量, 控制变量. 如返回的结果不足三个会自动用nil补足
- 第二, 将状态常量和控制变量作为参数调用迭代函数(对for结构来说, 状态常量没有用处, 仅仅在初始化时获取他的值并传递哦给迭代函数)
- 第三, 将迭代函数返回的值赋给变量列表
- 第四, 如果返回的第一个值为nil循环结束, 否则执行循环体
- 第五, 回到第二步再次调用迭代函数
- 如我们的迭代函数是f, 状态常量是s, 控制变量的初始值为a0, 则循环: a1 = f(s, a0), a2 = f(s, a1), ... 知道 ai = nil
7.3 无状态的迭代器
- 无状态迭代器是指不保存任何状态的迭代器, 在循环中我们可以利用无状态迭代器避免创建闭包花费额外的代价
- 每一次迭代, 迭代器都是用两个变量(状态变量和控制变量)的值作为参数被调用, 一个无状态的迭代器只利用这两个值可以获取下一个元素
a = {"one", "two", "three"} for i, v in ipairs(a) do print(i, v) end
- ipairs的实现
function iter (a, i) i = i + 1 local v = a[i] if v then return i, v end end function ipairs(a) return iter, a, 0 end
7.4 多状态的迭代器
- 迭代器保存多个状态信息, 最简单的方法是用闭包, 还有一种方法就是将所有的状态信息封装到table内, 将table作为迭代器的状态常量.
local iterator function allwords() local state = {line = io.read(), pos = 1} return iterator, state end function iterator (state) while (state.line) local s, e = string.find(state.line, "%w+", state.pos) if a then state.pos = e + 1 return string.sub(state.line, s, e) else state.line = io.read() state.pos = 1 end end return nil end
- 尽可能使用无状态迭代器, 不行则尽可能使用闭包, 尽量不要使用table这种方式
7.5 真正的迭代器
function allwords (f) for l in io.lines() do for w in string.gfind(l, "%w+") do f(w) end end end
- 打印单词 allwords(print)
- 匿名函数
local count = 0 allwords(function (w) if w == "hello" then count = count + 1 end end) print(count) for循环 local count = 0 for w in allwords() do if w == "hello" then count = count + 1 end print(count)
第8章 编译.运行.错误信息
- 完全简单的功能dofile比较方便, 读入文件编译并且执行.
- loadfile读取文件编译但不执行, 如发生错误, 返回nil和错误信息, loadfile只需要编译一次, 但可多次运行. dofile每次都要编译
- loadstring和loadfile类似, 只是它从一个串中读入
f = loadstring("i = i + 1") i = 0 f() -->执行该代码 i=1 f() --> i=2
- Lua把每一个chunk都作为一个匿名函数处理, chunks可以定义局部变量也可以返回值
f = loadstring("local a = 10; return a + 20") print(f())
- loadstring和loadfile都不会抛出错误, 如果发生错误他们将返回nil加上错误信息
- 如果你想快捷的调用dostring, 可以这样: loadstring(s)()
- 为了返回更清楚的错误信息可以使用assert: assert(loadstring(s))()
- 使用loadstring, 可以只编译一次, 多次使用, 另外它不关心词法范围
8.1 require 函数
- require函数加载运行库. 与dofile完成相同的功能, 但有两点不同:
- require 会搜索目录加载文件
- require 会判断是否文件已经加载避免重复加载同一文件.
- require的路径是一个模式列表, 例如: ?;?.lua;c:\windows\?;/user/local/lua/?/?.lua
- 为了确定路径, Lua首先检查全局变量LUA_PATH是否为一个字符串, 而后判断这个字符串是否为路径. 如果失败则使用固定的路径 "?;?.lua"
- Lua保留一张所有已加载的文件列表, 全局变量_LOADED访问文件名列表, 通过设置其值为nil, 可以让require重新加载相同的文件, 比如文件"foo"
_LOADED["foo"] = nil
- 路径模式可以不包含问号, 而只是一个固定的路劲: ?;?.lua;/usr/local/default.lua
- _REQUIREDNAME保存被required的虚文件的名称
- 如, 我们把路径设置为 "/usr/local/default.lua", 这样每次调用require都会运行newrequire.lua, 然后在使用_REQUIREDNAME的值实际加载required的文件
8.2 C Package
- 在Lua提示符下运行print(loadlib())看返回的结果, 如果显示bad arguments则说明你的发布版本支持动态连接机制, 否则说明动态连接机制不支持或者没有安装
- Lua的loadlib函数内提供了所有动态连接的功能, 有两个参数: 库的绝对路径和初始化函数
local path = "/usr/local/lua/lib/libluasocket.so" local f = loadlib(path, "luaopen_socket")
- 加载指定的库, 并不打开库(即没有调用初始化函数), 返回初始化函数作为Lua的一个函数
f() --- 打开库
8.3 错误
- 你可以通过调用error函数显示地抛出错误, error的参数是要抛出的错误信息
print "enter a number:" n = io.read("*number") if not n then error("invalid input") end
- Lua提供了专门的内置函数来完成上面类似的功能
print "enter a number:" n = assert(io.read("*number"), "invalid input")
- assert 首先检查第一个参数, 若没问题, assert不作任何事情, 否则, assert以第二个参数作为错误信息抛出. 第二个参数是可选的
- 对于程序逻辑上能够避免的异常, 以抛出错误的方式处理之, 否则返回错误代码
8.4 异常和错误处理
- 如果在lua中需要处理错误, 使用pcall函数封装你的代码
- pcall在保护模式下执行函数内容, 同时捕获所有的异常和错误, 若一切正常, 返回true以及"被执行函数"的返回值, 否则返回nil和错误信息
- 传递给error的任何信息都会被pcall返回
8.5 错误信息和回跟踪(Tracebacks)
- 不管什么情况下, Lua都尽可能清楚的描述问题发生的缘由
- 函数error还可以有第二个参数, 表示错误发生的层级
- cpcall实现功能, 两个参数: 调用函数, 错误处理函数
- 最常用的debug处理函数: debug.debug和debug.traceback
- 最简单的构造函数是{}, 用来创建一个空表, 可以直接初始化数组