L04. 自定义函数

一. Lua函数介绍

  1. lua中的函数本身是匿名的(或者说本身是一个地址), 将函数地址赋值给一个容器,如: 变量 表 函数返回值等,则这个容器就能够调用(使用)这个函数

  2. 函数主要的两种用途

    ①. 完成指定的任务,这种情况下函数作为调用语句使用

    ②. 完成指定任务并返回"值",这种情况下函数作为赋值语句的表达式使用

  3. Lua中的函数是带有词法定界的第一类值 ,第一类值指:在Lua中函数和其他值(数值、字符串)一样,函数可以被存放在变量中,也可以存放在表中,可以作为函数的参数,还可以作为函数的返回值。

 

二. Lua函数的定义

复制代码
--local 表示局部函数, 省略则是全局函数
--以下这两种方式是相同的,都是将函数的地址赋值给变量
local function funname()
    --函数体
    return 1         --函数返回值,
end

local funname = function ()
    --函数体
    return 1,2,function() return 6 end,{4}   --函数返回值,Lua语言函数可以返回多个值,每个值以逗号隔开
end
--保存在变量中的函数调用
funname()           --在Lua中带上括号才是调用,不带括号则是一个变量,只不过这个变量里面保存的是函数地址


--将函数保存在表中
local funtbl = {
                function() return 1 end,
                function(a,b) 
                    return a + b
                end,
                sub = function(a,b)
                    return a - b
                end
}
--保存在表中的函数调用
print( funtbl.sub(10,50) )

print( funtbl[1]() )

print( funtbl[2](50,10) )

--函数作为返回值返回
local function a(var)
  return function(num)
    return var + num
  end
end

--函数是返回值的调用方式1
print( a(50)(100) )
复制代码

 

三. 函数的传递

  1. lua中所有关键字命令也可以赋值给变量, 关键字可以理解为Lua内部定义的变量, 这个变量中保存着值或者地址

复制代码
local traceprint
do
   --将print关键字赋值给自定义变量traceprint
   traceprint = print 
   traceprint("这个是输出语句")
end
traceprint("这个也是输出语句")
--将print关键字(内部定义的变量)重新赋值
print = 1
--下面代码正常输出(因为: traceprint是被赋值的是print保存的"值",而不是print这个变量)
traceprint("这个是输出语句")
复制代码

  2. 函数可以作为参数进行传递

  3. 函数可以作为返回值进行传递

  4. 函数可以保存在任意容器中进行传递

 

四. 内嵌函数

  1. 内嵌函数的使用场可以在函数中起到代码好看的作用, 代码分明,功能各管各的

复制代码
math.randomseed(os.time())
--外层随机数字函数
local function rndnum(num1,num2)
    --使用内嵌判断数字的大小函数
    local function sortnum()
        if num1 > num2 then --内嵌函数可以访问外部函数的局部变量,这种特征叫做词法定界
            num1, num2 = num2, num1
        end
    end
    
    num1 = tonumber(num1) or 0
    num2 = tonumber(num2) or 0
    
    sortnum()  --调用内嵌函数
    
    return math.random(num1,num2)
end

--调用随机数字函数 print ( rndnum(20,10) )
复制代码

   2.  function block()只是block = function()的语法糖, 定义函数写上函数名就相当于给变量赋值了函数地址

复制代码
do 
    --block函数到底是局部函数还是全局函数呢?
    local block
    do
        local a = 1
        function block() --function block()只是block = function()的语法糖, 那么block函数名就是上面local block
            print("a =", a)
        end
        print(block)
    end
    --证明block函数是一个局部函数
    print(_G.block) --在_G表输出结果: nil
end 
复制代码

  3. 局部函数中定义全局函数

复制代码
do 
    --局部函数中定义全局函数
    local function func1()
        function func2()
            -- body
        end
        print("call func1 ...")
    end
    print("func1 =", _G["func1"])   --输出结果: nil
    print("func2 =", _G["func2"])   --输出结果: nil
    func1()
    print("func1 =", _G["func1"])   --输出结果: nil
    print("func2 =", _G["func2"])   --输出结果: function: 001abf60
    --结论: 只有当运行到定义的函数时,函数才会存在,这与变量是一样的, 就是说只有当func1运行之后func2才会在全局函数中存在
end
复制代码

   4. 函数中调用本函数, 此种写法是错误的

复制代码
do 
    --两个局部函数如果需要互相调用,需要在两个函数的上面先定义变量, 这样可以使函数a 正常的调用到函数b
    local a,b
    a = function ()
        print(b)
    end
    b = function ()
        a()
    end
    a()
end

do 
    --这段语句块中运行a函数时会报错, 因为b是一个nil
    local a = function()
       b() 
    end
    
    local b = function()
       a() 
    end
    a()
