Lua中關於table物件引用傳遞的注意事項
內容導航
前言
最近寫了挺長一段時間的Lua,發現Lua這個語言真的是很隨意,產生這種感覺的根本原因應該是它把“函式” 作為了“第一類值”,也就是說函式也可以作為變數的“值”,這使得Lua可以隨處定義函式,進而改變邏輯的走向,整個流程任你擺佈。
雖說把一個函式來回設定方便了許多,但是同樣帶來了一些不容易發現的問題,如果搞不清定義域和引用關係,常常會導致程式錯誤,比如最近用Lua寫按鈕的觸發事件時,使用公有函式建立了對應的閉包,一開始感覺table的引用有問題,寫了很多中轉的程式碼,最後發現直接就可以使用,浪費了不少時間,最後仔細分析就是閉包最根本的形式,只不過被業務邏輯給干擾了視線,接下來我們一起看看,table和閉包究竟會發生什麼關係!
程式碼測試
- table作為函式引數時的操作
print("\nexample 1:");
data_table = {a = 1, b = 2, 3, 4, 5, 6};
function filter(data_tb)
for k,v in pairs(data_tb) do
if v % 2 == 0 then
data_tb[k] = nil;
end
end
end
-- 過濾掉偶數
filter(data_table);
for k,v in pairs(data_table) do
print(k, v)
end
example 1:
1 3
3 5
a 1
以上為去掉table中的偶數的程式碼,直接操作引數data_tb
就可以對傳入的data_table
進行改變,這樣的邏輯一般不會出錯,接著我們看下接下來需求,直接將表中資料清空。
print("\nexample 2:");
data_table = {a = 1, b = 2, 3, 4, 5, 6};
function destroy(data_tb)
data_tb = {};
end
-- 銷燬整個表
destroy(data_table);
for k,v in pairs(data_table) do
print (k,v)
end
example 2:
1 3
2 4
3 5
4 6
b 2
a 1
看到這次的輸出可能有些人就感到奇怪了,怎麼上個例子改變元素可以,而這裡直接給變數data_tb
賦值,變成空表怎麼不行了?這是因為data_tb
是對變數data_table
的整體引用,所以可以通過data_tb
來改變變數data_table
內部的值,但是當執行data_tb = {};
程式碼時表示data_tb
不再引用data_table
,而去引用{}
了,也就是data_tb
和data_table
脫離了關係,這一點可以類比C++程式碼:
#include <iostream>
using namespace std;
void change_string(char* pStr)
{
pStr[0] = '5';
pStr[1] = '0';
pStr = "only test\n";
}
int main()
{
char szContent[32] = "help";
change_string(szContent);
cout << szContent << endl;
return 0;
}
分析一下這段程式碼的輸出結果,如果你能知道結果為50lp
,那說明你的C++水平已經超過了入門級別,理解了這段程式碼有助於清楚的理解前兩段Lua程式碼。
- 看一個標準閉包實現的計數器
print("\nexample 3:");
function counter()
local count = 0;
return function()
count = count + 1;
return count;
end
end
func = counter();
print(func());
print(func());
print(func());
example 3:
1
2
3
這段程式碼的不同之處就在於變數count
,這是一個標準的計數器,也是一個標準的閉包,也就是說Lua支援這樣的語法,閉包中可以在定義之後一直引用外部的變數,並且在返回函式的整個使用生命週期內都可以引用這個變數,加入外部修改了這個變數,閉包中引用的值也會改變,換句話來說就是閉包這種引用是引用的變數,而不是僅僅儲存了一個值。
- lua中常見的table引用
print("\nexample 4:");
local t1 = {i = 1};
local t2 = t1;
t1.i = 666;
print(t2.i)
example 4:
666
這個例子類似於前面“過濾掉偶數”的程式碼,首先定義了表t1
,然後定義了變數t2
引用了變數t1
,實際上這裡t2
不是定義了變數t1
本身,而是引用了t1
的值,也就是引用的是{i=1}
,這裡通過t1.i = 666
也可以影響到變數t2
,其實這個例子看不出引用的究竟是變數t1
還是t1
的值,可以接著看下面的例子。
print("\nexample 5:");
local t1 = {i = 1};
local t2 = t1;
t1 = {i = 11};
print(t2.i)
example 5:
1
通過這個例子就很清楚了,前面的部分和上個例子一致,但是後面直接給變數t1
賦值時並沒有改變t2
的值,由此可以看出t1
和t2
已經“分離”了。
- table引用和閉包結合的例子
print("\nexample 6:");
local tb = {i= 1};
function outer()
return function()
local t = tb;
print(t.i);
end
end
local show = outer();
tb = {i = 6};
show();
example 6:
6
這個例子應該會有猜錯結果的人,我自己就是在類似的程式碼中搞糊塗的,一種想法是函式outer
定義的時候變數t
的值已經定義了,還有一種就是認為在返回函式show
的時候變數t
的值會定義,但是這兩種想法都是錯誤的,實際上是呼叫函式show
的時候才給t
賦值,這時變數t
引用的是擁有最新值的tb
,所以t.i
的值是6,如果你猜對了這個例子的結果,接下來看看下面的程式碼。
print("\nexample 7:");
local tb = {i= 1};
function outer()
local t = tb;
return function()
print(t.i);
end
end
local show = outer();
tb = {i = 7};
show();
example 7:
1
如果清楚了上個例子的執行過程,就應該很容易知道這個例子的結果,其中變數t
的值是在呼叫函式outer
時確定的,所以後面的賦值tb = {i = 7};
對變數t
的值沒有影響。
總結
- lua中操作變數注意值和引用,其實很多語言都有這種區分。
- 注意閉包可以訪問外部變數的特性,程式中使用起來非常方便。
- 實際使用過程中往往還夾雜著業務邏輯,要學會挖掘本質問題,這樣往往可以看到真正的執行邏輯。