资讯专栏INFORMATION COLUMN

Lua5.3学习笔记

AWang / 2552人阅读

摘要:对于正常结束,将返回,并接上协程主函数的返回值。当错误发生时,将返回与错误消息。通过调用使协程暂停执行,让出执行权。通用形式的通过一个叫作迭代器的函数工作。

Lua 是一门强大、轻量的嵌入式脚本语言,可供任何需要的程序使用。Lua 没有 "main" 程序的概念: 它只能 嵌入 一个宿主程序中工作.宿主程序可以调用函数执行一小段 Lua 代码,可以读写 Lua 变量,可以注册 C 函数让 Lua 代码调用。
Lua 是一门动态类型语言。 这意味着变量没有类型;只有值才有类型。
Lua 中所有的值都是 一等公民。 这意味着所有的值均可保存在变量中、 当作参数传递给其它函数、以及作为返回值。
文档:5.3:http://cloudwu.github.io/lua5...
5.1:http://book.luaer.cn/

基础 八种基本类型

Lua 中有八种基本类型: nil、boolean、number、string、function、userdata、 thread 和 table。
Nil 是值 nil 的类型.和Python中None,Java中null类似。
Boolean 是 false 与 true 两个值的类型。与其他语言不通的是nil 和 false 都会导致条件判断为假; 而其它任何值都表示为真。而其它语言判断比如0时为假,但lua为真。
Number 代表了整数和实数(浮点数)。 它也按需作自动转换
String 表示一个不可变的字节序列。Lua 的字符串与编码无关; 它不关心字符串中具体内容。
Lua 可以调用(以及操作)用 Lua 或 C 编写的函数。 这两种函数有统一类型 function
userdata 类型允许将 C 中的数据保存在 Lua 变量中。 用户数据类型的值是一个内存块, 有两种用户数据: 完全用户数据 ,指一块由 Lua 管理的内存对应的对象; 轻量用户数据 ,则指一个简单的 C 指针。 用户数据在 Lua 中除了赋值与相等性判断之外没有其他预定义的操作。 通过使用 元表 ,程序员可以给完全用户数据定义一系列的操作。 你只能通过 C API 而无法在 Lua 代码中创建或者修改用户数据的值, 这保证了数据仅被宿主程序所控制。
thread 类型表示了一个独立的执行序列,被用于实现协程Lua 的线程与操作系统的线程毫无关系。 Lua 为所有的系统,包括那些不支持原生线程的系统,提供了协程支持
table 是一个关联数组, 也就是说,这个数组不仅仅以数字做索引,除了 nil 和 NaN 之外的所有 Lua 值 都可以做索引。(Not a Number 是一个特殊的数字,它用于表示未定义或表示不了的运算结果,比如 0/0。) 表可以是 异构 的; 也就是说,表内可以包含任何类型的值( nil 除外)。
表是 Lua 中唯一的数据结构, 它可被用于表示普通数组、序列、符号表、集合、记录、图、树等等。 对于记录,Lua 使用域名作为索引。 语言提供了 a.name 这样的语法糖来替代 a["name"]。
我们使用 序列 这个术语来表示一个用 {1..n} 的正整数集做索引的表.注意:lua中索引从1开始,而非0
和索引一样,表中每个域的值也可以是任何类型。 需要特别指出的是:既然函数是一等公民,那么表的域也可以是函数。 这样,表就可以携带 方法 了。

表、函数、线程、以及完全用户数据在 Lua 中被称为 对象: 变量并不真的 持有 它们的值,而仅保存了对这些对象的 引用。 赋值、参数传递、函数返回,都是针对引用而不是针对值的操作, 这些操作均不会做任何形式的隐式拷贝。

类型判断

库函数 type 用于以字符串形式返回给定值的类型。

错误处理

由于 Lua 是一门嵌入式扩展语言,其所有行为均源于宿主程序中 C 代码对某个 Lua 库函数的调用。 (多带带使用 Lua 时,lua 程序就是宿主程序。) 所以,在编译或运行 Lua 代码块的过程中,无论何时发生错误, 控制权都返回给宿主,由宿主负责采取恰当的措施(比如打印错误消息)。
可以在 Lua 代码中调用 error 函数来显式地抛出一个错误。 如果你需要在 Lua 中捕获这些错误, 可以使用 pcall 或 xpcall 在 保护模式 下调用一个函数。
无论何时出现错误,都会抛出一个携带错误信息的 错误对象 (错误消息),这是一个字符串对象。
使用 xpcall 或 lua_pcall 时, 你应该提供一个 消息处理函数 用于错误抛出时调用。 该函数需接收原始的错误消息,并返回一个新的错误消息。

元表及元方法

Lua 中的每个值都可以有一个 元表。 这个 元表 就是一个普通的 Lua 表, 它用于定义原始值在特定操作下的行为。元表中的键对应着不同的 事件 名; 键关联的那些值被称为 元方法。
你可以用 getmetatable 函数 来获取任何值的元表。
使用 setmetatable 来替换一张表的元表。在 Lua 中,你不可以改变表以外其它类型的值的元表 (除非你使用调试库); 若想改变这些非表类型的值的元表,请使用 C API。
表和完全用户数据有独立的元表 (当然,多个表和用户数据可以共享同一个元表)。 其它类型的值按类型共享元表; 也就是说所有的数字都共享同一个元表, 所有的字符串共享另一个元表等等。
元表决定了一个对象在数学运算、位运算、比较、连接、 取长度、调用、索引时的行为。 元表还可以定义一个函数,当表对象或用户数据对象在垃圾回收时调用它。
其它具体见文档。。。。

垃圾收集

Lua 采用了自动内存管理。Lua 实现了一个增量标记-扫描收集器。
垃圾收集元方法:
你可以为表设定垃圾收集的元方法,对于完全用户数据, 则需要使用 C API 。 该元方法被称为 终结器。 终结器允许你配合 Lua 的垃圾收集器做一些额外的资源管理工作 (例如关闭文件、网络或数据库连接,或是释放一些你自己的内存)。
如果要让一个对象(表或用户数据)在收集过程中进入终结流程, 你必须 标记 它需要触发终结器。 当你为一个对象设置元表时,若此刻这张元表中用一个以字符串 "__gc" 为索引的域,那么就标记了这个对象需要触发终结器。

协程

