持续更新

记录 lua 相关的常识,以及使用过程中遇到的问题。


1. 常识


1.1 pairs 的底层实现

lua manual 关于 pairs 的描述[1] :

If t has a metamethod __pairs, calls it with t as argument and returns the first three results from the call.

Otherwise, returns three values: the next function, the table t, and nil, so that the construction

for k,v in pairs(t) do body end     

will iterate over all key–value pairs of table t.


pairs 对应的 api 是 luaB_pairs,它要求传入一个 table 类型的参数 t,逻辑如下:
1)如果 t 的元表包括 __pairs 元方法,则调用此元方法,元方法同样要求返回三个值。
2)否则,返回的是三个值:next 函数(luaB_next),t,nil。


假设 t 是 table,在 for 循环中调用 pairs(t) 的工作过程大致如下:
1)调用 pairs(t) 返回 next, t, nil;
2)调用 next 进行迭代,直到返回空的 key;

k1, v1 = next(t, nil)   
k2, v2 = next(t, k1)    
...
直到 kn 为 nil。   


luaB_pairs 的源码:

static int luaB_pairs (lua_State *L) {
  return pairsmeta(L, "__pairs", 0, luaB_next);
}

static int pairsmeta (lua_State *L, const char *method, int iszero,
                      lua_CFunction iter) {
  luaL_checkany(L, 1);
  if (luaL_getmetafield(L, 1, method) == LUA_TNIL) {  /* no metamethod? */
    lua_pushcfunction(L, iter);  /* will return generator, */
    lua_pushvalue(L, 1);  /* state, */
    if (iszero) lua_pushinteger(L, 0);  /* and initial value */
    else lua_pushnil(L);
  }
  else {
    lua_pushvalue(L, 1);  /* argument 'self' to metamethod */
    lua_call(L, 1, 3);  /* get 3 values from metamethod */
  }
  return 3;
}

1.2 ipairs 的底层实现

lua manual 关于 ipairs 的描述 [2]:

Returns three values (an iterator function, the table t, and 0) so that the construction

for i,v in ipairs(t) do body end

will iterate over the key–value pairs (1,t[1]), (2,t[2]), …, up to the first nil value.


与 pairs 不同,1)ipairs 没有对应的元方法 __ipairs;2)ipairs 只遍历正整数键,从 1 开始遍历,直到遇到第一个 nil 键。

与 pairs 相似的,ipairs 也是返回三个值:next, t, 0。它对应的 api 是 luaB_ipairs,它的 next 函数对应的是 ipairsaux


假设 t 是 table,在 for 循环中调用 ipairs(t) 的工作过程大致如下:
1)调用 pairs(t) 返回 next, t, 0;
2)调用 next 进行迭代,直到返回空的 key;

k1, v1 = next(t, 0)   
k2, v2 = next(t, k1)    
...
直到 kn 为 nil。   


luaB_ipairs 的源码:

static int ipairsaux (lua_State *L) {
  lua_Integer i = luaL_checkinteger(L, 2) + 1;
  lua_pushinteger(L, i);
  return (lua_geti(L, 1, i) == LUA_TNIL) ? 1 : 2;
}

static int luaB_ipairs (lua_State *L) {
#if defined(LUA_COMPAT_IPAIRS)
  return pairsmeta(L, "__ipairs", 1, ipairsaux);
#else
  luaL_checkany(L, 1);
  lua_pushcfunction(L, ipairsaux);  /* iteration function */
  lua_pushvalue(L, 1);  /* state */
  lua_pushinteger(L, 0);  /* initial value */
  return 3;
#endif
}

1.3 for statement 的两种模式

简单描述,for 有两种模式。

一、数字迭代

形式是 for v = var, limit, step do block end

比如:

for i = 1, 2, 1 do
    print(i)
end

打印出:

1
2

有一点要注意的,像下面这样的代码, #t 只在初始时计算一次:

local t = {1,2,3}
for i = 1, #t do
    print(i, t[i])
end


二、通用迭代

形式是 for var1, var2, ... varn in explist do block end

explist 是由三个值构成的,比如 pairs(t) 返回 next、t、nil,其中 next 是迭代函数,t 是表,nil 是初始的键值。

那么当 t 是一个普通的没有 __pairs 元方法的表时, for k, v in pairs(t) do block endfor k, v in next, t, nil do block end 是等价的。

