当前位置 : 首页 » 文章分类 :  开发  »  Lua

Lua

Lua 脚本笔记

https://www.lua.org/


面向对象

定义表(类)内方法的时候带上一个额外的参数,来表示方法作用的对象,这个参数经常为 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命令

下一篇 紫金陈《谋杀官员》系列读后感

阅读
评论
4.4k
阅读预计16分钟
创建日期 2015-01-26
修改日期 2021-07-16
类别
标签

页面信息

location:
protocol:
host:
hostname:
origin:
pathname:
href:
document:
referrer:
navigator:
platform:
userAgent:

评论