调用函数 coroutine.create 可创建一个协程。 其唯一的参数是该协程的主函数。 create 函数只负责新建一个协程并返回其句柄 (一个 thread 类型的对象); 而不会启动该协程。

调用 coroutine.resume 函数执行一个协程。
协程的运行可能被两种方式终止: 正常途径是主函数返回 (显式返回或运行完最后一条指令); 非正常途径是发生了一个未被捕获的错误。 对于正常结束, coroutine.resume 将返回 true, 并接上协程主函数的返回值。 当错误发生时, coroutine.resume 将返回 false 与错误消息。

通过调用 coroutine.yield 使协程暂停执行,让出执行权。 协程让出时,对应的最近 coroutine.resume 函数会立刻返回,即使该让出操作发生在内嵌函数调用中 (即不在主函数,但在主函数直接或间接调用的函数内部)。 在协程让出的情况下, coroutine.resume 也会返回 true, 并加上传给 coroutine.yield 的参数。 当下次重启同一个协程时, 协程会接着从让出点继续执行。

与 coroutine.create 类似, coroutine.wrap 函数也会创建一个协程。 不同之处在于,它不返回协程本身,而是返回一个函数。 调用这个函数将启动该协程。 传递给该函数的任何参数均当作 coroutine.resume 的额外参数。 coroutine.wrap 返回 coroutine.resume 的所有返回值,除了第一个返回值(布尔型的错误码)。 和 coroutine.resume 不同, coroutine.wrap 不会捕获错误; 而是将任何错误都传播给调用者。

下面的代码展示了一个协程工作的范例:

     function foo (a)
       print("foo", a)
       return coroutine.yield(2*a)
     end
     
     co = coroutine.create(function (a,b)
           print("co-body", a, b)
           local r = foo(a+1)
           print("co-body", r)
           local r, s = coroutine.yield(a+b, a-b)
           print("co-body", r, s)
           return b, "end"
     end)
     
     print("main", coroutine.resume(co, 1, 10))
     print("main", coroutine.resume(co, "r"))
     print("main", coroutine.resume(co, "x", "y"))
     print("main", coroutine.resume(co, "x", "y"))
当你运行它,将产生下列输出:

     co-body 1       10
     foo     2
     main    true    4
     co-body r
     main    true    11      -9
     co-body x       y
     main    true    10      end
     main    false   cannot resume dead coroutine

你也可以通过 C API 来创建及操作协程: 参见函数 lua_newthread, lua_resume, 以及 lua_yield。

语言定义

Lua 语言的格式自由。 它会忽略语法元素(符记)间的空格(包括换行)和注释, 仅把它们看作为名字和关键字间的分割符。
Lua 语言对大小写敏感。
字面串 可以用单引号或双引号括起。 字面串内部可以包含下列 C 风格的转义串。
转义串 "z" 会忽略其后的一系列空白符,包括换行; 它在你需要对一个很长的字符串常量断行为多行并希望在每个新行保持缩进时非常有用。
对于用 UTF-8 编码的 Unicode 字符,你可以用 转义符 u{XXX} 来表示

代码注释:

--这是行注释
--[[这是块
注释]]
变量:

Lua 中有三种变量: 全局变量、局部变量和表的域。
所有没有显式声明为局部变量的变量名都被当做全局变量 这一点倒是和js很相似。
全局变量 x = 1234 的赋值等价于 _ENV.x = 1234
局部变量 local namelist [‘=’ explist] 比如 local name="xbynet"

语句:

每个语句结尾的分号(;)是可选的,但如果同一行有多个语句最好用;分开

-- file "lib1.lua"
 
function norm (x, y)
    local n2 = x^2 + y^2
    return math.sqrt(n2)
end
 
function twice (x)
    return 2*x
end

在交互模式下:

> lua -i 
> dofile("lib1.lua")     -- load your library
> n = norm(3.4, 1.0)
> print(twice(n))        --> 7.0880180586677

-i和dofile在调试或者测试Lua代码时是很方便的。

命令行方式

> lua -e "print(math.sin(12))"   --> -0.53657291800043

全局变量arg存放Lua的命令行参数。
prompt> lua script a b c
在运行以前,Lua使用所有参数构造arg表。脚本名索引为0,脚本的参数从1开始增加。脚本前面的参数从-1开始减少。

if, while, and repeat 这些控制结构符合通常的意义,而且也有类似的语法:

while exp do block end
repeat block until exp
if exp then block elseif exp then block else block end

for 有两种形式:一种是数字形式,另一种是通用形式。
数值for循环:

for var=exp1,exp2,exp3 do
    loop-part
end

for将用exp3作为step从exp1(初始值)到exp2(终止值),执行loop-part。其中exp3可以省略,默认step=1
有几点需要注意:

三个表达式只会被计算一次,并且是在循环开始前。

for i=1,f(x) do

print(i)

end

for i=10,1,-1 do

print(i)

end

第一个例子f(x)只会在循环前被调用一次

通用形式的 for 通过一个叫作 迭代器 的函数工作。 每次迭代,迭代器函数都会被调用以产生一个新的值, 当这个值为 nil 时,循环停止

for var_1, ···, var_n in explist do block end

-- print all values of array "a"
for i,v in ipairs(a) do print(v) end

再看一个遍历表key的例子:
-- print all keys of table "t"
for k in pairs(t) do print(k) end  
 

控制结构中的条件表达式可以返回任何值。 false 与 nil 两者都被认为是假。 所有不同于 nil 与 false 的其它值都被认为是真 (特别需要注意的是,数字 0 和空字符串也被认为是真)。
有break,但是没有continue
return 被用于从函数或是代码块(其实它就是一个函数) 中返回值。 函数可以返回不止一个值。
return 只能被写在一个语句块的最后一句。 如果你真的需要从语句块的中间 return, 你可以使用显式的定义一个内部语句块, 一般写作 do return end。 可以这样写是因为现在 return 成了(内部)语句块的最后一句了。

Lua语法要求break和return只能出现在block的结尾一句(也就是说:作为chunk的最后一句,或者在end之前,或者else前,或者until前),例如:

local i = 1
while a[i] do
    if a[i] == v then break end
    i = i + 1
end

有时候为了调试或者其他目的需要在block的中间使用return或者break,可以显式的使用do..end来实现:

function foo ()
    return            --<< SYNTAX ERROR
    -- "return" is the last statement in the next block
    do return end        -- OK
    ...               -- statements not reached
end
表达式

数学运算操作符

