【笨木头Lua专栏】基础补充04:for循环与迭代器的秘密

笨木头  2014-08-18 22:01   Cocos2d-x Lua   阅读(4,945)   7条评论

 

上一篇我们介绍了,可以使用for循环来完成迭代器的调用,十分简洁。

那么,具体这for循环做了什么呢?我当然没有去看源码,我只是看书而已。

资料来源于《Lua程序设计》第二版,如果这本书的内容没有错的话,那么,本篇文章理论上也不会有错~

笨木头花心贡献,哈?花心?不,是用心~

转载请注明,原文地址: http://www.benmutou.com/archives/1717

文章来源:笨木头与游戏开发

1.返回两个值的迭代器

pairs是能遍历table的key和value的,而我们之前写的dieDaiQi函数只能返回value。

所以,我们要改改dieDaiQi函数,如下:


1
2
3
4
5
6
7
8
9
10
11
function dieDaiQi(t)
    local i = 0;
    return function()
        i = i + 1;  
       
        if i > #t then
            return nil;
        end          
        return i, t[i];
    end
end

当然了,这不是一个安全的迭代器,我们假设table中没有nil值。

至于为什么要有一个if i > #t的判断,待会会说到。

 

使用如下方式调用迭代器:


1
2
3
4
    local t = {"fdsd", "445", "9999"};
    for k, v in dieDaiQi(t) do
        print(k .. "," .. v);
    end

输出结果如下:

[LUA-print] 1,fdsd
[LUA-print] 2,445
[LUA-print] 3,9999

 

2.for .. in .. do的真面目

【for k, v in dieDaiQi(t) do  end】这段代码实际上等价于以下代码:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
    do
        local _f, _s, _var = dieDaiQi(t);
       
        while true do
            local k, v = _f(_s, _var);
            _var = k;
           
            if _var == nil then
                break;
            end
           
            print(k .. "," .. v);
        end
    end

是不是很复杂?其实它和我们之前第一次调用迭代器的代码很像,我们先删掉复杂的部分,代码变成如下:


1
2
3
4
5
6
7
8
9
10
11
12
13
    do
        local _f = dieDaiQi(t);
       
        while true do
            local k, v = _f();
           
            if k == nil then
                break;
            end
           
            print(k .. "," .. v);
        end
    end

试试运行这段代码,结果如下:

[LUA-print] 1,fdsd
[LUA-print] 2,445
[LUA-print] 3,9999

 

和直接使用for in循环是一样的结果。

 

实际上,我说的这些都是废话,因为我们之前就已经说,for in循环就是用来简化迭代器的调用的,所以当然是一样的结果。

 

3.迭代器函数、恒定状态、控制变量初值

我们来看看for in真面目的第一句代码:local _f, _s, _var = dieDaiQi(t);

三个返回值分别代表迭代器函数(_f)、恒定状态(_s)、控制变量初值(_var)。

 

迭代器函数:就不用解释了,就是我们的dieDaiQi返回的闭合函数。

恒定状态:其实就是一个变量,这个变量一直不变,所以称之为恒定。

控制变量初值:和恒定相对于的,这是一个会不断改变的变量。

 

因为我本人没有实际使用过这种特性,所以没法举出实际的例子,只能从理论上去解释。

1.比如我们的dieDaiQi函数,它只有一个返回值,就是那个闭合函数,所以,_s和_var都是nil。

2.接着调用local k, v = _f(_s, _var); 这实际上就是调用了闭合函数,并且将恒定值和变量值都作为参数传递进去。

3.Lua的函数是很自由的,即使_f函数本身没有参数,也可以传参数进去,不会影响什么,所以,两个nil值传进去了,没有任何事情发生,就像是直接调用_f()一样。

4.再下一句代码:_var = k;  这是把闭合函数(_f)的第一个返回值保存起来,因为每次调用闭合函数(_f)返回值都是下一个迭代值,所以_var每次都是不一样的值。

5.如果_var的值为nil,则停止循环,结束迭代。

 

因此,我们编写迭代器的时候,迭代结束的方式就是让第一个返回值为nil。

 

那么,如果我们让dieDaiQi函数返回恒定状态和控制变量初值,又是什么样的情况呢?

代码如下:


1
2
3
4
5
6
7
8
9
10
11
12
function dieDaiQi(t)
    local i = 0;
    return function(s, var)
        i = i + 1;  
       
        if i > #t then
            return nil;
        end        
        print("恒定值=" .. s .. ", 变量值=" .. var)
        return i, t[i];
    end, 10, 0
end

留意一下,dieDaiQi函数现在会返回三个参数,后面的10和0分别就是恒定状态和控制变量初值。

同时,闭合函数也多了两个参数:s和var。

 

于是,我们再次用for循环遍历迭代器:


1
2
3
    for k, v in dieDaiQi(t) do
        print(k .. "," .. v);
    end

输出结果如下:

[LUA-print] 恒定值=10, 变量值=0
[LUA-print] 1,fdsd
[LUA-print] 恒定值=10, 变量值=1
[LUA-print] 2,445
[LUA-print] 恒定值=10, 变量值=2
[LUA-print] 3,9999

 

恒定值自然是一直不变的,而变量值在每一次调用了闭合函数之后,就会赋值为k的值,所以变量值一直按着table的key值在变化。

可能一时有点混乱,不过,只要对照着for .. in .. do .. end对应的实现代码,就很好理解了。

 

4.结束

终于写完了,我快撑不住了,一晚上写两篇文章,可够折腾的。

现在眼睛都是花的…我不知道我还能坚持多少个晚上…

幸好学习的内容会越来越难,这样我就没法一个晚上就理解透彻,也就没法每晚写一篇教程了~

太好了,呵呵。(小若:想偷懒就偷懒吧,说这么多做什么)

 

 

 

7 评论

  1. 你好,我是lua新手 想问一下 为什么return function end,10,0 会吧10个0当然var和value穿进去呢,是有这种写法吗 在函数的end后面加上参数传进去?新手请指点一下,谢谢木头

    1. 你想问的是为什么函数可以同时返回三个参数吗?
      如果是这个问题的话,在lua中,函数确实是可以返回多个参数的。

      1. 不是不是 是这样 为什么会自动给s和var赋值为10和0 我查阅了资料 找不到关于函数的隐式调用 我也自己测试了一下 发现当我的写法是return 后面加一个function的时候 这时候用逗号加变量或者常量都会传进去给function
        比如这样: local t = {3, 4, 5}
        function func(s, i)

        end
        local function myIpairs(t)
        return func, 100, 1
        end
        然后100和1就会被传进去给func里面 看不懂源码 只好请教木头了 \(^o^)/~

        1. 我还是不太理解你的意思,【100和1就会被传进去给func里面】是什么意思呢?是说100和1被当成了func的参数?
          你举的这个例子运行结果是什么?你希望的运行结果又是什么呢?

          1. 是的 就是这个意思 100个1当成了参数传了进去 我的问题就是为什么 会被当成参数传进去 在木头你的教程中也是把10和0当成参数传给了s和var,我们并没有为他显式的调用 为什么100和1被当成了参数传进去给func
            完整代码是:
            local t = {3, 4, 5}
            print(s)
            function func(s, i)
            i = i + 1
            local v = t[i]
            if v then
            print(s,i)
            return i, v
            end
            end
            local function myIpairs(t)
            return func, 100, 1
            end
            for k, v in myIpairs(t) do
            print(k, v)
            end

            我想请教的是为什么100和1当成参数传进去给func

            1. 你这代码我试过了,除了【return func,100,1】要改为【return func,100,0】以为,并没有什么问题。
              local t = {3, 4, 5}

              function func(s, i)
              i = i + 1
              local v = t[i]
              if v then
              print("---" .. s .. "," .. i);
              return i, v
              end
              end

              local function myIpairs(t)
              return func, 100, 0
              end

              for k, v in myIpairs(t) do
              print(k .. "," .. v);
              end

              输出结果是对的,你这个调用for each的代码相当于以下源码:
              do
              local _f, _s, _var = myIpairs(t);

              while true do
              local k, v = _f(_s, _var);
              _var = k;

              if _var == nil then
              break;
              end
              end
              end

              在源码中,会使用_f、_s、_var来保存myIpairs的三个返回值,然后在while循环里,又把_s和_var(相当于你说的100和0)传递给_f函数作为参数。
              于是,在表面上看来,你的100和0传进了func里,实际上是因为源码里面把100和0取出来,然后再主动调用func,把100和0传给func了。
              这样能理解吗?~

发表评论

电子邮件地址不会被公开。 必填项已用*标注