end
复制代码

 

五. 多返回值

复制代码
--查找表中最大的数,并返回key和value
local function maximum(t)
    local max, index
    local function isSize(a,b) return a < b end
    
    for k,v in pairs(t) do 
        if not max or isSize(max,v)  then
            index,max = k,v
        end
    end
    return index,max        --多返回值,可以是函数, 变量,表等任何有效表达式
end

print( maximum{1,2,3,40,5} )
复制代码

  1. 表达式调用函数注意事项

复制代码
local function fun1()
    return 10,20,"你好"
end

local a = fun1()
print(a)        --输出结果: 10

local a,b,c,d = 1,fun1()
print(a,b,c,d)  --输出结果: 1 10  20  你好

--将函数放在表达式中,不在末尾时,则仅会得到第一个返回值
local a,b = fun1(),50
print(a,b)  --输出结果: 10 50

local a,b = ( fun1() .. "1" )
print(a,b)  --输出结果: 101 nil

--fun1函数进行了运算,则默认仅会得到第一个返回值
local a,b = ( 1 .. fun1() )
print(a,b)  --输出结果: 110 nil

--将函数表达式括起来,就进行了运算,则仅会得到第一个返回值
local a,b = ( fun1() )
print(a,b)  --输出结果: 10 nil

--函数调用在表构造函数中初始化时,和多值赋值时相同
print(fun1())           --输出结果: 10  20  你好      
print(fun1(), 1)        --输出结果: 10  1 
print(fun1() .. "x")    --输出结果: 10x
复制代码

  2. 如何省略前面的返回值直接取指定位置的返回值

复制代码
local function fun1()
    return 10,20,"你好"
end

--取第二个返回值
local num2 = ({fun1()})[2]
print(num2)     --输出结果: 20

--取第三个返回值
local str3 = ({fun1()})[3]
print(str3)     --输出结果: 你好

--利用select函数取指定位置的返回值
print(select(2,fun1())) --输出结果 20  你好

local num = select(2,fun1())
print(num) --输出结果 20
复制代码

  3. 函数调用外围加括号和不加括号的区别

local function fun1()
    return 
end
--以下是加括号和不加括号的区别, 加括号则作为表达式运算,不加括号则是普通的调用
print(fun1())       --输出结果: 
print((fun1()))     --输出=结果: ni

 

六. 可变长参数(...)

  1. "..."不可以在不同的作用域下使用

复制代码
--以下代码会报错,因为"..."是匿名的不可以在不同的作用域下使用(包含也不行)
function tuple(...)
    return function()
        return ...
    end
end
--以下代码可以使用,因为"..."在同一个作用域
function tuple(...)
    return function(...)
        return ...
    end
    local t = {...}
end
复制代码

  2. 当可变参数中存在nil,转为table后,在转为可变参数,并且不会出问题

复制代码
--当可变参数中存在nil,转为table后,在转为可变参数,并且不会出问题

--以下代码输出结果是错误的
local function foo1(...)
    local t = {...}
    return table.unpack(t)
end
print( foo1(1,2,nil,nil,5,nil) )    --输出结果: 1    2



--正确写法1: 使用table.pack函数
local function foo2(...)
    local t = table.pack(...)
    return table.unpack(t,1,t.n)
end
print( foo2(1,2,nil,nil,5,nil) )    --输出结果: 1    2    nil    nil    5    nil

--正确写法2: 使用select函数
local function foo3(...)
    local t = {n = select("#",...),...}
    return table.unpack(t,1,t.n)
end
print( foo3(1,2,nil,nil,5,nil) )    --输出结果: 1    2    nil    nil    5    nil

--正确写法3: 把nil换成{}进行占位
print("正确写法3")
do 
   local NIL = {}
   function pack(...)
       local t = {n= select("#",...),...}
       for i = 1,t.n do 
           --这里为什么会将值为nil下标赋值一个NIL而不是直接一个{}, 因为可以更好的判断他是不是{}空表, 直接判断地址(地址是唯一的)
           t[i] = t[i] or NIL       
       end
       return t
   end
   --解包的第一种写法使用内嵌函数特征进行递归
   function unpack(t,Start,End)
      Start = Start or 1
      End = End or #t
      local t1 = {}
      table.move(t,Start,End,1,t1)
      local function recursion(t)
          if #t == 0 then
              return
          end
          local vluae = table.remove(t, 1)
          vluae = vluae ~= NIL and vluae or nil
          return vluae, recursion(t)
      end
      return recursion(t1)
   end
   
   --解包的第二种写法使用参数传递的方式进行递归
   function unpack1(t,Start,End)
       Start = Start or 1
       End = End or #t
       if Start > End then
           return
       end
       t[Start] = t[Start] ~= NIL and t[Start] or nil
       return t[Start], unpack1(t,Start + 1,End)
   end
   --解包函数运用了table.remove函数
   --学到知识: table.remove函数运用的很巧妙, 在遇到这种需要单独获取table中的值的时候可以使用这种方法
   function unpack2(t,Start,End)
       Start = Start or 1
       End = End or #t
       if Start > End then
           return
       end
       local value = table.remove(t, Start)
       value = value ~= NIL and value or nil
       return value, unpack2(t,Start,End-1)
   end