+: 加法
-: 减法
*: 乘法
/: 浮点除法
//: 向下取整除法
%: 取模
^: 乘方
-: 取负

比较操作符

==: 等于
~=: 不等于
<: 小于
>: 大于
<=: 小于等于
>=: 大于等于

注意:不等于是~=,而不是!=
这些操作的结果不是 false 就是 true。
等于操作 (==)先比较操作数的类型。 如果类型不同,结果就是 false。 否则,继续比较值。 字符串按一般的方式比较。 数字遵循二元操作的规则
表,用户数据,以及线程都按引用比较: 只有两者引用同一个对象时才认为它们相等。
你可以通过使用 "eq" 元方法来改变 Lua 比较表和用户数据时的方式。

逻辑操作符
Lua 中的逻辑操作符有 and, or,以及 not
和控制结构一样, 所有的逻辑操作符把 false 和 nil 都作为假, 而其它的一切都当作真。

字符串连接
Lua 中字符串的连接操作符写作两个点("..")。 如果两个操作数都是字符串或都是数字, 连接操作将以中提到的规则把其转换为字符串。 否则,会调用元方法 __concat
多行字符串
还可以使用[[...]]表示字符串。这种形式的字符串可以包含多行

page = [[
qwwqwq
adas
ss
]]

取长度操作符
取长度操作符写作一元前置符 #。 字符串的长度是它的字节数(就是以一个字符一个字节计算的字符串长度)。而Python是内置的len()函数,Java和JS都是字符串函数.length()
程序可以通过 __len 元方法来修改对字符串类型外的任何值的取长度操作行为。

表构建

表构造子是一个构造表的表达式。 每次构造子被执行,都会构造出一张新的表。 构造子可以被用来构造一张空表, 也可以用来构造一张表并初始化其中的一些域。
构造器是创建和初始化表的表达式。表是Lua特有的功能强大的东西。最简单的构造函数是{},用来创建一个空表。可以直接初始化数组:

days = {"Sunday", "Monday", "Tuesday", "Wednesday",
              "Thursday", "Friday", "Saturday"}

Lua将"Sunday"初始化days[1](第一个元素索引为1),用"Monday"初始化days[2]...
如果想初始化一个表作为record使用可以这样:

a = {x=0, y=0}       <-->       a = {}; a.x=0; a.y=0

不管用何种方式创建table,我们都可以向表中添加或者删除任何类型的域,构造函数仅仅影响表的初始化。

w = {x=0, y=0, label="console"}
x = {sin(0), sin(1), sin(2)}
w[1] = "another field"
x.f = w
print(w["x"])     --> 0
print(w[1])       --> another field
print(x.f[1])     --> another field
w.x = nil         -- remove field "x"

值得注意的是:w.x = nil -- remove field "x"

在构造函数中域分隔符逗号(",")可以用分号(";")替代,通常我们使用分号用来分割不同类型的表元素。

{x=10, y=45; "one", "two", "three"}

举个例子:

a = { [f(1)] = g; "x", "y"; x = 1, f(x), [30] = 23; 45 }

等价于

 do
   local t = {}
   t[f(1)] = g
   t[1] = "x"         -- 1st exp
   t[2] = "y"         -- 2nd exp
   t.x = 1            -- t["x"] = 1
   t[3] = f(x)        -- 3rd exp
   t[30] = 23
   t[4] = 45          -- 4th exp
   a = t
 end

如果表单中最后一个域的形式是 exp , 而且其表达式是一个函数调用或者是一个可变参数, 那么这个表达式所有的返回值将依次进入列表

函数

函数定义

 function f () body end
 local function f () body end
 

当一个函数被调用, 如果函数并非一个 可变参数函数, 即在形参列表的末尾注明三个点 ("..."), 那么实参列表就会被调整到形参列表的长度。

     function f(a, b) end
     function g(a, b, ...) end
     function r() return 1,2,3 end

下面看看实参到形参数以及可变长参数的映射关系:

 CALL            PARAMETERS
 
 f(3)             a=3, b=nil
 f(3, 4)          a=3, b=4
 f(3, 4, 5)       a=3, b=4
 f(r(), 10)       a=1, b=10
 f(r())           a=1, b=2
 
 g(3)             a=3, b=nil, ... -->  (nothing)
 g(3, 4)          a=3, b=4,   ... -->  (nothing)
 g(3, 4, 5, 8)    a=3, b=4,   ... -->  5  8
 g(5, r())        a=5, b=1,   ... -->  2  3
 

冒号 语法可以用来定义 方法, 就是说,函数可以有一个隐式的形参 self。 因此,如下语句

 function t.a.b.c:f (params) body end

是这样一种写法的语法糖

 t.a.b.c.f = function (self, params) body end

Lua函数可以返回多个结果值

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总是调整函数返回值的个数以适用调用环境,当作为独立的语句调用函数时,所有返回值将被忽略
第一,当作为表达式调用函数时,有以下几种情况:

当调用作为表达式最后一个参数或者仅有一个参数时,根据变量个数函数尽可能多地返回多个值,不足补nil,超出舍去。

其他情况下,函数调用仅返回第一个值(如果没有返回值为nil)
第二,函数调用作为函数参数被调用时,和多值赋值是相同。

第三,函数调用在表构造函数中初始化时,和多值赋值时相同。

可变参数与命名参数

printResult = ""
 
function print(...)
    for i,v in ipairs(arg) do
       printResult = printResult .. tostring(v) .. "	"
    end
    printResult = printResult .. "
"
end

命名参数用表来作为参数传递。

可见性规则

Lua 语言有词法作用范围。 变量的作用范围开始于声明它们之后的第一个语句段, 结束于包含这个声明的最内层语句块的最后一个非空语句。

     x = 10                -- 全局变量
     do                    -- 新的语句块
       local x = x         -- 新的一个 "x", 它的值现在是 10
       print(x)            --> 10
       x = x+1
       do                  -- 另一个语句块
         local x = x+1     -- 又一个 "x"
         print(x)          --> 12
       end
       print(x)            --> 11
     end
     print(x)              --> 10 (取到的是全局的那一个)

局部变量可以被在它的作用范围内定义的函数自由使用。 当一个局部变量被内层的函数中使用的时候, 它被内层函数称作 上值,或是 外部局部变量

