简介
Lua特有的特征:
- 可扩展性。Lua 的扩展性非常卓越,以至于很多人把 Lua 用作搭建领域语言的 工具(注:比如游戏脚本)。Lua 被设计为易于扩展的,可以通过 Lua 代码或者 C 代码扩展,Lua 的很多功能都是通过外部库来扩展的。Lua 很容易与 C/C++、java、 fortran、Smalltalk、Ada,以及其他语言接口。
- 简单。Lua 本身简单,小巧;内容少但功能强大,这使得 Lua 易于学习,很容 易实现一些小的应用。他的完全发布版(代码、手册以及某些平台的二进制文件)仅用一张软盘就可以装得下。
- 高效率。Lua 有很高的执行效率,统计表明 Lua 是目前平均效率高的脚本语 言。
- 与平台无关。Lua 几乎可以运行在所有我们听说过的系统上,如 NextStep、 OS/2、PlayStation II (Sony)、Mac OS-9、OS X、BeOS、MS-DOS、IBM mainframes、EPOC、PalmOS、MCF5206eLITE Evaluation Board、RISC OS,及所有的 Windows 和 Unix。Lua 不是通过使用条件编译实现平台无关,而 是完全使用 ANSI (ISO) C,这意味着只要你有 ANSI C 编译器你就可以编译并 使用 Lua。
loadstring 和 load 函数
5.2以下
local f = assert(loadstring("print('Hello World!')"))
f()
5.2后废除loadstring,改为load
local f = assert(load("print('Hello World!')"))
f()
require 函数
Lua 提供高级的 require 函数来加载运行库。粗略的说 require 和 dofile 完成同样的功 能但有两点不同:
- require 会搜索目录加载文件
- require 会判断是否文件已经加载避免重复加载同一文件
Lua 保留一张所有已经加载 的文件的列表(使用 table 保存),可以通过全局变量_LOADED 访问文件名列表
模拟Continue
list = {1, 2, 3, 4, 5}
for i, v in ipairs(list) do
repeat
if i == 3 then
break
end
-- todo
print(i)
until true
end
输出:1 2 4 5
goto语句
i = 0
::start::
print(i)
i = i + 1
if i < 5 then
goto start
end
输出:0 1 2 3 4
协同程序
协同程序(coroutine)与多线程情况下的线程比较类似:有自己的堆栈,自己的局 部变量,有自己的指令指针(IP,instruction pointer),但与其它协同程序共享全局变量 等很多信息。线程和协同程序的主要不同在于:在多处理器情况下,从概念上来讲多线 程程序同时运行多个线程;而协同程序是通过协作来完成,在任一指定时刻只有一个协 同程序在运行,并且这个正在运行的协同程序只在必要时才会被挂起。
co = coroutine.create(function ()
print("hi")
end)
print(co) --> thread: 0x8071d98
coroutine有三种状态:
- suspended:挂起
- running:运行中
- dead:停止
查看状态:
coroutine.status(co)
执行coroutine:
coroutine.resume(co)
基本代码:
co = coroutine.create(function(count)
for i = 1, count do
print(i)
coroutine.yield(i) --> 返回i
end
end)
print(co)
print(coroutine.status(co))
print("resume:", coroutine.resume(co, 10)) --> 传入参数10,返回第一个参数bool(是否执行成功),后续参数为yield返回值
print(coroutine.status(co))
print("resume:", coroutine.resume(co))
字符串连接
strs = {}
for i = 1, 10 do
table.insert(strs, i)
end
print(table.concat(strs)) --> 12345678910
print(table.concat(strs, "\t")) --> 1 2 3 4 5 6 7 8 9 10
序列化
function serialize(o, i)
if i == nil then
io.write(tostring(o), " = ")
end
i = i or 1
if type(o) == "number" then
io.write(o)
elseif type(o) == "string" then
io.write(string.format("%q", o))
elseif type(o) == "table" then
io.write("{\n")
for k, v in pairs(o) do
for c=1, i do
io.write(" ")
end
io.write("[")
serialize(k)
io.write("] = ")
serialize(v, i + 1)
io.write(",\n")
end
for c=1, i - 1 do
io.write(" ")
end
io.write("}")
end
end
Metatables and Metamethods
基础代码,外加__tostring
Set = {}
Set.mt = {}
function Set.new(t)
t = t or {}
local set = {}
setmetatable(set, Set.mt)
for _, l in ipairs(t) do
set[l] = true
end
return set
end
function Set.tostring(set)
local s = "{"
local sep = ""
for e in pairs(set) do
s = s .. sep .. e
sep = ", "
end
return s .. "}"
end
function Set.print(s)
print(Set.tostring(s))
end
s1 = Set.new({10, 20, 30, 50})
s2 = Set.new({30, 10})
print(getmetatable(s1))
print(getmetatable(s2))
-- 可以发现,s1 跟 s2 的metatable 指向的引用是相同的
Set.mt.__tostring = Set.tostring
print(s1)
关于__add
-- 相加
function Set.union(a, b)
local res = Set.new()
for k in pairs(a) do
res[k] = true
end
for k in pairs(b) do
res[k] = true
end
return res
end
Set.mt.__add = Set.union
Set.print(s1 + s2)
关于__mul
-- 相乘(并集)
function Set.intersection(a, b)
local res = Set.new()
for k in pairs(a) do
res[k] = b[k]
end
return res
end
Set.mt.__mul = Set.intersection
Set.print(s1 * s2)
关于__le(小于或等于), __lt(小于),__eq(等于)
-- 小于等于
function Set.le(a, b)
for k in pairs(a) do
if not b[k] then
return false
end
end
return true
end
-- 小于
function Set.lt(a, b)
return a <= b and not (b <= a)
end
-- 等于
function Set.eq(a, b)
return a <= b and b <= a
end
Set.mt.__lt = Set.lt
Set.mt.__le = Set.le
Set.mt.__eq = Set.eq
print(s1 <= s2)
print(s1 < s2)
print(s1 >= s2)
print(s1 > s2)
print(s1 * s2 == s2)
关于__metatable
setmetatable/getmetatable 函数也会使用 metafield,在这种情况下,可以保护metatables。假定你想保护你的集合使其使用者既看不到也不能修改 metatables。如果你 对 metatable 设置了__metatable 的值,getmetatable 将返回这个域的值,而调用 setmetatable 将会出错
Set.mt.__metatable = "not your business"
print(getmetatable(s1)) --> not your business
setmetatable(s1, {}) --> error
关于__index
当我们访问一个表的不存在的域,返回结果为 nil,这是正确的,但并不 一定正确。实际上,这种访问触发 lua 解释器去查找__index metamethod:如果不存在, 返回结果为 nil;如果存在则由__index metamethod 返回结果。
Window = {}
Window.prototype = {x = 0, y = 0, width = 100, height = 100}
Window.mt = {}
function Window.new(o)
setmetatable(o, Window.mt)
return o
end
-- 可以是一个函数
Window.mt.__index = function(table, key)
return Window.prototype[key]
end
w = Window.new({x = 10, y = 10})
print("x:", w.x, "y:", w.y, "width:", w.width, "height:", w.height)
--[[ 当 Lua 发现 w 不存在域 width 时,但是有一个 metatable 带有__index 域,
Lua 使用 w(the table)和 width(缺少的值)来调用__index metamethod,
metamethod 则通过访问 原型表(prototype)获取缺少的域的结果。
--]]
-- __index metamethod 不需要非是一个函数,他也可以是一个表
Window.mt.__index = Window.prototype
w = Window.new({x = 20, y = 20})
print("x:", w.x, "y:", w.y, "width:", w.width, "height:", w.height)
关于__newindex
__newindex metamethod 用来对表更新,__index 则用来对表访问。
下面是一个把table修改成只读的方法:
function readOnly(t)
local p = {}
local mt = {
__index = t,
__newindex = function(t, k, v)
error("attempt to update a read-only table", 2)
-- 二个参数 2,将错误信息返回给企图执行的地方(报错指向18行而不是8)
end
}
setmetatable(p, mt)
return p
end
t = readOnly({1, 2, 3})
print(t[1])
t[2] = 4 --> attempt to update a read-only table
关于全局 _G
Lua 用一个名为 environment 普通的表来保存所有的全局变量。(更精确的说,Lua 在一系列的environment 中保存他的“global”变量,但是我们有时候可以忽略这种多样 性)这种结果的优点之一是他简化了 Lua 的内部实现,因为对于所有的全局变量没有必 要非要有不同的数据结构。另一个(主要的)优点是我们可以像其他表一样操作这个保存 全局变量的表。为了简化操作,Lua 将环境本身存储在一个全局变量_G 中,(_G._G 等 于_G)。
下面可以遍历打印出_G包含了那些变量:
for n in pairs(_G) do
print(n)
end
防止其他人把一些局部变量写成全局,可以用到__index,__newindex:
setmetatable(_G, {
__index = function(_, k)
error("attempt to write to undeclared variable "..k, 2)
end,
__newindex = function(_, k, v)
error("attempt to read undeclared variable "..k.." = "..v, 2)
end
})
a = 10 --> attempt to read undeclared variable a = 10
print(a) --> attempt to write to undeclared variable a
但是我们如何声明一个新的变量呢?使用 rawset,可以绕过 metamethod:
function declare (name, initval)
rawset(_G, name, initval or false)
end
但是现在,为了测试一个变量是否存在,我们不能简单的比较他是否为 nil。如果他 是 nil 访问将抛出错误。所以,我们使用 rawget 绕过 metamethod:
if rawget(_G, var) == nil then
-- 'var' is undeclared
...
end
setfenv
当我们在全局环境中定义变量时经常会有命名冲突,尤其是在使用一些库的时候,变量声明可能会发生覆盖,这时候就需要一个非全局的环境来解决这问题。setfenv函数可以满足我们的需求。
setfenv(f, table):设置一个函数的环境
- 当第一个参数为一个函数时,表示设置该函数的环境
- 当第一个参数为一个数字时,1代表当前函数,2代表调用自己的函数,3代表调用自己的函数的函数,以此类推
local P = {}
setfenv(1, P)
print("abc") --> attempt to call global `print' (a nil value)
这时候可以使用setmetatable来继承_G:
local P = {}
setmetatable(P, {__index = _G})
setfenv(1, P)
print("abc") --> abc
类
PA = {
desc = "PA"
}
function PA.new(o)
o = o or {}
setmetatable(o, {__index = PA})
return o
end
function PA:Print()
print(self.desc)
end
a = PA.new()
a.Print(a)
a:Print()
随机数
math.randomseed(os.time()) -- 设置随机种子
print(math.random()) -- [0, 1)
print(math.random(4)) -- [1, 4]
print(math.random(2,10)) -- [2, 10]
正则匹配
. 任意字符
%a 字母
%c 控制字符
%d 数字
%l 小写字母
%p 标点字符
%s 空白符
%u 大写字母
%w 字母和数字
%x 十六进制数字
%z 代表 0 的字符
+ 匹配前一字符 1 次或多次
* 匹配前一字符 0 次或多次
- 匹配前一字符 0 次或多次
? 匹配前一字符 0 次或 1 次
- string.find
date = "17/7/1990"
_, _, d, m, y = string.find(date, "(%d+)/(%d+)/(%d+)")
print(d, m, y) --> 17 7 1990
- string.gsub
print(string.gsub("hello Lua!", "(%a)", "%1-%1"))
--> h-he-el-ll-lo-o L-Lu-ua-a!
print(string.gsub("hello Lua", "(.)(.)", "%2%1"))
--> ehll ouLa
-- 去除前后空格(^ $ 保证我们获取的是整个字符串)
function trim (s)
return (string.gsub(s, "^%s*(.-)%s*$", "%1"))
end