Lua中关于table对象引用传递的注意事项

内容导航

  • 前言
  • 代码测试
  • 总结
  • 测试源码

前言

最近写了挺长一段时间的Lua,发现Lua这个语言真的是很随意,产生这种感觉的根本原因应该是它把“函数” 作为了“第一类值”,也就是说函数也可以作为变量的“值”,这使得Lua可以随处定义函数,进而改变逻辑的走向,整个流程任你摆布。

虽说把一个函数来回设置方便了许多,但是同样带来了一些不容易发现的问题,如果搞不清定义域和引用关系,常常会导致程序错误,比如最近用Lua写按钮的触发事件时,使用公有函数创建了对应的闭包,一开始感觉table的引用有问题,写了很多中转的代码,最后发现直接就可以使用,浪费了不少时间,最后仔细分析就是闭包最根本的形式,只不过被业务逻辑给干扰了视线,接下来我们一起看看,table和闭包究竟会发生什么关系!

代码测试

  1. 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_tbdata_table脱离了关系,这一点可以类比C++代码:

#include 
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代码。

  1. 看一个标准闭包实现的计数器
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支持这样的语法,闭包中可以在定义之后一直引用外部的变量,并且在返回函数的整个使用生命周期内都可以引用这个变量,加入外部修改了这个变量,闭包中引用的值也会改变,换句话来说就是闭包这种引用是引用的变量,而不是仅仅保存了一个值。

  1. 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的值,由此可以看出t1t2已经“分离”了。

  1. 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的值没有影响。

总结

  1. lua中操作变量注意值和引用,其实很多语言都有这种区分。
  2. 注意闭包可以访问外部变量的特性,程序中使用起来非常方便。
  3. 实际使用过程中往往还夹杂着业务逻辑,要学会挖掘本质问题,这样往往可以看到真正的运行逻辑。

测试源码

示例传送门:lua中table引用

你可能感兴趣的:(C++,Lua)