注意,每次执行到一个 local 语句都会定义出一个新的局部变量。 看看这样一个例子:

 a = {}
 local x = 20
 for i=1,10 do
   local y = 0
   a[i] = function () y=y+1; return x+y end
 end

这个循环创建了十个闭包(这指十个匿名函数的实例)。 这些闭包中的每一个都使用了不同的 y 变量, 而它们又共享了同一份 x。

C API接口

由于,目前关注重点在于简单的redis与Lua及nginx与lua交互,故而暂时略去此部分学习。。。

标准库

所有的库都是直接用 C API 实现的,并以分离的 C 模块形式提供。 目前,Lua 有下列标准库:
基础库
协程库
包管理库
字符串控制
基础 UTF-8 支持
表控制
数学函数
输入输出
操作系统库
调试库

基础函数

(只列出一些个人认为对初学者常用的)

assert (v [, message])
如果其参数 v 的值为假(nil 或 false), 它就调用 error; 否则,返回所有的参数。 在错误情况时, message 指那个错误对象; 如果不提供这个参数,参数默认为 "assertion failed!" 。

dofile ([filename])
打开该名字的文件,并执行文件中的 Lua 代码块。

error (message [, level])
中止上一次保护函数调用, 将错误对象 message 返回。level 参数指明了怎样获得出错位置。 对于 level 1 (默认值),出错位置指 error 函数调用的位置。 Level 2 将出错位置指向调用 error的函数的函数;以此类推。 传入 level 0 可以避免在消息前添加出错位置信息。

_G
一个全局变量(非函数), 内部储存有全局环境。 Lua 自己不使用这个变量; 改变这个变量的值不会对任何环境造成影响,反之亦然。

_VERSION
一个包含有当前解释器版本号的全局变量(并非函数)。 当前这个变量的值为 "Lua 5.3"。

getmetatable (object)
如果 object 不包含元表,返回 nil 。 否则,如果在该对象的元表中有 "__metatable" 域时返回其关联值, 没有时返回该对象的元表。

ipairs (t)
以下代码for i,v in ipairs(t) do body end
将迭代键值对(1,t[1]) ,(2,t[2]), ... ,直到第一个空值。

pairs (t)
for k,v in pairs(t) do body end
能迭代表 t 中的所有键值对。

load (chunk [, chunkname [, mode [, env]]])
加载一个代码块。
如果 chunk 是一个字符串,代码块指这个字符串。 如果 chunk 是一个函数, load 不断地调用它获取代码块的片断。 每次对 chunk 的调用都必须返回一个字符串紧紧连接在上次调用的返回串之后。 当返回空串、nil、或是不返回值时,都表示代码块结束。
如果没有语法错误, 则以函数形式返回编译好的代码块; 否则,返回 nil 加上错误消息。
如果结果函数有上值, env 被设为第一个上值。 若不提供此参数,将全局环境替代它。
chunkname 在错误消息和调试消息中,用于代码块的名字。 如果不提供此参数,它默认为字符串chunk 。
字符串 mode 用于控制代码块是文本还是二进制(即预编译代码块)。 它可以是字符串 "b" (只能是二进制代码块), "t" (只能是文本代码块), 或 "bt" (可以是二进制也可以是文本)。 默认值为 "bt"。

loadfile ([filename [, mode [, env]]])
和 load 类似, 不过是从文件 filename 或标准输入(如果文件名未提供)中获取代码块。

next (table [, index])
运行程序来遍历表中的所有域。 第一个参数是要遍历的表,第二个参数是表中的某个键。 next 返回该键的下一个键及其关联的值。 特别指出,你可以用 next(t) 来判断一张表是否是空的。

pcall (f [, arg1, ···])
传入参数,以 保护模式 调用函数 f 。 这意味着 f 中的任何错误不会抛出; 取而代之的是,pcall 会将错误捕获到,并返回一个状态码。 第一个返回值是状态码(一个布尔量), 当没有错误时,其为真。 此时,pcall 同样会在状态码后返回所有调用的结果。 在有错误时,pcall 返回 false 加错误消息。

xpcall (f, msgh [, arg1, ···])
这个函数和 pcall 类似。 不过它可以额外设置一个消息处理器 msgh。

print (···)
接收任意数量的参数,并将它们的值打印到 stdout。 它用 tostring 函数将每个参数都转换为字符串。 print 不用于做格式化输出。完整的对输出的控制,请使用 string.format 以及 io.write

tostring (v)
可以接收任何类型,它将其转换为人可阅读的字符串形式。

select (index, ···)
如果 index 是个数字, 那么返回参数中第 index 个之后的部分; 负的数字会从后向前索引(-1 指最后一个参数)。 否则,index 必须是字符串 "#", 此时 select 返回参数的个数。

tonumber (e [, base])
如果调用的时候没有 base, tonumber 尝试把参数转换为一个数字。

type (v)
类型判断, 函数可能的返回值有 "nil" (一个字符串,而不是 nil 值), "number", "string", "boolean", "table", "function", "thread", "userdata"。

协程管理

关于协程的操作作为基础库的一个子库, 被放在一个独立表 coroutine 中。

coroutine.create (f)
创建一个主体函数为 f 的新协程。 f 必须是一个 Lua 的函数。 返回这个新协程,它是一个类型为 "thread" 的对象。

coroutine.isyieldable ()
如果正在运行的协程可以让出,则返回真。
不在主线程中或不在一个无法让出的 C 函数中时,当前协程是可让出的。

coroutine.resume (co [, val1, ···])
开始或继续协程 co 的运行。 当你第一次延续一个协程,它会从主体函数处开始运行。 val1, ... 这些值会以参数形式传入主体函数。 如果该协程被让出,resume 会重新启动它; val1, ... 这些参数会作为让出点的返回值。

如果协程运行起来没有错误, resume 返回 true 加上传给 yield 的所有值 (当协程让出), 或是主体函数的所有返回值(当协程中止)。 如果有任何错误发生, resume 返回 false 加错误消息。

coroutine.running ()
返回当前正在运行的协程加一个布尔量。 如果当前运行的协程是主线程,其为真。

coroutine.status (co)
以字符串形式返回协程 co 的状态: 当协程正在运行(它就是调用 status 的那个) ,返回 "running"; 如果协程调用 yield 挂起或是还没有开始运行,返回 "suspended"; 如果协程是活动的,都并不在运行(即它正在延续其它协程),返回 "normal"; 如果协程运行完主体函数或因错误停止,返回 "dead"。