end
print( unpack(pack(1,2,nil,nil,5,nil)) )        --输出结果: 1    2    nil    nil    5    nil

print( unpack1(pack(1,2,nil,nil,5,nil)) )       --输出结果: 1    2    nil    nil    5    nil

print( unpack2(pack(1,2,nil,nil,5,nil)) )       --输出结果: 1    2    nil    nil    5    nil

--正确写法4: 采用内嵌函数特征的方式
local function unpack(t)
  local i = 0
  local function pack()
    i = i + 1
    if t[i] then
      return t[i], pack()
    end
  end
  return pack()
end
print(unpack({1,2,3,4,5,6}))
复制代码

   3. 函数的多返回值进行完整的传递

复制代码
--将函数的多返回值进行完整的传递
local function a()
   return 0,1,2,3,4
end

local function b()
   return 5,6,7,8,9
end

--将函数作为参数进行传递, 可以得到函数完整的多返回值
local function fun(f1,f2)
    print(f1())
    print(f2())
end
fun(a,b)
复制代码

 

七. 表中的域 (表中的函数被是非全局函数)

   1. Lua中函数作为table的域, 如:(io.read、math.sin)这种形式可以叫做表中的域, 函数必须使用表名来进行调用

复制代码
--3种方式在表中中声明函数(将函数保存在表的作用域)

--1. 将函数赋值给表中的key
Lib = {}
Lib.foo = function (x,y) return x + y end
Lib.goo = function (x,y) return x - y end
--2. 使用表构造函数
Lib = {
    foo = function (x,y) return x + y end,
    goo = function (x,y) return x - y end
}
--3. Lua提供另一种语法方式
Lib = {}
function Lib.foo (x,y)
    return x + y
end
function Lib.goo (x,y)
    return x - y
end
复制代码

 

八. 函数的调用

  1. 调用函数的时候,如果参数列表为空,必须使用()表明是函数调用,否则会认为他是一个变量。

local function func1()
   return ("调用func1函数") 
end
print(func1())  --输出结果: 调用func1函数
print(func1)    --输出结果: function: 0076dd50

  2. 上述规则, 有一种情况;例外, 当函数只有一个参数并且这个参数是字符串或者表构造的时候,()可有可无

print "Hello World"      <-->      print("Hello World")
dofile 'a.lua'          <-->      dofile ('a.lua')
print [[a multi-line    <-->      print([[a multi-line
          message]]                          message]])
f{x=10, y=20}            <-->      f({x=10, y=20})
type{}                  <-->      type({})

  3. o:foo(x)与o.foo(o, x)是等价的,后面的章节会介绍面向对象内容

复制代码
--o:foo(x)与o.foo(o, x)是等价的,后面的章节会详细介绍面向对象内容

local o = {}
function o.foo(x,y)
   print(x,y) 
end

o.foo(o, 123)       --table: 0097d640    123
--采用面向对象的方式调用
o:foo(123)          --填写一个参数默认将o当第一个参数传入进去,123则对应的是第二个参数
o:foo(1,123)        --输出结果: table: 0097d640    1, 参数123是多余的则省略
复制代码

  4. 函数实参和形参的匹配与赋值语句类似,多余部分被忽略,缺少部分用nil补足

function f(a, b) print(a,b) end

--形参b将会为nil
f(3)            --输出结果: 3   nil     
f(3, 4)         --输出结果: 3   4 
--实参5将被省略
f(3, 4, 5)      --输出结果: 3   4   

 

九. 函数的可变长参数的使用技巧及应用

  1. 连接两个列表

复制代码
--连接两个列表
--思路: 将第一个列表用函数返回, 然后将这个函数传递给连接函数

--这里加一个a参数是为了每次递归都是取第一个列表中的内容
local function connect(f,a,...)       
    if a == nil then
        return f()
    end
    return a, connect(f,...)
end
print("连接两个列表结果为: ", connect(function() return 1,2,3 end,4,5,6) )
复制代码

  2. 连接多个列表

复制代码

--连接多个列表
--思路: 将每个列表使用函数返回, 然后将函数作为参数传递给connect函数,

