Lua
Lua 脚本笔记
面向对象
定义表(类)内方法的时候带上一个额外的参数,来表示方法作用的对象,这个参数经常为 self 或者 this
在 Lua 中,可以通过使用冒号操作符来隐藏 self 参数的声明,冒号的效果相当于在函数定义和函数调用的时候,增加一个额外的隐藏参数。这种方式只是提供了一种方便的语法,实际上并没有什么新的内容。
我们可以使用 dot 语法定义函数而用冒号语法调用函数,反之亦然,只要我们正确的处理好额外的参数
metatable
元表为重定义 Lua 中任意一个对象(值)的默认行为提供了一种公开入口,如同许多语言的操作符重载或方法重载。
在 metatable 中,Lua 定义了许多重定义这些操作的入口,他们均以双下划线开头为 table 的域,
当你为一个值设置了 Metatable 并在 Metatable 中设置了重写了相应的操作域,在这个值执行这个操作的时候就会触发重写的自定义操作
metatable 中定义的操作
算术运算
__add, __sub, __mul, __div, __mod(模), __pow(幂), __unm(负), __concat(连接), len,
关系运算
__eq(等于), __lt(小于), __le(小于等于),
库相关__tostring
注意:print函数总是调用tostring来格式化它的输出。
然而当格式化一个对象的时候,tostring会首先检查对象是否存在一个带有__tostring域的metatable。如果存在则以对象作为参数调用对应的函数来完成格式化,返回的结果即为tostring的结果。
所以我们可以通过重写__tostring来实现自定义print函数的输出格式
__index
当表要索引一个值时如table[key],Lua会首先在table本身中查找key的值,如果没有并且这个table存在一个带有 __index
属性的Metatable,则Lua会按照 __index
所定义的函数逻辑查找。
rawget() 函数可绕过 __index
以原始方式访问表
__newindex
当你给表的一个缺少的域赋值,解释器就会查找 __newindex
metamethod:如果存在则调用这个函数而不进行赋值操作。
rawset(t,k,v) 不调用任何 metamethod 对表t的k域赋值为v
结合 __index
和 __newindex
可实现
只读表、监控表、面向对象编程的带有继承默认值的表
setmetatable(value, table);
设定值value的metatable,返回value
getmetatable(value);
返回值value的metatable,若没有返回nil
表table
table 是 Lua 中唯一的数据结构,其他语言所提供的其他数据结构比如:arrays、records、lists、queues、sets 等,Lua 都是通过 table 来实现
表使用 {}
来构造
不管用何种方式创建 table, 我们都可以随时向表中添加或者删除任何类型的域,构造函数仅仅影响表的初始化。
例如:
a = {x=0, y=0}
等价于:
a = {}; a.x=0; a.y=0;
经常先建立一个空表,再向其中添加元素
用 [expression]
显式的表示将被初始化的索引
例如:
opnames = {["+"] = "add", ["-"] = "sub", ["*"] = "mul", ["/"] = "div"}
例如:
{x=0, y=0} 等价于 {[“x”]=0, [“y”]=0}
数组
在 lua 中通过整数下标访问表中的元素即可简单的实现数组。
Lua 中习惯上数组的下标从 1 开始,Lua 的标准库与此习惯保持一致。若改变了数组下标的起始值,则无法正确使用标准库的函数。
table.insert(list, [pos,] value)
在 list 的指定位置 pos 插入一个元素,并将后面所有其他的元素 (list[pos], list[pos+1],…,list[#list]) 后移。
pos 的默认值是 #list+1,即插入到表的最后
table.sort(list [, comp])
对表 list 中元素进行 in-place 排序,不稳定排序
比较函数 comp 若给定,必须接受 2 个 list 元素为参数,且当排序后第一个元素在第二个元素前面时返回true
字符串string
字符串中下标从 1 开始。
可以使用单引号或者双引号表示字符串 string, 为了风格统一,最好使用一种,除非两种引号嵌套情况。
还可以使用 [[...]]
表示字符串。这种形式的字符串可以包含多行也可以嵌套且不会解释转义序列,如果第一个字符是换行符会被自动忽略掉。这种形式的字符串用来包含一段代码是非常方便的。
lua 中可以使用 ..
连接字符串。
string字符串和数字互转
如果需要显式将 string 转成数字可以使用函数 tonumber()
, 如果 string 不是正确的数字该函数将返回 nil
可以调用 tostring()
将数字转成字符串,这种转换一直有效。
string.len(s);
返回字符串s的长度,空串””长度为0
string.sub(s, i [, j]);
截取字符串 s 的从第 i 个字符到第 j 个字符之间的串。
如果不提供第 3 个参数,默认为 -1 即到字符串结尾。
Lua 中字符串的第一个字符索引从 1 开始。
可以使用负索引,负索引从字符串的结尾向前计数,-1 指向最后一个字符,-2 指向倒数第二个,以此类推。
注意:Lua 中的字符串是恒定不变的。String.sub 函数以及 Lua 中其他的字符串操作函数都不会改变字符串的值,而是返回一个新的字符串。
format()
string.format(formatstring, ...);
返回格式化后的字符串,规则和 c 的 sprintf 函数近乎相同
%c - 接受一个数字,并将其转化为ASCII码表中对应的字符
%d, %i - 接受一个数字并将其转化为有符号的整数格式
%o - 接受一个数字并将其转化为八进制数格式
%u - 接受一个数字并将其转化为无符号整数格式
%x - 接受一个数字并将其转化为十六进制数格式,使用小写字母
%X - 接受一个数字并将其转化为十六进制数格式,使用大写字母
%e - 接受一个数字并将其转化为科学记数法格式,使用小写字母e
%E - 接受一个数字并将其转化为科学记数法格式,使用大写字母E
%f - 接受一个数字并将其转化为浮点数格式
%g(%G) - 接受一个数字并将其转化为%e(%E,对应%G)及%f中较短的一种格式
%q - 接受一个字符串并将其转化为可安全被Lua编译器读入的格式
%s - 接受一个字符串并按照给定的参数格式化该字符串
find()
string.find(s, pattern [, init [, plain]] );
在字符串 s 中查找 pattern, 返回匹配的第一个 pattern 的位置,即匹配串的开始索引和结束索引;若找不到则返回 nil
第 3 个可选参数 init 指定搜索的起始位置,默认值为 1
使用模式匹配
如果 find 的第二个参数使用了某种匹配模式, 并且模式串里面带括号,那么表示会 捕捉(capture) 括号括起来的模式匹配到的字符串,并将其返回。此时 find() 函数的返回值个数为 2+n 个,前两个整数表示整个模式匹配到的字符串的开始索引和结束索引,后面 n 个字符串是模式中括号内的捕捉(captures),按照对应的左括号顺序依次返回
例如
pair = " name = Anna "
print(string.find(pair, "(%a+)%s*=%s*(%a+)")
输出结果为:2 12 name Anna
匹配此正则表达式的是”name = Anna”,所以返回2 12
两个captures分别为name和Anna,按照对应的左括号顺序依次返回
以 ^
开头的模式只匹配目标串的开始部分,相似的,以 $
结尾的模式只匹配目标串的结尾部分
例如
if string.find(s, “^%d”) then …
可检查字符串s是否以数字开头
例如
if string.find(s, “^[+-]?%d+$”) then …
检查字符串s是否是一个整数
例如
从rtmp://centos:1935/myapp 中获得端口号冒号之前的部分
local _, _, domain = string.find(tcurl, "^([^:]+://[^/:]+).*")
解释:模式”^([^:]+://[^/:]+).*”以’^’开头,表示只匹配开始部分,括号内
第一部分[^:]+表示1个或多个非冒号字符,可匹配”rtmp”
第二部分://匹配本身
第三部分[^/:]+表示1个或多个非/非冒号字符,可匹配”centos”,以及上线后的”www.wasu.com"
之后的.*表示任意多个任意字符
所以返回的capture是rtmp://centos
正则表达式
http://www.lua.org/manual/5.3/manual.html#6.4.1
magic characters:^ $ () % . [] *+-?
.:所有字符
%a:所有字母
%c:所有控制字符
%d:所有数字
%g:除空白外的所有可打印字符
%l:所有小写字母
%u:所有大写字母
%p:所有标点字符
%s:所有空白字符(空格,回车,制表)
%w:所有字母和数字
%x:所有十六进制数字
%x:若x是非字母非数字字符,则表示x字符本身,即%是转义字符
‘%’ 用作特殊字符的转义字符,因此 ‘%.’ 匹配点;’%%’ 匹配字符 ‘%’。转义字符 ‘%’不仅可以用来转义特殊字符,还可以用于所有的非字母的字符。当对一个字符有疑问的时候,为安全起见请使用转义字符转义他。
通配符的大写表示对应的补集,例如%S表示所有非空白字符
*: 匹配前面指定的 0 或多个同类字符, 尽可能匹配更长的符合条件的字串
+:匹配前面指定的 1 或多个同类字符, 尽可能匹配更长的符合条件的字串
-: 匹配前面指定的 0 或多个同类字符, 尽可能匹配更短的符合条件的字串
?:匹配前面指定的 0 或1个同类字符
字符集[set]
[^set]:表示[set]的补集,即在字符集的开始处使用 ‘^’ 表示其补集,例如 %S 等于 [^%s]
[%w_]:任意字母数字加下划线
[0-7]:任意八进制数字,等于[01234567]
[0-7%l%-]:八进制数字 加 小写字母 加 ‘-‘
例如
[+-]?%d+:带符号的整数
.*:任意个任意字符,并且做最长匹配
[^/n]:任何非换行符
表达式和语法
全局变量
全局变量不需要声明,给一个变量赋值后即创建了这个全局变量,访问一个没有初始化的全局变量也不会出错,只不过得到的结果是 nil
想删除一个全局变量,只需要将变量赋值为 nil
基本类型
Lua 中有 8 个基本类型分别为:nil、boolean、number、string、userdata、function、thread和table。
函数 type 可以测试给定变量或者值的类型。
逻辑运算
booleans
可取值true或false,但要注意 Lua中所有的值都可以作为条件。
在控制结构的条件中除了false和nil为假,其他值都为真。
所以Lua认为0和空串都是真
Lua 通过引用比较tables、userdata、functions。也就是说当且仅当两者表示同一个对象时相等。
逻辑运算符认为false和nil是假(false),其他为真,0也是true
and和or的运算结果不是true和false,而是和它的两个操作数相关。
a and b – 如果a为false,则返回a,否则返回b,即返回起决定作用的变量
a or b – 如果a为true,则返回a,否则返回b,即返回起决定作用的变量
c中的三元运算符 a?b:c 可以实现为: (a and b) or c
连接符
连接运算符 .. 用来连接字符串,如果操作数为数字,Lua 将数字转成字符串。
print("Hello " .. "World") --> Hello World
print(0 .. 1) --> 01
赋值语句
Lua 可以对多个变量同时赋值,变量列表和值列表的各个元素用逗号分开,赋值语句右边的值会依次赋给左边的变量。
所以我们可以这样进行交换变量的值:x, y = y, x
当变量个数和值的个数不一致时,Lua 会一直以变量个数为基础采取以下策略:
a. 变量个数>值的个数,按变量个数补足nil
b. 变量个数<值的个数,多余的值会被忽略
所以a, b, c = 0 不会将三个变量的值都赋为0,后两个变量为nil,这是一个常见错误。
局部变量
使用 local 创建一个局部变量,与全局变量不同,局部变量只在被声明的那个代码块内有效。
尽可能使用局部变量,使用局部变量可避免命名冲突,并且比全局变量速度更快。
想更好的控制局部变量的作用范围时,最好将代码放在 do..end 块中
for语句
(1) 数值for循环
for var = exp1, exp2, exp3 do
loop-part
end
for 将用 exp3 作为 step 从 exp1(初始值)到 exp2(终止值),执行 loop-part。其中 exp3 可以省略,默认 step=1
三个表达式只会被计算一次,并且是在循环开始前
循环过程中不要改变控制变量的值,那样做的结果是不可预知的。如果要退出循环,使用 break 语句。
(2) 范型for循环
for i,v in ipairs(a) do
print(i, v)
end
break和return语句
当一个函数自然结束结尾会有一个默认的 return
Lua 语法要求 break 和 return 只能出现在 block 的结尾一句(也就是说:作为chunk的最后一句,或者在end之前,或者else前,或者until前)
若需要在 block 中间使用return或break,可显示使用do..end,例如:do return end
函数
Lua 函数格式:
optional_function_scope function function_name( argument1, argument2, argument3..., argumentn)
function_body
return result_params_comma_separated
end
optional_function_scope: 可选的函数的作用域,未设置该参数默认为全局函数,如果你需要设置函数为局部函数需要使用关键字 local
result_params_comma_separated: 函数返回值,Lua语言函数可以返回多个值,每个值以逗号隔开。
函数是第一类值(和其他变量相同)
第一类值指:在 Lua 中函数和其他值(数值、字符串)一样,函数可以被存放在变量中,也可以存放在表中,可以作为函数的参数,还可以作为函数的返回值。
Lua 使用的函数可以是 Lua 编写也可以是其他语言编写,对于 Lua 程序员来说用什么语言实现的函数使用起来都一样
Lua 所有标准库都是用 C 实现的,标准库包括 string 库、table库、I/O库、OS库、算术库、debug库。
若函数参数列表为空,必须加 ()
表明是函数调用,当函数只有一个参数并且这个参数是字符串或者表构造的时候,()
是可选的
Lua 函数中,在 return 后列出要返回的值的列表即可返回多值
return 语句如果使用圆括号将返回值括起来也将导致返回一个值
unpack 函数:接受一个数组作为输入参数,返回数组的所有元素
可变参数
Lua 函数可以接受可变数目的参数,使用三点 (...)
表示函数有可变的参数,Lua 将函数的参数放在一个叫 arg 的表中,除了参数以外,arg 表中还有一个域 n 表示参数的个数。
**虚变量(下划线)**,比如只需要返回的第二个参数时:
local _, x = string.find(s, p)
合理处理尾调用(Proper Tail Calls)
当函数最后一个动作是调用另外一个函数,且调用后不再做其他任何事情,则我们称这种调用为尾调用。
尾调用之后程序不需要在栈中保留关于调用者的任何信息,一些编译器比如 Lua 解释器利用这种特性在处理尾调用时不使用额外的栈,我们称这种语言支持正确的尾调用。
由于尾调用不需要使用栈空间,那么尾调用递归的层次可以无限制的。例如下面调用不论 n 为何值不会导致栈溢出。
require
require (modname)
类似 c 中的 include 用来加载文件
例如,
在 nginx 的配置文件中定义变量 lua_package_path 为:
“./?.lua;./?.lc;/usr/local/?/init.lua”
如果 lua 代码中调用:
require("hello.world")
首先 lua 会将 require 参数中的每个点都替换成”目录分隔符”(比如Unix中的”/“),即
hello/world
然后将所有问号替换为转换后的 require 参数 hello/world
依次查找:
./hello/world.lua ==> 这里”hello.world”变成了”hello/world”,并替换了模型”./?.lua”
./hello/world.lc
/usr/local/hello/world/init.lua
package.path
require 用于查找Lua加载器的路径
在启动时 Lua 使用环境变量 LUA_PATH 或者如果环境变量未定义就使用 luaconf.h 中定义的默认值来初始化该值
环境变量中的任何 “::” 都被替换为默认路径.
package.loaded
一个用于控制哪些模块已经加载的表,该表由 require 使用.
当 require 一个模块名为 modname 的模块且 package.loaded[modname] 不为 false 时,require 仅返回 package.loaded[modname] 存储的值.
上一篇 GCC命令
下一篇 紫金陈《谋杀官员》系列读后感
页面信息
location:
protocol
: host
: hostname
: origin
: pathname
: href
: document:
referrer
: navigator:
platform
: userAgent
: