函数本无名
这部分应该算关于nodemcu
的第三节,但是我想取个拽一点的名字。
前面简单介绍了lua的基本语法和脚本文件操作,这里我们谈一下lua最重要的内容:函数
lua的函数是一个特殊的存在,而我认为,这代表着某种真实。
函数本无名
lua中的函数其实都是匿名函数,没有名称。匿名函数是一个值”value”,就像数值和字符串一样。
-
所谓的函数名称,只是一个幻觉。函数名其实是一个变量。只是这个变量被赋值为匿名函数。函数的定义过程也就是变量的赋值过程,比如:
foo = function (x) return 2*x end
foo是被赋值的变量,也就是所谓的函数名,
=
右侧就是匿名函数,是一个值,被赋值给foo, 上述赋值过程等价于函数的定义,即:function foo (x) return 2*x end
-
既然函数名只是一个变量,因此它可以随意赋值,包括lua内已经定义的函数。下面举例:
a = print
a("high a big news!")
print = function(a) return a+1 end
a(print(1))a = print;
将变量print对应的匿名函数赋给了a,所以a()可以实现print()的功能。print = ...
将变量print重新赋值为一个匿名函数,不再实现屏幕打印功能。 -
匿名函数的一般形式是
function(arg)
statment
return result --可以无返回值
end
高阶函数
既然函数名是一个变量,因此也可以作为另一个函数的自变量(argument)。
更常见的情况是,将匿名函数作为argument,另一个函数就是匿名函数的函数,所谓高阶函数。
- 以关于lua
里的代码作为例子
c:on("receive",function(sck,pl) uart.write(0,pl) end)
`c`是`nec.sockt`类型,代表客户端的连接状态,c:on的自变量argument包括:“recieve”字符串和后面的匿名函数。c:on是匿名函数的高阶函数
- 再举一个高阶函数的例子
list = {1,3,-5,-2,4,7}
table.sort( list,function(a,b) return (a>b) end )
for i,v in ipairs(list) do print(v) end
table.sort( list,function(a,b) return (a^2 > b^2) end )
for i,v in ipairs(list) do print(v) end
table.sort是表格的排序函数,第一个自变量是需要排序的表格list,第二个自变量是一个匿名函数,表示排列的规则,具体含义是相邻元素之间的关系。
第二行是按周list中的元素大小排列,排列规则为,相邻元素左侧大于右侧
第四行是按元素的平方大小排序。
封闭函数
如果一个函数a()的体内包含另一个函数b()( 函数b()的statements,或者return包含另一个函数a() ),a称作b的封闭函数(也有翻译成外围函数的,我不喜欢外围这个词)enclosing function
- 封闭函数与高阶函数的区别是,一个是函数位于另一个函数体内,另一个是函数是另一个函数的自变量。用代码表示就是:
function b(arg) ... a() ... end --enclosing function
function b(a(),arg) ... end -- higher order function
-
举例,把表格按平方大小排序写成一个独立的函数
function sortbysquare(list)
table.sort(list,function(a,b)
return(a^2 >b^2)
end)
endsortbysquare() 是 table.sort() 的enclosing function
table.sort()可以访问封闭函数sortbysquare()的自变量 list,这个特点称作lexical scoping,词法作用域。list 被称作upvalue
-
关于封闭函数的闭包特性(closure),再举一个例子。所谓closure,就是封闭函数a()体内的函数b(),可以访问所有a()的自变量,也就是upvalue。我们看一个计数函数:
function newCounter ()
local i = 0
return function () i = i + 1 return i end
end
c1 = newCounter()
c2 = newCounter()
print(c1) print(c1) print(c2) print(c1) print(c2)newCounter()是一个计数函数,每次调用返回值就增加1
c1和c2是两个独立的计数函数,两个独立的闭包。二者之间的计数变量
i
互不干扰。
非全局函数
本地函数
变量有全局变量(global variable)和本地变量(local variable)之分,作为变量的函数名同样有gloable function和local function。
我们之前定义的function都属于global function,函数名是global variable。如果函数名是一个local variable,就称作local function
- 本地函数的定义过程如下:
local sortbysquare
sortbysquare = function(list)
table.sort(list,function(a,b)
return(a^2 >b^2)
end)
end
注意,定义本地函数,一定要先定义本地变量,再给本地变量赋值匿名函数。
不能这样做:
local sortbysquare = function(list)
...
否则sortbysquare()仍是一个全局函数
表格函数
- 局部变量可以定义为函数,表格中的元素同样可以定义为函数,称为表格函数
-
表格函数的定义方式如下:
mySort = {}
mySort.sortbyvalue = function(list) table.sort( list,function(a,b)
return (a>b) end) end
mySort.sortbysquare = function(list) table.sort( list,function(a,b)
return (a^2 > b^2) end) end -
此外,表格函数的定义方式还有以下两种:
mySort = {}
function mySort.sortbyvalue(list)
...
function mySort.sortbysquare(list)
...
mySort = {
sortbyvalue = function(list) ...
sortbysquare = function(list) ...
}
尾部调用
-
尾部调用可以实现类似go to语句的功能,这里简单介绍一下,它的形式为:
function f (x)
return g(x)
endf(x)尾部调用g(x)时,不会在堆栈中保存任何关于f(x)的信息,反复调用f(x),不会引起堆栈溢出,效果类似与goto语句
-
以下形式都不属于尾部调用:
function f (x) g(x) return end
function f (x) return g(x)+1 end
function f (x) return x or g(x) end
function f (x) return (g(x)) end –(())表示如果g(x)有多个返回值,只返回一个函数的其他特点
lua函数还具有其他的特点,例如:
比较简单,这里就不详细介绍了。
函数中的迭代和for循环
这一部分,我们回顾一下函数中常用的for循环
- 数字for循环
for i=1,10,2 do print(i) end
1为起始值,10为结束值,2为步长
-
一般for循环,用在table结构中
list = {1,2,3,4}
for i,v in ipairs(list) do print(v) end
for i in ipairs(list) do print(i) end
record = {x=1,y=2,z=3}
for k,v in pairs(record) do print(k,v) end
for k in pairs(record) do print(k) end针对list和record,有ipair和pair两种形式,如果in ipairs(pairs)前只有一个变量,这个变量为index(key)
除了lua语言自身提供的迭代函数pairs和ipaires,也可以自己编写迭代函数。可以采用闭包形式或者无状态形式编写,
- 采用闭包构建迭代函数,
下面给出定义和使用方式
function list_iter (list)
local i = 0
local n = table.getn(list)
return function ()
i = i + 1
if i <= n then return list[i] end
end
end
list = {1,2,3,4}
for v in list_iter(list) do print(v) end
和pair,ipairs不同,list_iter 只给出list中的元素迭代
table.gen()可以得到table的元素个数
for循环中,list_iter()每调用一次,i就会增加1,返回下一个元素,即list[i]。list[i]的值赋给v
-
当所有元素都被迭代,没有下一个元素了,将返回nil
for循环等价于如下的while循环:
iter = list_iter(list)
while true do
local v = iter()
if v == nil then break end
print(v)
end- #### 无状态迭代,
无状态迭代不使用闭包,也就是说迭代函数体内不调用函数,迭代函数不是封闭函数。多次调用不会引起堆栈溢出。
- #### 无状态迭代,
-
ipairs()和pairs()就是采用无状态的迭代函数,ipairs的内部结构如下:
function iter (list, i)
i = i + 1
local v = list[i]
if v then
return i, v
end
end
function ipairs (list) return iter, list, 0 end这里,ipairs()的返回值有三个。iter是迭代函数名,list是iter的第一个变量,0是第二个变量,初始index
-
对于pairs()函数,其内部结构为:
function pairs (list) return next, list, nil end
next适用于list和table,可以返回下个index或key,也可以单独在for循环中使用,例如:
for k,v in next,list do .... end
-
数据结构
最后,再简单提一下lua中的各种数据结构:
- 数组,元素为数字变量的list,数组可以是一维,二维,或者高维。
对于特殊的一维数组和二维数组(矩阵),可以采用for循环定义,例如
a = {} -- 定义新数组
for i=1, 10 do a[i] = fun(...) ... end end
mt = {} -- 创建矩阵
for i=1,N do
mt[i] = {} -- 创建新行
for j=1,M do
mt[i][j] = fun(...) ... end
end
end
-
链表,list可以作为链表,元素有两个,链接地址和插入的元素。代码如下:
list = nil
list = {next = list, value = 1}
list = {next = list, value = 2}
list = {next = list, value = 3}
local l = list1
while l do
print(l.value)
l = l.next
end
for k,v in pairs(list) do print(k,v) end -
队列可以用list实现,list同样可以实现栈结构。
队列的特点是先进先出 (fisrt in first out,FIFO),它的操作函数定义和使用如下
List = {}
-- 初始化序列,定义first第一个元素和last最后一个元素对应的index
function List.new () return {first = 0, last = -1} end
function List.pushleft (list, value)
local first = list.first - 1
list.first = first
list[first] = value
end
function List.popright (list)
local last = list.last
if list.first > last then error("list is empty") end
local value = list[last]
list[last] = nil
list.last = last - 1
return value
end
list = List.new()
List.pushleft (list, 2)
List.pushleft (list, 3)
List.popright (list)
for i,v in pairs(list) do print(i,v) end序列的元素包括两部分,一部分是first和last对应的index,伴随插入和删除元素操作,值会变化。另一个部分是序列内的数组,index和对应得值。
建立栈结构,实现后进先出功能,(last in first off,LIFO)需使用List.popleft()函数,插入元素也可以从右侧插入,使用List.pushright()函数,可以仿照list.popright()和list.pushleft()编写,具体代码见这里
这部分就到这里了。掌握了lua的函数和数据结构,阅读固件中的示例代码应该不成问题。如果还有问题,以后再补充。
想了解关键词的详细内容,请点击蓝色链接,或阅读线上的官方教程。
文中的大部分代码来自官方教程,根据文章需要作了修改。
下一部分,按照计划,讲一下网络协议。