coroutine.wrap (f)
创建一个主体函数为 f 的新协程。 f 必须是一个 Lua 的函数。 返回一个函数, 每次调用该函数都会延续该协程。 传给这个函数的参数都会作为 resume 的额外参数。 和 resume 返回相同的值, 只是没有第一个布尔量。 如果发生任何错误,抛出这个错误。

coroutine.yield (···)
挂起正在调用的协程的执行。 传递给 yield 的参数都会转为 resume 的额外返回值。

模块

包管理库提供了从 Lua 中加载模块的基础库。 只有一个导出函数直接放在全局环境中: require。 所有其它的部分都导出在表 package 中。

require (modname)
加载一个模块。 这个函数首先查找 package.loaded 表, 检测 modname 是否被加载过。 如果被加载过,require 返回 package.loaded[modname] 中保存的值。 否则,它试着为模块寻找 加载器 。(require 遵循 package.searchers 序列的指引来查找加载器。)

package.path
这个路径被 require 在 Lua 加载器中做搜索时用到。
在启动时,Lua 用环境变量 LUA_PATH_5_3 或环境变量 LUA_PATH 来初始化这个变量。

package.searchers
用于 require 控制如何加载模块的表。

package.searchpath (name, path [, sep [, rep]])
在指定 path 中搜索指定的 name 。

其余,请看文档。

字符串处理

这个库提供了字符串处理的通用函数。 例如字符串查找、子串、模式匹配等。 当在 Lua 中对字符串做索引时,第一个字符从 1 开始计算(而不是 C 里的 0 )。 索引可以是负数,它指从字符串末尾反向解析。 即,最后一个字符在 -1 位置处,等等。
字符串库中的所有函数都在表 string 中。字符串库假定采用单字节字符编码。(这意味着不是原生支持中文。)

string.byte (s [, i [, j]])
返回字符 s[i], s[i+1], ... ,s[j] 的内部数字编码。

string.char (···)
接收零或更多的整数。 返回和参数数量相同长度的字符串。

string.find (s, pattern [, init [, plain]])
查找第一个字符串 s 中匹配到的 pattern 。 如果找到一个匹配,find 会返回 s 中关于它起始及终点位置的索引; 否则,返回 nil。 第三个可选数字参数 init 指明从哪里开始搜索; 默认值为 1 ,同时可以是负值。 第四个可选参数 plain 为 true 时, 关闭模式匹配机制。 此时函数仅做直接的 “查找子串”的操作.

string.format (formatstring, ···)
返回不定数量参数的格式化版本, 格式化串为第一个参数(必须是一个字符串)。 格式化字符串遵循 ISO C 函数 sprintf 的规则。

string.match (s, pattern [, init])
在字符串 s 中找到第一个能用 pattern (参见 §6.4.1)匹配到的部分。 如果能找到,match 返回其中的捕获物; 否则返回 nil 。 如果 pattern 中未指定捕获, 返回整个 pattern 捕获到的串。 第三个可选数字参数 init 指明从哪里开始搜索; 它默认为 1 且可以是负数。

string.gmatch (s, pattern)
返回一个迭代器函数。 每次调用这个函数都会继续以 pattern (参见 §6.4.1) 对 s 做匹配,并返回所有捕获到的值。 如果 pattern 中没有指定捕获,则每次捕获整个 pattern。
下面这个例子会循环迭代字符串 s 中所有的单词, 并逐行打印:

 s = "hello world from Lua"
 for w in string.gmatch(s, "%a+") do
   print(w)
 end

下一个例子从指定的字符串中收集所有的键值对 key=value 置入一张表:

 t = {}
 s = "from=world, to=Lua"
 for k, v in string.gmatch(s, "(%w+)=(%w+)") do
   t[k] = v
 end

对这个函数来说,模板前开始的 "^" 不会当成锚点。因为这样会阻止迭代。

string.sub (s, i [, j])
返回 s 的子串, 该子串从 i 开始到 j 为止; i 和 j 都可以为负数。

string.gsub (s, pattern, repl [, n])
将字符串 s 中,所有的(或是在 n 给出时的前 n 个) pattern (参见 §6.4.1)都替换成 repl ,并返回其副本。 repl 可以是字符串、表、或函数。 gsub 还会在第二个返回值返回一共发生了多少次匹配。 这个和python中的re.sub有点类似。
如果 repl 是一个字符串,那么把这个字符串作为替换品。 字符 % 是一个转义符: repl 中的所有形式为 %d 的串表示 第 d 个捕获到的子串,d 可以是 1 到 9 。 串 %0 表示整个匹配。 串 %% 表示单个 %。
如果 repl 是张表,每次匹配时都会用第一个捕获物作为键去查这张表。
如果 repl 是个函数,则在每次匹配发生时都会调用这个函数。 所有捕获到的子串依次作为参数传入。
这里有一些用例:

 x = string.gsub("hello world", "(%w+)", "%1 %1")
 --> x="hello hello world world"
 
 x = string.gsub("hello world", "%w+", "%0 %0", 1)
 --> x="hello hello world"
 
 x = string.gsub("hello world from Lua", "(%w+)%s*(%w+)", "%2 %1")
 --> x="world hello Lua from"
 
 x = string.gsub("home = $HOME, user = $USER", "%$(%w+)", os.getenv)
 --> x="home = /home/roberto, user = roberto"
 
 x = string.gsub("4+5 = $return 4+5$", "%$(.-)%$", function (s)
       return load(s)()
     end)
 --> x="4+5 = 9"
 
 local t = {name="lua", version="5.3"}
 x = string.gsub("$name-$version.tar.gz", "%$(%w+)", t)
 --> x="lua-5.3.tar.gz"
 

string.len (s)
接收一个字符串,返回其长度。 空串 "" 的长度为 0 。

string.lower (s)
接收一个字符串,将其中的大写字符都转为小写后返回其副本。
string.upper (s)

string.pack (fmt, v1, v2, ···)
string.unpack (fmt, s [, pos])
返回一个打包了(即以二进制形式序列化) v1, v2 等值的二进制字符串。 字符串 fmt 为打包格式

string.rep (s, n [, sep])
返回 n 个字符串 s 以字符串 sep 为分割符连在一起的字符串。 默认的 sep 值为空字符串(即没有分割符)。

string.reverse (s)
返回字符串 s 的翻转串。

匹配模式

