lua易错点

3.4k 词

table.remove 删除时导致的错?

正确的删除方案:先记录要删除的表记录的k(位置),然后反向遍历记录逐个删除。

删除1~10中的4,5,6,7,8. Table.remove 删除表时后面的元素会自动向前与移动导致隔一个删除一个.

table长度

table当字数组来用时获取的是正确的长度,当map来用时# 和table.getn获取的都是不是争取的长度,需要自行使用 for k,v in pairs获取长度. # 和 table.getn遇到v为nil时会返回长度.

字符串链接

尽量使用table.concat连接字符串

  1. 使用运算符..
    每次拼接都需要申请新的空间,旧的result对应的空间会在某时刻被Lua的垃圾回收期GC,且随着result不断增长,越往后会开辟更多新的空间,并进行拷贝操作,产生更多需要被GC的空间,所以性能降低。

  2. 使用table.concat (table [, sep [, start [, end]]])函数
    table.concat 底层拼接字符串的方式也是使用运算符.. ,但是其使用算法减少了使用运算符..的次数,减少了GC,从而提高效率

    pairs 和 ipairs区别

  3. pairs: 迭代 table,可以遍历表中所有的 key 可以返回 nil
  4. ipairs: 迭代数组,不能返回 nil,如果遇到 nil 则退出

    function定义的两种方式

    local function func() .. end
    local func = function() … end
    第一种方法定义是可以做递归的第二种是不行的,第二种定义必须待函数体定义之后才能调用自己

a and b or c 当b为nil时总是返回c?

a and b :当a为真时返回b为假时返回a

a or b: a为true返回a 为false返回b

> true and true or 1

true

> true and false or 1

1

> false and true or 1

1

> false and false or 1

1

> 2 and true or 1

true

> (true and false) or 1

1

> (1 and false) or 1

1

> (a and {b} or {c})[1]

nil

> (1 and {false} or {2})[1]

false

>

解决方式:if else 另外就是(a and {b} or {c})[1]

弱引用表

1.弱引用

元表 __mode字段来设置表k,v是弱引用,被弱引用table引用,垃圾回收时可回收,只要k,v被回收,整个条目会删除。

Lua采用了基于垃圾收集的内存管理机制,当某个table对象被存放在容器中,而容器的外部不再有任何变量引用该对象,对于这样的对象,Lua的垃圾收集器是不会清理的,因为容器对象仍然引用着他。见如下代码:

1 a = {}

2 key = {}

3 a[key] = 1

4 key = {}

5 a[key] = 2

6collectgarbage()

7for k,v inpairs(a) do

8print(v)

9end

​ 在执行垃圾收集之后,table a中的两个key都无法被清理,但是对value等于1的key而言,如果后面的逻辑不会遍历table a的话,那么我们就可以认为该对象内存泄露了。在Lua中提供了一种被称为弱引用table的机制,可以提示垃圾收集器,如果某个对象,如上面代码中的第一个table key,只是被弱引用table引用,那么在执行垃圾收集时可以将其清理。

​ Lua中的弱引用表提供了3中弱引用模式,即key是弱引用、value是弱引用,以及key和value均是弱引用。不论是哪种类型的弱引用table,只要有一个key或value被回收,那么它们所在的整个条目都会从table中删除。

​ 一个table的弱引用类型是通过其元表的__mode字段来决定的。如果该值为包含字符”k”,那么table就是key弱引用,如果包含”v”,则是value若引用,如果两个字符均存在,就是key/value弱引用。见如下代码:

1 a = {}

2 b = {__mode = "k"}

3setmetatable(a,b)

4 key = {}

5 a[key] = 1

6 key = {}

7 a[key] = 2

8collectgarbage()

9for k,v inpairs(a) do

10print(v)

11end

12–仅仅输出2

​ 在上面的代码示例中,第一个key在被存放到table a之后,就被第二个key的定义所覆盖,因此它的唯一引用来自key弱引用表。事实上,这种机制在Java中也同样存在,Java在1.5之后的版本中也提供了一组弱引用容器,其语义和Lua的弱引用table相似。

​ 最后需要说明的是,Lua中的弱引用表只是作用于table类型的变量,对于其他类型的变量,如数值和字符串等,弱引用表并不起任何作用。

尾递归

什么是尾递归

尾递归的写法只是具备了使当前函数在调用下一个函数前把当前占有的栈销毁,但是会不会真的这样做,是要具体看编译器是否最终这样做。

什么是尾递归呢?(tail recursion), 顾名思议,就是一种“不一样的”递归,说到它的不一样,就得先说说一般的递归。对于一般的递归,比如下面的求阶乘,教科书上会告诉我们,如果这个函数调用的深度太深,很容易会有爆栈的危险。

// 先不考虑溢出问题

int func(int n) {
if (n <= 1) return 1;
return (n * func(n-1));
}

原因很多人的都知道,让我们先回顾一下函数调用的大概过程:

1)调用开始前,调用方(或函数本身)会往栈上压相关的数据,参数,返回地址,局部变量等。

2)执行函数。

3)清理栈上相关的数据,返回。

因此,在函数 A 执行的时候,如果在第二步中,它又调用了另一个函数 B,B 又调用 C…. 栈就会不断地增长不断地装入数据,当这个调用链很深的时候,栈很容易就满 了,这就是一般递归函数所容易面临的大问题。

而尾递归在某些语言的实现上,能避免上述所说的问题,注意是某些语言上,尾递归本身并不能消除函数调用栈过长的问题,那什么是尾递归呢?在上面写的一般递归函数 func() 中,我们可以看到,func(n) 是依赖于 func(n-1) 的,func(n) 只有在得到 func(n-1) 的结果之后,才能计算它自己的返回值,因此理论上,在 func(n-1) 返回之前,func(n),不能结束返回。因此func(n)就必须保留它在栈上的数据,直到func(n-1)先返回,而尾递归的实现则可以在编译器的帮助下,消除这个限制:

// 先不考虑溢出

int tail_func(int n, int res) {
if (n <= 1) return res;
return tail_func(n - 1, n * res);
} // 像下面这样调用 tail_func(10000000000, 1);

从上可以看到尾递归把返回结果放到了调用的参数里。这个细小的变化导致,tail_func(n, res)不必像以前一样,非要等到拿到了tail_func(n-1, nres)的返回值,才能计算它自己的返回结果 – 它完全就等于tail_func(n-1, nres)的返回值。因此理论上:tail_func(n)在调用tail_func(n-1)前,完全就可以先销毁自己放在栈上的东西。

这就是为什么尾递归如果在得到编译器的帮助下,是完全可以避免爆栈的原因:每一个函数在调用下一个函数之前,都能做到先把当前自己占用的栈给先释放了,尾递归的调用链上可以做到只有一个函数在使用栈,因此可以无限地调用!

所谓尾调用,就是一个函数返回另一个函数的返回值:

function f()
…
return g()
end

因为调用g()后,f()中不再执行任何代码,所以不需要保留f()的调用桟信息;Lua做了这样的优化,称为”尾调用消除”,g()返回后,控制点直接返回到调用f()的地方。

这种优化对尾递归非常有益,通常递归意味着调用桟的不断增长,甚至可能造成堆栈溢出;而尾递归提供了优化条件,编译器可以优化掉调用桟