_ENV和_G

5.1之前, 全局變量存儲在_G這個table中, 這樣的操作:
a = 1 
相當於:
_G['a'] = 1

但在5.2之後, 引入了_ENV叫做環境,與_G全局變量表產生了一些混淆,需要從原理上做一個理解。
在5.2中, 
操作a = 1
相當於
_ENV['a'] = 1
這是一個最基礎的認知改變,其次要格外注意_ENV不是全局變量,而是一個upvalue(非局部變量)。

其次,_ENV['_G']指向了_ENV自身,這一目的是爲了兼容5.1之前的版本,因爲之前你也許會用到:

_G['a'] = 2 , 在5.2中, 這相當於_ENV['_G']['a'],爲了避免5.1之前的老代碼在5.2中運行錯誤,所以5.2設置了_ENV['_G']=_ENV來兼容這個問題。然而你不要忘記_ENV['_G']=_ENV,所以一切都順理成章了。

在5.1中,我們可以爲一段代碼塊(或者函數)設置環境,使用函數setfuncs,這樣會導致那一段代碼/函數訪問全局變量的時候使用了setfuncs指定的table,而不是全局的_G。

在5.2中,setfuncs遭到了廢棄,因爲引入了_ENV。 通過在函數定義前覆蓋_ENV變量即可爲函數定義設置一個全新的環境,比如:
a = 3
function get_echo()
local _ENV={print=print, a = 2}
return function echo()
print(a)
end
end

get_echo()()
會打印2,而不是3,因爲echo函數的環境被修改爲{print=print, a=2},而print(a)相當於訪問_ENV['a'](先忘掉那爲了兼容而存在的_G)。

這就是_ENV的基本用法了。

另外,不得不提到lua的C支持中關於全局變量與環境的細節,只能簡單描述,你必須自己試試才能記得清楚。

lua_setglobal/lua_getglobal都是操作lua_State註冊表中LUA_RIDX_GLOBALS僞索引指向的全局變量表,與lua中訪問_ENV['a']或者a是不同的。

lua_load加載lua代碼後會返回一個函數,默認會給這個函數設置一個upvalue就叫_ENV,起值是LUA_RIDX_GLOBALS的全局變量表,你可以lua_setupvalue設置這個函數的upvalue,即下標1的upvalue,因爲這個位置是這個函數的_ENV表存放位置(你可以通過lua_setupvalue的返回值印證這一點)

這裏巧妙的是,lua_State會在創建時保證LUA_RIDX_GLOBALS的全局變量表中包含一個指向自己的_G元素,這樣就保證了在不調用lua_setupvalue的情況下該返回函數的_ENV['_G']是指向自己的,即LUA_RIDX_GLOBALS這個全局表。(其實你的lua解釋器就是簡單的lua_load後pcall的,對於一個剛啓動lua_State來說是沒有_ENV的,是lua解釋器load你的代碼時自動給帶上的_ENV,其值是lua_state的LUA_RIDX_GLOBALS全局表。)

一些有意思的東西是需要你自己摸索的,lua語言自身就很簡練,並且所有東西都不是什麼神祕的事情,可以通過讀源碼或者試驗摸索得到。

最後,提一下,lua_state啓動後在註冊表裏LUA_RIDX_GLOBALS下標存放的全局表一定有一個元素是指向自己的,即_G.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章