Lua 中的匹配模式直接用常规的字符串来描述。 它用于模式匹配函数 string.find, string.gmatch, string.gsub, string.match

字符类:
字符类 用于表示一个字符集合。 下列组合可用于字符类:

x: (这里 x 不能是 魔法字符 ^$()%.[]*+-? 中的一员) 表示字符 x 自身。
.: (一个点)可表示任何字符。
%a: 表示任何字母。
%c: 表示任何控制字符。
%d: 表示任何数字。
%g: 表示任何除空白符外的可打印字符。
%l: 表示所有小写字母。
%p: 表示所有标点符号。
%s: 表示所有空白字符。
%u: 表示所有大写字母。
%w: 表示所有字母及数字。
%x: 表示所有 16 进制数字符号。
%x: (这里的 x 是任意非字母或数字的字符) 表示字符 x。 这是对魔法字符转义的标准方法。 所有非字母或数字的字符 (包括所有标点,也包括非魔法字符) 都可以用前置一个 "%" 放在模式串中表示自身。

交叉使用类和范围的行为未定义。 因此,像 [%a-z] 或 [a-%%] 这样的模式串没有意义。

所有单个字母表示的类别(%a,%c,等), 若将其字母改为大写,均表示对应的补集。 例如,%S 表示所有非空格的字符。

如何定义字母、空格、或是其他字符组取决于当前的区域设置。 特别注意:[a-z] 未必等价于 %l 。

模式条目:
模式条目 可以是
单个字符类匹配该类别中任意单个字符;
单个字符类跟一个 "*", 将匹配零或多个该类的字符。 这个条目总是匹配尽可能长的串;
单个字符类跟一个 "+", 将匹配一或更多个该类的字符。 这个条目总是匹配尽可能长的串;
单个字符类跟一个 "-", 将匹配零或更多个该类的字符。 和 "*" 不同, 这个条目总是匹配尽可能短的串;
单个字符类跟一个 "?", 将匹配零或一个该类的字符。 只要有可能,它会匹配一个;
%n, 这里的 n 可以从 1 到 9; 这个条目匹配一个等于 n 号捕获物(后面有描述)的子串。
%bxy, 这里的 x 和 y 是两个明确的字符; 这个条目匹配以 x 开始 y 结束, 且其中 x 和 y 保持 平衡 的字符串。 意思是,如果从左到右读这个字符串,对每次读到一个 x 就 +1 ,读到一个 y 就 -1, 最终结束处的那个 y 是第一个记数到 0 的 y。 举个例子,条目 %b() 可以匹配到括号平衡的表达式。
%f[set], 指 边境模式; 这个条目会匹配到一个位于 set 内某个字符之前的一个空串, 且这个位置的前一个字符不属于 set 。 集合 set 的含义如前面所述。 匹配出的那个空串之开始和结束点的计算就看成该处有个字符 "0" 一样。
模式:

模式 指一个模式条目的序列。 在模式最前面加上符号 "^" 将锚定从字符串的开始处做匹配。 在模式最后面加上符号 "$" 将使匹配过程锚定到字符串的结尾。 如果 "^" 和 "$" 出现在其它位置,它们均没有特殊含义,只表示自身。

捕获:
模式可以在内部用小括号括起一个子模式; 这些子模式被称为 捕获物。 当匹配成功时,由 捕获物 匹配到的字符串中的子串被保存起来用于未来的用途。 捕获物以它们左括号的次序来编号。 例如,对于模式 "(a(.)%w(%s))" , 字符串中匹配到 "a(.)%w(%s)" 的部分保存在第一个捕获物中 (因此是编号 1 ); 由 "." 匹配到的字符是 2 号捕获物, 匹配到 "%s*" 的那部分是 3 号。

作为一个特例,空的捕获 () 将捕获到当前字符串的位置(它是一个数字)。 例如,如果将模式 "()aa()" 作用到字符串 "flaaap" 上,将产生两个捕获物: 3 和 5 。

UTF-8 支持

这个库提供了对 UTF-8 编码的基础支持。 所有的函数都放在表 utf8 中。

utf8.char (···)
接收零或多个整数, 将每个整数转换成对应的 UTF-8 字节序列,并返回这些序列连接到一起的字符串。

utf8.charpattern
用于精确匹配到一个 UTF-8 字节序列的模式(是一个字符串,并非函数)"0-x7FxC2-xF4*"

utf8.len (s [, i [, j]])
返回字符串 s 中 从位置 i 到 j 间 (包括两端) UTF-8 字符的个数。 默认的 i 为 1 ,默认的 j 为 -1 。

utf8.offset (s, n [, i])
返回编码在 s 中的第 n 个字符的开始位置(按字节数) (从位置 i 处开始统计)。

其余看文档

表处理

这个库提供了表处理的通用函数。 所有函数都放在表 table 中。

table.concat (list [, sep [, i [, j]]])
返回字符串 list[i]..sep..list[i+1] ··· sep..list[j]。 sep 的默认值是空串, i 的默认值是 1 , j 的默认值是 #list 。 如果 i 比 j 大,返回空串。

