Lua中的全局變量與環境

環境的概念

Lua中類型爲thread,function和userata的對象均可以關聯一個表,稱之爲環境。環境也是一個常規的table。能夠和普通的table同樣進行操做,存放與對象相關的各類變量。程序員

  • 關聯的thread上的環境只能經過C代碼中訪問。
  • 關聯在userdata上的環境在 Lua 中沒有意義。 這個東西只是爲了在程序員想把一個表關聯到一個 userdata 上時提供便利。
  • 關聯在function上的環境用來接管本函數內全局變量的訪問。

全局變量

Lua中的全局變量存在放當前函數的環境中,Lua標準庫中的函數如setmetable, string.find等註冊在函數的環境中,在Lua腳本就能夠直接訪問。閉包

操做函數的環境

Lua代碼中能夠訪問和操做和函數關聯的環境,Lua標準庫爲此提供了兩個方法函數

  • getfenv 獲取當前函數的環境
  • setfenv 設置當前對象函數關聯的環境

查看全部的全局變量

經過getfenv(0)獲取當前環境後遍歷,能夠查看全部的全局變量。lua

for k, v in pairs(getfenv(0)) do
    print ("k: ", k, ", v: ", type(v))
end

定義全局變量與局部變量

Lua中定義的變量默認爲全局變量,保存在當前函數的環境中。 以下面的代碼code

local env = getfenv(1)
print("var: ", env["var"])
print("loval_var: ", env["loval_var"])

var = "Hello, global variable"
local local_var = "Hell, local variable"

print("after set")
print("var: ", env["var"])
print("loval_var: ", env["loval_var"])

變量var爲全局變量,存放在當前的環境中,經過getfenv().var能夠訪問,而變量local_var爲局部變量,getfenv().local_var值爲nil對象

執行結果字符串

$ lua test3.lua
var: 	nil
loval_var: 	nil
after set
var: 	Hello, global variable
loval_var: 	nil

嵌套函數的環境

在thread中經過load等方式建立的函數稱爲非嵌套函數,非嵌套函數的默認環境爲此thread的環境。在函數中建立函數時,會將本身的環境設置爲新建立的函數的默認環境。get

訪問全局變量時訪問的是當前所在函數的環境,以下面的代碼虛擬機

local function f()
    print("env: ", getfenv())

    local function f1()
        print("env f1: ", getfenv())
    end

    local function f2()
        print("env f2: ", getfenv())
    end

    f1()
    f2()
end

f()

執行結果 f,f1,f2三個函數的環境是同一個tablestring

env: 	table: 0x25a16b0
env f1: 	table: 0x25a16b0
env f2: 	table: 0x25a16b0

改變函數的環境

由於函數f1和f2都是在函數f中建立的,他們的環境的初始值就是函數f的環境。 下面經過setfenv改變函數的環境

local function f()
    print("env: ", getfenv())

    local function f1()
        print("env f1: ", getfenv())
    end

    local function f2()
        print("env f2: ", getfenv())
    end
    
    setfenv(f2, {})

    f1()
    f2()
end

f()

執行結果以下,getfenv自己就在環境中存放,經過setfenv(f2, {})將函數f2的環境設置成一個空的table,在函數f2中調用getfenv時因爲getfenv是nil,致使腳本報錯。

env: 	table: 0x17a66b0
env f1: 	table: 0x17a66b0
lua: test4.lua:9: attempt to call global 'getfenv' (a nil value)
stack traceback:
	test4.lua:9: in function 'f2'
	test4.lua:15: in function 'f'
	test4.lua:18: in main chunk
	[C]: ?

將函數f的環境經過閉包保存下來,就能夠在函數f2中調用其中的方法。以下所示

local function f()
    print("env: ", getfenv())

    local function f1()
        print("env f1: ", getfenv())
    end
    
    local env = getfenv()
    local function f2()
        local e = env
        e.print("env f2: ", e.getfenv())
    end
    
    setfenv(f2, {})

    f1()
    f2()
end

f()

此時能夠看到函數f2的環境與函數f和f1不一樣

$ lua test4.lua 
env: 	table: 0x1ebf6b0
env f1: 	table: 0x1ebf6b0
env f2: 	table: 0x1ec7480

_G

Lua中的_G是一個指向全局環境(thread的環境)的全局變量。詳細一點能夠這樣理解

  1. thread有一個環境,稱爲全局環境
  2. 全局環境中有一個變量,變量名爲_G, 值爲全局環境(_G._G = _G?)
  3. thread中調用load(string)或者loadstring等,解析Lua代碼,生成一個函數
  4. 生成的函數的環境默認爲thread的環境
  5. 在非嵌套函數中使用_G如print(_G), 打印的是全局環境,也是此函數的環境。

輸出_G和_G._G,值徹底相同

$ cat test.lua 
print(_G)
print(_G._G)
$ lua test.lua
table: 0x1d0c6b0
table: 0x1d0c6b0

若是經過setfenv設置了環境,新的環境是沒有_G這個變量的。

local function f()

    local function f1()
        print("f1: ", _G)
    end
    
    local env = getfenv()
    local function f2()
        local e = env
        e.print("f2: ", _G)
    end
    
    setfenv(f2, {})

    f1()
    f2()
end

f()

執行結果

f1: 	table: 0xf916b0
f2: 	nil

GETGLOBAL 和 lua_getglobal的迷惑

調用print輸出字符串print("Hello"),能夠經過luac查看編譯後的lua虛擬機的指令

$ luac -l -l test.lua

main <test.lua:0,0> (4 instructions, 16 bytes at 0xd92530)
0+ params, 2 slots, 0 upvalues, 0 locals, 2 constants, 0 functions
	1	[1]	GETGLOBAL	0 -1	; print
	2	[1]	LOADK    	1 -2	; "Hello"
	3	[1]	CALL     	0 2 1
	4	[1]	RETURN   	0 1
constants (2) for 0xd92530:
	1	"print"
	2	"Hello"
locals (0) for 0xd92530:
upvalues (0) for 0xd92530:

這裏只關注GETGLOBAL這條指令,print是全局變量,須要經過GETGLOBAL取到這個變量的值

對於GETGLOBAL,Lua虛擬機的執行以下(以lua5.1爲例),在函數luaV_execute中

case OP_GETGLOBAL: {
        TValue g;
        TValue *rb = KBx(i);
        sethvalue(L, &g, cl->env);
        lua_assert(ttisstring(rb));
        Protect(luaV_gettable(L, &g, rb, ra));
        continue;
      }

注意sethvalue(L, &g, cl->env);這句,cl表明當前的函數,cl->env指向當前函數的環境。即GETGLOBAL指令取得的是從當前函數的環境中取得變量的值的。

而lua_getglobal這個函數確是直接訪問thread的全局環境,取得全局環境中key爲name的值。

void lua_getglobal (lua_State *L, const char *name);

一樣都是"getglobal",倒是兩個不一樣的意思,直接讓我迷惑了好久,差點分不清什麼是全局變量了。

相關文章
相關標籤/搜索