摘要:lua程序设计第二版学习笔记
脚本语言的基础语法大都比较简单,这里只列举一些lua独有,或者需要特别注意的语法点。
书中前三章的内容是一些惯常的引言,基础数据类型,运算符等内容,相对简单,这里就不再赘述。
语句
1、do...end 可以用来包含一个程序块。
2、在循环语句中声明的局部变量,在条件判断时依然存在
3、for循环分为数字型和泛型
数字型:
1 for var=exp1, exp2, exp3 do 2 <执行体> 3 end
类似于C中将括号和句号去掉,var初始值为exp1,增长到exp2,步进为exp3。
exp3可选,默认为+1,不设上限可将exp2设置为math.huge。
for的控制变量会被自动声明为for的局部变量,无需单独声明,同时也无法在外部访问。
泛型for:通过迭代器访问table
1 for i,v in ipairs(a) do print(v) end -- 打印所有值 2 for k in pairs(t) do print(k) end -- 打印所有key
4、lua中提供多种迭代器,也可自行编写
迭代文件中的行 io.lines
迭代table元素 pairs
迭代数组元素 ipairs
迭代字符串中单词 string.gmatch
5、return ,break只能作为代码块的最后一条语句,或是end,else,until前的最后一条语句,否则lua语法报错,可以通过 do return end 显式包住一条return。
函数
6、lua中函数,若只有一个参数,且该参数为字面字符串或table构造式,函数的()可以省略。
7、lua为面向对象提供特殊语法——冒号操作符,将本身作为第一个值传入。
8、lua中函数可以返回多个值,同时用多个变量接受,类似于多重赋值,但是若函数调用不是一系列表达式的最后一个值,则只产生一个值。
9、上一条中的现象,在多返回值函数作为另一个函数的非最后一个参数时也有效。
10、通过将函数调用放入一对圆括号中,可以迫使其之返回一个值。
11、unpack() 函数接受一个数组为参数,并且从下标1逐个返回所有参数,常用于泛型调用。
12、声明函数时,参数为(...),即为变长参数,在函数中 ‘...’ 被当作表达式使用。
13、变长参数和固定参数同时出现在函数参数中时,变长参数需放到最后。
14、当变长参数中含有故意传入的nil时,需要用select函数访问,select函数首先必须传入一个固定实参,如果这个实参为数字n,那么函数返回第n个变长参数(包括nil),否则变长参数必须为‘#’,返回变长数的总和。
15、具名函数,类似于python中指定函数参数赋值,但是需要把参数名和参数值写到一个table中,传入函数。
16、lua中的函数为第一类值,实际上将lua中的函数名理解为一个持有函数的变量更为合适。
1 function foo (x) return 2*x end 等价于 2 foo = function (x) return 2*x end
这使得lua可以轻松实现回调模式,例如C++中STL提供的排序函数,需要传入一个返回值为bool类型的函数指针,用以比较容器变量的大小,lua中可以这样实现,
例如一个给table排序的函数:
1 network = {............} 2 table.sort(network, function (a, b) return (a.id > b.id) end)
对于这个特性的一个高阶应用,求导数:
1 function derivative(f, delta) 2 delta = delta or 1e-4 3 return function(x) 4 return (f(x + delta) - f(x))/delta 5 end 6 end 7 8 c = derivate(math.sin) 9 print(math.cos(10), c(10)) 10 --> -0.83907152907645 -0.83904432662041
深入函数
17、closure(闭合函数),从翻译上不太好理解这个概念。首先,在一个函数内部定义另一个函数时,那么内层的函数可以访问外层函数的变量,这项特征被称为“词法域”。而这个变量相对于内部的函数称
为“非局部的变量”,一个closure就是一个函数加上该函数所需访问的所有非局部的变量。
1 function newCount() 2 local i = 0 3 return function() i = i + 1 return i end 4 end 5 6 c1 = newCount() 7 print(c1()) --> 1 8 print(c1()) --> 2 9 c2 = newCount() 10 print(c2()) --> 1 11 print(c1()) --> 3 12 print(c2()) --> 2
c1 和 c2是同一个函数所创建的两个不同的closure,他们各自拥有局部变量i的实例。
18、closure的另一个用处是创建一个安全的沙盒环境去运行一些不受信任的代码。
1 do 2 local oldOpen = oi.open 3 local access_OK = function(filename, mode) 4 <检查访问权限> 5 end 6 io.open = function(filename, mode) 7 if access_OK(filename, mode) then 8 return oldOpen(filename, mode) 9 else 10 return nil, "access denied" 11 end 12 end 13 end
19、非全局函数,如某个table的成员或用local修饰了声明的函数,只有在当前块可以访问该函数。有一点需要注意,当定义递归的局部函数时,在递归时由于局部的函数尚未定义完毕,所以其实调用的是
一个同名的全局函数,可以通过先定义一个局部的变量作为函数名来解决这个问题。
20、对于非全局函数,lua中有一种语法糖:
1 local function foo(<参数>) <函数体> end
lua将其展开为:
1 local foo 2 foo = function (<参数>) <函数体> end
这样定义不会产生递归错误。当然对于间接递归这是无效的,间接递归必须一个前向声明,示例代码就不贴了,间接递归实在是种糟糕的语法,如非必要不要使用。
21、尾调用,lua中正确的尾调用形式如下:
1 return <function>(<args>)
尾调用相当于一条goto语句,因为调用函数已经没有需要执行的代码,所以返回值可以直接被被调函数,也就是尾调用的函数返回值覆盖,此时递归的话不会产生栈溢出问题,同时也可以利用lua的这一特
性实现状态机,用尾调用跳转到下一个状态,不会出现任何内存问题。