--函数返回列表
local a,b,c
function a()
   return 1,2,3
end
function b()
   return 4,5,6
end
function c()
   return 7,8,9
end

--这里的"..."是函数列表
local function connect(f, ...)
    
    if select("#",...) == 0  then
        return f()
    end
    
    --这里的"..."是返回值列表
    local function recursion(fun, a, ...)
        if select("#", ...) == 0 then
            return a, fun()
        end
        return a, recursion(fun, ...)
    end
    
    --下面这段代码才是关键, 思考递归有时候需要逆推理思考
    return recursion(f, connect(...))
    
end

print("连接两个列表结果为: ", connect(c,b,a))
复制代码

   3. 反转列表

复制代码
--反转列表
local function reversal(a, ...)
    if a == nil then
        return
    end
    
    local function recursion(a,b, ...)
       if b == nil then
           return a
       end
       return b, recursion(a, ...)
    end
    
    return recursion( a, reversal(...) )
end

print ("反转列表: ", reversal(1,2,3,4) )
复制代码

  4. 过滤列表

复制代码
--可变长列表依次当作参数传入函数, 并返回函数执行后为true的函数返回值(过滤)

local function FilterResults(f, ...)
    
    local function Recursion(n, a, ...)
        if n > 0 then
            if f(a) then
                return a, Recursion(n - 1, ...)
            else
                return Recursion(n - 1, ...)
            end
        end
    end
    
    local function Len(f, ...)
        return Recursion(select("#",...), ...)
    end
    return Len(f, ...)
end

print("过滤列表: ", FilterResults(function(a)
                return a % 2  == 0
            end, 1,2,3,4,5,6) )         --输出结果: 过滤列表:     2    4    6
复制代码

  5. 可变长列表依次当作参数传入函数, 并返回函数执行后的所有结果(类似于for循环中的迭代器)

复制代码
--可变长列表依次当作参数传入函数, 并返回函数执行后的所有结果

local function AllResults(f, ...)
    
    local function recursion(n, a, ...)
       if n > 0 then
          return f(a), recursion(n-1, ...)
       end
    end
    
    local function Len(...)
        return recursion(select("#", ...), ...)
    end
    
    return Len(...)
    
end


print ( AllResults(function(a)
                       return a + 100
                   end, 1,2,3,4,5) )
复制代码

  6. 遍历可变长列表

复制代码
--遍历可变长参数列表

--第一种写法: 
print("遍历可变长参数列表, 第一种写法 ")
local function ErgodicList1(...)
    for _, i in pairs({...}) do 
        print(i)
    end
end
ErgodicList1(1,2,3)

--第二种写法:
print("遍历可变长参数列表, 第二种写法 ")
local function ErgodicList2(...)
    for i = 1, select("#", ...) do
       print((select(i, ...)) )
    end
end
ErgodicList2(4,5,6)
复制代码

 

十. 函数的使用技巧及应用

   1. 计算数组的边界

复制代码
--计算数组边界
local t = {}
t[1] = {0,0,0,1,0}
t[2] = {0,0,0,0,0}
t[3] = {0,0,0,0,1}
local function Getwh(t)
    local Right, down, Left, up
    for i = 1,3 do 
        for j = 1,5 do 
            if t[i][j] == 1 then
                up = up or i
                if not down or i > down then down = i end
                if not Left or Left > j then Left = j end
                if not Right or j > Right then Right = j end
            end
        end
    end
    print(string.format("上:%s 左:%s 下:%s 右:%s",up , Left , down , Right))
end
Getwh(t)
复制代码

  2. 查找表中最大的数

复制代码
--查找表中最大的数,并返回key和value
local function maximum(t)
    local max, index
    local function isSize(a,b) return a < b end
    
    for k,v in pairs(t) do 
        if not max or isSize(max,v)  then
            index,max = k,v
        end
    end
    
    return index,max        --多返回值,可以是函数, 变量,表等任何有效表达式
end
print( maximum{1,2,3,40,5} )
复制代码

  3. 将函数作为参数传递, 可以更加灵活或者更加有拓展性

复制代码
--将函数b作为参数传递给函数a, 可与使函数a更加灵活
--将函数的返回值作为参数传递, 可以更好的解决多返回值中存在nil的情况
--例子: 将参数进行运算, 运算规则有多种
local function calculation(f)
    local function traceprint(...)
        --这里可以写运算之后的代码
        print(...)
    end
    
    return function(...)
        traceprint(f(...))
    end
end

local fun1 = calculation(function(x,y,z) return x + y, z end)
fun1(1,5,3)
fun1(8,5,nil)
复制代码
posted @   小书臣  阅读(94)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示