table.insert (list, [pos,] value)
在 list 的位置 pos 处插入元素 value , 并后移元素 list[pos], list[pos+1], ···, list[#list] 。 pos 的默认值为 #list+1 , 因此调用 table.insert(t,x) 会将 x 插在列表 t 的末尾。

table.remove (list [, pos])
移除 list 中 pos 位置上的元素,并返回这个被移除的值。

table.sort (list [, comp])
在表内从 list[1] 到 list[#list] 原地 对其间元素按指定次序排序。 如果提供了 comp , 它必须是一个可以接收两个列表内元素为参数的函数。 当第一个元素需要排在第二个元素之前时,返回真 (因此 not comp(list[i+1],list[i]) 在排序结束后将为真)。

table.pack (···)
返回用所有参数以键 1,2, 等填充的新表, 并将 "n" 这个域设为参数的总数。 注意这张返回的表不一定是一个序列。

table.unpack (list [, i [, j]])
返回列表中的元素。 这个函数等价于
return list[i], list[i+1], ···, list[j]
i 默认为 1 ,j 默认为 #list。

数学函数

这个库提供了基本的数学函数。 所以函数都放在表 math 中。
略。。。

输入输出库

I/O 库提供了两套不同风格的文件处理接口。 第一种风格使用隐式的文件句柄; 它提供设置默认输入文件及默认输出文件的操作, 所有的输入输出操作都针对这些默认文件。 第二种风格使用显式的文件句柄。

当使用隐式文件句柄时, 所有的操作都由表 io 提供。 若使用显式文件句柄, io.open 会返回一个文件句柄,且所有的操作都由该文件句柄的方法来提供。

表 io 中也提供了三个 和 C 中含义相同的预定义文件句柄: io.stdin, io.stdout, 以及 io.stderr。 I/O 库永远不会关闭这些文件。

I/O 函数在出错时都返回 nil (第二个返回值为错误消息,第三个返回值为系统相关的错误码)。 成功时返回与 nil 不同的值。

隐式文件句柄操作

io.close ([file])
等价于 file:close()。 不给出 file 时将关闭默认输出文件。

io.flush ()

io.lines ([filename ···])
读模式打开指定的文件名并返回一个迭代函数。 此迭代函数的工作方式和用一个已打开的文件去调用 file:lines(···) 得到的迭代器相同。 当迭代函数检测到文件结束, 它不返回值(让循环结束)并自动关闭文件
调用 io.lines() (不传文件名) 等价于 io.input():lines("*l"); 即,它将按行迭代标准输入文件。 在此情况下,循环结束后它不会关闭文件。

io.open (filename [, mode])
这个函数用字符串 mode 指定的模式打开一个文件。 返回新的文件句柄。 当出错时,返回 nil 加错误消息。
mode 字符串可以是下列任意值:
"r": 读模式(默认);
"w": 写模式;
"a": 追加模式;
"r+": 更新模式,所有之前的数据都保留;
"w+": 更新模式,所有之前的数据都删除;
"a+": 追加更新模式,所有之前的数据都保留,只允许在文件尾部做写入。
mode 字符串可以在最后加一个 "b" , 这会在某些系统上以二进制方式打开文件。

io.popen (prog [, mode])
这个函数和系统有关,不是所有的平台都提供。
用一个分离进程开启程序 prog, 返回的文件句柄可用于从这个程序中读取数据 (如果 mode 为 "r",这是默认值) 或是向这个程序写入输入(当 mode 为 "w" 时)。

io.input ([file])
用文件名调用它时,(以文本模式)来打开该名字的文件, 并将文件句柄设为默认输入文件。 如果用文件句柄去调用它, 就简单的将该句柄设为默认输入文件。 如果调用时不传参数,它返回当前的默认输入文件。

io.read (···)
等价于 io.input():read(···)。

io.tmpfile ()
返回一个临时文件的句柄。 这个文件以更新模式打开,在程序结束时会自动删除。

io.type (obj)
检查 obj 是否是合法的文件句柄。 如果 obj 它是一个打开的文件句柄,返回字符串 "file"。 如果 obj 是一个关闭的文件句柄,返回字符串 "closed file"。 如果 obj 不是文件句柄,返回 nil 。

io.output ([file])
类似于 io.input。 不过都针对默认输出文件操作。

io.write (···)
等价于 io.output():write(···)。

显式文件句柄操作
若使用显式文件句柄, io.open 会返回一个文件句柄

file:close ()
关闭 file。

file:flush ()
将写入的数据保存到 file 中。

file:lines (···)
返回一个迭代器函数, 每次调用迭代器时,都从文件中按指定格式读数据。 如果没有指定格式,使用默认值 "l" 。 看一个例子

for c in file:lines(1) do body end

会从文件当前位置开始,中不断读出字符。 和 io.lines 不同, 这个函数在循环结束后不会关闭文件。

file:read (···)
读文件 file, 指定的格式决定了要读什么。 对于每种格式,函数返回读出的字符对应的字符串或数字。 若不能以该格式对应读出数据则返回 nil。 (对于最后这种情况, 函数不会读出后续的格式。) 当调用时不传格式,它会使用默认格式读下一行(见下面描述)。
提供的格式有
"n": 读取一个数字,根据 Lua 的转换文法,可能返回浮点数或整数。 (数字可以有前置或后置的空格,以及符号。) 只要能构成合法的数字,这个格式总是去读尽量长的串; 如果读出来的前缀无法构成合法的数字 (比如空串,"0x" 或 "3.4e-"), 就中止函数运行,返回 nil。
"i": 读取一个整数,返回整数值。
"a": 从当前位置开始读取整个文件。 如果已在文件末尾,返回空串。
"l": 读取一行并忽略行结束标记。 当在文件末尾时,返回 nil 这是默认格式。
"L": 读取一行并保留行结束标记(如果有的话), 当在文件末尾时,返回 nil。
number: 读取一个不超过这个数量字节数的字符串。 当在文件末尾时,返回 nil。 如果 number 为零, 它什么也不读,返回一个空串。 当在文件末尾时,返回 nil。
格式 "l" 和 "L" 只能用于文本文件。

file:seek ([whence [, offset]])
设置及获取基于文件开头处计算出的位置。 设置的位置由 offset 和 whence 字符串 whence 指定的基点决定。基点可以是:
"set": 基点为 0 (文件开头);
"cur": 基点为当前位置了;
"end": 基点为文件尾;
当 seek 成功时,返回最终从文件开头计算起的文件的位置。 当 seek 失败时,返回 nil 加上一个错误描述字符串。
whence 的默认值是 "cur", offset 默认为 0 。 因此,调用 file:seek() 可以返回文件当前位置,并不改变它; 调用 file:seek("set") 将位置设为文件开头(并返回 0); 调用 file:seek("end") 将位置设到文件末尾,并返回文件大小。

file:setvbuf (mode [, size])
设置输出文件的缓冲模式。 有三种模式:
"no": 不缓冲;输出操作立刻生效。
"full": 完全缓冲;只有在缓存满或当你显式的对文件调用 flush(参见 io.flush) 时才真正做输出操作。
"line": 行缓冲; 输出将到每次换行前, 对于某些特殊文件(例如终端设备)缓冲到任何输入前。
对于后两种情况,size 以字节数为单位 指定缓冲区大小。 默认会有一个恰当的大小。

file:write (···)
将参数的值逐个写入 file。 参数必须是字符串或数字。
成功时,函数返回 file。 否则返回 nil 加错误描述字符串。

操作系统库

这个库都通过表 os 实现。

os.clock ()
返回程序使用的按秒计 CPU 时间的近似值。

os.date ([format [, time]])
返回一个包含日期及时刻的字符串或表。 格式化方法取决于所给字符串 format。
如果提供了 time 参数, 格式化这个时间 (这个值的含义参见 os.time 函数)。 否则,date 格式化当前时间。
如果 format 以 "!" 打头, 日期以协调世界时格式化。 在这个可选字符项之后, 如果 format 为字符串 "*t", date 返回有后续域的表: year (四位数字),month (1–12),day (1–31), hour (0–23),min (0–59),sec (0–61), wday (星期几,星期天为 1 ), yday (当年的第几天), 以及 isdst (夏令时标记,一个布尔量)。 对于最后一个域,如果该信息不提供的话就不存在。
如果 format 并非 "*t", date 以字符串形式返回, 格式化方法遵循 ISO C 函数 strftime 的规则。

os.difftime (t2, t1)
返回以秒计算的时刻 t1 到 t2 的差值。 (这里的时刻是由 os.time 返回的值)。

os.execute ([command])
它调用系统解释器执行 command。 如果命令成功运行完毕,第一个返回值就是 true, 否则是 nil。 在第一个返回值之后,函数返回一个字符串加一个数字。如下:
"exit": 命令正常结束; 接下来的数字是命令的退出状态码。
"signal": 命令被信号打断; 接下来的数字是打断该命令的信号。

os.exit ([code [, close]])
调用 ISO C 函数 exit 终止宿主程序。 如果 code 为 true, 返回的状态码是 EXIT_SUCCESS; 如果 code 为 false, 返回的状态码是 EXIT_FAILURE; 如果 code 是一个数字, 返回的状态码就是这个数字。 code 的默认值为 true。

os.getenv (varname)
返回进程环境变量 varname 的值, 如果该变量未定义,返回 nil 。

os.remove (filename)
删除指定名字的文件(在 POSIX 系统上可以是一个空目录) 如果函数失败,返回 nil 加一个错误描述串及出错码。

os.rename (oldname, newname)
将名字为 oldname 的文件或目录更名为 newname。 如果函数失败,返回 nil 加一个错误描述串及出错码。

os.time ([table])
当不传参数时,返回当前时刻。 如果传入一张表,就返回由这张表表示的时刻。 这张表必须包含域 year,month,及 day; 可以包含有 hour (默认为 12 ), min (默认为 0), sec (默认为 0),以及 isdst (默认为 nil)。

调试库

这个库里的所有函数都提供在表 debug 内。 所有操作线程的函数,可选的第一个参数都是针对的线程。 默认值永远是当前线程。

debug.traceback ([thread,] [message [, level]])
如果 message 有,且不是字符串或 nil, 函数不做任何处理直接返回 message。 否则,它返回调用栈的栈回溯信息。 字符串可选项 message 被添加在栈回溯信息的开头。 数字可选项 level 指明从栈的哪一层开始回溯 (默认为 1 ,即调用 traceback 的那里)。
其他,请看文档

独立版 Lua

虽然 Lua 被设计成一门扩展式语言,用于嵌入一个宿主程序。 但经常也会被当成独立语言使用。 独立版的 Lua 语言解释器随标准包发布,就叫 lua。 其命令行用法为:

lua [options] [script [args]]

选项有:

-e stat: 执行一段字符串 stat ;
-l mod: “请求模块” mod ;
-i: 在运行完 脚本 后进入交互模式;
-v: 打印版本信息;
-E: 忽略环境变量;
--: 中止对后面选项的处理;
-: 把 stdin 当作一个文件运行,并中止对后面选项的处理。

在处理完选项后,lua 运行指定的 脚本。 如果不带参数调用, 在标准输入(stdin)是终端时,lua 的行为和 lua -v -i 相同。 否则相当于 lua - 。

为了让 Lua 可以用于 Unix 系统的脚本解释器。

!/usr/local/bin/lua

如果 lua 在你的 PATH 中, 写成 #!/usr/bin/env lua更为通用。

与之前版本(5.2)不兼容的地方

该版本主要实现了对整数、位操作、UTF-8 的支持以及打包和解包的功能。
Lua 5.2 到 Lua 5.3 最大的变化是引入了数字的整数子类型,你可以通过把数字都强制转换为浮点数来消除差异 (在 Lua 5.2 中,所有的数字都是浮点数)。
bit32 库废弃了。 使用一个外部兼容库很容易, 不过最好直接用对应的位操作符来替换它。 (注意 bit32 只能针对 32 位整数运算, 而标准 Lua 中的位操作可以用于 64 位整数。)
io.read 的选项名不再用 "*" 打头。 但出于兼容性考虑,Lua 会继续忽略掉这个字符。
数学库中的这些函数废弃了: atan2, cosh, sinh, tanh, pow, frexp, 以及 ldexp 。 你可以用 x^y 替换 math.pow(x,y);
require 在搜索 C 加载器时处理版本号的方式有所变化。 现在,版本号应该跟在模块名后(其它大多数工具都是这样干的)。 出于兼容性考虑,如果使用新格式找不到加载器的话,搜索器依然会尝试旧格式。
可以参考:http://blog.codingnow.com/201...
http://blog.csdn.net/weiyuefe...

5.2与5.1的变化

5.1 与 5.2 是完全不兼容的,相关的第三方库必须重新为 5.2 适配。
luajit不支持5.2,5.3
具体见
http://www.lua.org/manual/5.2...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/39421.html

相关文章

  • 编译VIM

    摘要:编译最麻烦的只有一个问题各个语言的位置和开发库的位置。第一个我们直接使用的语言,第二个是本机编译开发能够引用的开发库。所以,根据语言支持的需要,我们要安装这些开发库如果安装好这些依赖,且明白各自的位置后,剩下的编译是超级简单的。 编译VIM最麻烦的只有一个问题:各个语言的位置和开发库的位置。 注意:语言本身的位置好说,但是dev开发库就不一样了。比如,一般我们本机只安装python,而...

    tianyu 评论0 收藏0
  • Openresty的开发闭环初探

    摘要:多返回值开始变得越来越与众不同了允许函数返回多个结果。这种情况函数没有足够的返回值时也会用来补充。中的索引习惯以开始。 showImg(https://segmentfault.com/img/bVIcQU?w=136&h=103); 为什么值得入手? Nginx作为现在使用最广泛的高性能后端服务器,Openresty为之提供了动态预言的灵活,当性能与灵活走在了一起,无疑对于被之前陷于...

    ruicbAndroid 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<