NODEMCU调试心得5 - 函数本无名

时间:2022-01-18 13:42:52

函数本无名

这部分应该算关于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)
    end

    sortbysquare() 是 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)
    end

    f(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函数还具有其他的特点,例如:

    • #### 函数返回值results 数量可变,详情点击这里
    • #### 自变量argument的数目可变,详情点击这里
    • #### 如果自变量的list是record,可用自变量的名称赋值,详情点击这里

    比较简单,这里就不详细介绍了。


函数中的迭代和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的函数和数据结构,阅读固件中的示例代码应该不成问题。如果还有问题,以后再补充。

想了解关键词的详细内容,请点击蓝色链接,或阅读线上的官方教程

文中的大部分代码来自官方教程,根据文章需要作了修改。

下一部分,按照计划,讲一下网络协议。