比如:

local t = {1,2,3, hello="world"}
for k, v in next, t, nil do
    print(k, v)
end

会打印出:

1	1
2	2
3	3
hello	world


这种迭代是通用的,也就是说,explist 只要能返回 迭代函数 f、变量 s、变量 var,for 就会执行这样的等价逻辑:

do
  local f, s, var = explist
  while true do
    local var_1, ···, var_n = f(s, var)
    if var_1 == nil then break end
    var = var_1
    block
  end
end


与 pairs 类似的,string.gmatch 也会返回迭代函数,所以也可以与 for 配合工作,比如[4]:

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

输出:

hello
world
from
Lua




lua manual 关于 for statement 的描述 [3]:

The for statement has two forms: one numerical and one generic.

The numerical for loop repeats a block of code while a control variable runs through an arithmetic progression. It has the following syntax:

stat ::= for Name ‘=’ exp ‘,’ exp [‘,’ exp] do block end

The block is repeated for name starting at the value of the first exp, until it passes the second exp by steps of the third exp. More precisely, a for statement like

for v = e1, e2, e3 do block end

is equivalent to the code:

do
  local var, limit, step = tonumber(e1), tonumber(e2), tonumber(e3)
  if not (var and limit and step) then error() end
  var = var - step
  while true do
    var = var + step
    if (step >= 0 and var > limit) or (step < 0 and var < limit) then
      break
    end
    local v = var
    block
  end
end

Note the following:

  • All three control expressions are evaluated only once, before the loop starts. They must all result in numbers.
  • var, limit, and step are invisible variables. The names shown here are for explanatory purposes only.
  • If the third expression (the step) is absent, then a step of 1 is used.
  • You can use break and goto to exit a for loop.
  • The loop variable v is local to the loop body. If you need its value after the loop, assign it to another variable before exiting the loop.

The generic for statement works over functions, called iterators. On each iteration, the iterator function is called to produce a new value, stopping when this new value is nil. The generic for loop has the following syntax:

stat ::= for namelist in explist do block end
namelist ::= Name {‘,’ Name}

A for statement like

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

is equivalent to the code:

do
  local f, s, var = explist
  while true do
    local var_1, ···, var_n = f(s, var)
    if var_1 == nil then break end
    var = var_1
    block
  end
end

Note the following:

  • explist is evaluated only once. Its results are an iterator function, a state, and an initial value for the first iterator variable.
  • f, s, and var are invisible variables. The names are here for explanatory purposes only.
  • You can use break to exit a for loop.
  • The loop variables var_i are local to the loop; you cannot use their values after the for ends. If you need these values, then assign them to other variables before breaking or exiting the loop.

1.4 _ENV_G

1、lua5.2 开始,_G 就相当于 _ENV,并且 _ENV 中包含一个 field _G 来指向 _G

所以,以下的值是相等的:

_G == _ENV == _ENV['_G'] = _G['_G']


2、lua 在遇到 _G 的时候是这样处理的,去 _ENV 表中查找名为 _G 的元素。

比如这样的语句:

print(_G)

翻译成字节码是这样的:

function main(...) --line 1 through 1
1	GETTABUP	0 0 -1	; _ENV "print"
2	GETTABUP	1 0 -2	; _ENV "_G"
3	CALL	0 2 1	
4	RETURN	0 1	

upvalues (1)
index	name	instack	idx	kind
0	_ENV	true	0	VDKREG (regular)

constants (2)
index	type	value
1	string	"print"
2	string	"_G"
end

_ENV 是这段代码的 upvalue,GETTABUP 1 0 -2 ; _ENV "_G" 就表示从 _ENV 这个类型为 table 的 upvalue 中获取名字为 _G 的成员。


3、但要注意,_ENV 里面并不包含一个名为 '_ENV' 的成员,即 _ENV['_ENV'] 是 nil 的。


2. 参考

[1] lua.org. pairs (t). Available at https://lua.org/manual/5.3/manual.html#pdf-pairs.

[2] lua.org. ipairs (t). Available at https://lua.org/manual/5.3/manual.html#pdf-ipairs.

[3] lua.org. For Statement. Available at https://lua.org/manual/5.3/manual.html#3.3.5.

[4] lua.org. string.gmatch (s, pattern). Available at https://lua.org/manual/5.3/manual.html#pdf-string.gmatch.