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)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了