Lua 数据类型 —— 函数

一、函数结构

1、声明

lua 复制代码
optional_function_scope function function_name( argument1, argument2, argument3..., argumentn)
    function_body
    return result_params_comma_separated
end
  • optional_function_scope: 该参数是可选的制定函数是全局函数还是局部函数,未设置该参数默认为全局函数,如果需要设置函数为局部函数需要使用关键字 local。
  • function_name: 指定函数名称。
  • argument1, argument2, argument3..., argumentn: 函数参数,多个参数以逗号隔开,函数也可以不带参数。参数的行为与局部变量的行为完全一致(可以通过下面的例子体会)。
  • function_body: 函数体,函数中需要执行的代码语句块。
  • result_params_comma_separated: 函数返回值,Lua 语言函数可以返回多个值,每个值以逗号隔开。

入参的行为

lua 复制代码
print("参数局部变量:")
function params(a, b)
    print("函数内部,未改变值:", a, b)     --> 函数内部,未改变值:	1	2
    a = 10
    b = 100
    print("函数内部,未改变值:", a, b)     --> 函数内部,未改变值:	10	100
end
local a = 1
local b = 2
print("入参前:", a, b)     --> 入参前:	1	2
params(a, b)
print("入参后:", a, b)     --> 入参后:	1	2

2、使用

函数的调用和 java 、 kotlin 没有区别

只是如果函数只有一个参数,且该参数类型是 字符串常量 或是 表构造器 ,则括号是可选,只需隔开一个空格。

lua 复制代码
-- 等同于 print("function")
print "function"                 --> function
-- 等同于 dofile("字符串.lua")
dofile "字符串.lua"
-- 等同于 type({})
print(type {})                   --> table

二、入参

1、入参个数

参数个数没有规定一定要和函数声明的个数一致,多余会被抛弃,不足参数会被设置为 nil .

lua 复制代码
function multiParams(p1, p2, p3, p4, p5)
    print(p1, p2, p3, p4, p5)
end
multiParams()                           --> nil	nil	nil	nil	nil
multiParams(1)                          --> 1	nil	nil	nil	nil
multiParams(1, 2)                       --> 1	2	nil	nil	nil
multiParams(1, "jiang", 2)              --> 1	jiang	2	nil	nil
multiParams(1, "jiang", 2, "peng")      --> 1	jiang	2	peng	nil
multiParams(1, "jiang", 2, "peng", 3)   --> 1	jiang	2	peng	3
multiParams(1, "jiang", 2, "peng", 3, "yong")   --> 1	jiang	2	peng	3

可以利用这一特性,加上 a or 默认值 达到默认值

lua 复制代码
local globalCounter = 0
function initGlobal(n)
    n = n or 1
    globalCounter = globalCounter + n;
end
initGlobal()
print(globalCounter)            --> 1
initGlobal(10086)
print(globalCounter)            --> 10087

2、使用 ... 接收可变参数

2-1、使用列表进行装载可变长参数

使用 ... 进行接收可变长参数函数,用 {} 装载则将其转为列表,值得注意的是,这里每次调用都会创建一个临时的表

和 kotlin 一样, _ 可以避免无用参数取名

lua 复制代码
function add(...)
    local total = 0
    for _, v in ipairs { ... } do
        total = total + v;
    end
    return total
end

print(add(3, 4, 10, 25, 12))            --> 54
-- 如果存在 nil , 则后面的就不再继续,因为使用的是 ipairs , 使用 paris 就不会
print(add(3, 4, 10, nil, 25, 12))       --> 17

可以将 ... 当作多值返回(见下一小节)来使用,直接将其赋值给变量

lua 复制代码
-- ... 和多值返回类似,可以用多值返回的所有操作
-- 多的会被舍弃,少的用 nil 补充
function foo(...)
    local a, b, c = ...
    print(a, b, c, "size: " .. #{ ... })
end

foo("a")                            --> a	nil	nil size: 1
foo("a", "b")                       --> a	b	nil size: 2
foo("a", "b", "c")                  --> a	b	c	size: 3
foo("a", "b", "c", "d")             --> a	b	c	size: 4
foo("a", "b", nil, "c", "d", nil)   --> a	b	nil	size: 5     -- 这里的最后一个元素 `nil` 则会被忽视,如果需要被计算则需要用 `table.pack()`

2-2、使用 select 解析可变长参数

nil 也会被认为是元素

当第一个参数 selector 是数值时,表示从哪个下标开始截取。(开始下标是 1 )

当第一个参数是 # 时,则表示获取长度( nil 也会被包括在内)。

lua 复制代码
print(select(1, "a", "b", "c"))             --> a	b	c
print(select(1, "a", "b", nil, "c"))        --> a	b	nil	c
print(select(2, "a", "b", "c"))             --> b	c
print(select(3, "a", "b", "c"))             --> c
print(select(4, "a", "b", "c"))             -->       "空"
print(select("#", "a", "b", "c"))           --> 3
-- nil 也会被计算
print(select("#", "a", "b", nil, "c", nil)) --> 5

可以使用 select 进行遍历处理

lua 复制代码
function add1(...)
    local s = 0
    for i = 1, select("#", ...) do
        -- 此处的 select 只会使用 i 下标的值
        s = s + select(i, ...)
    end
    return s
end
print(add1(1, 2, 3, 4, 5))

2-3、{ ... } 和 select 的区别

两种都可以进行遍历元素。

在元素比较少的情况下, select 会快一些,可以避免每次都创建临时新表

元素较多的情况下,则 { ... } 会快一些

3、... 行为和多值返回行为一样

可以进行多值赋值,多的参数会被抛弃,少的用 nil 补充

对于所有的函数入参都可以使用可变长参数接收,基于这一特性,我们可以使用以下函数进行跟踪一些入参,调试的时候很有用

lua 复制代码
function showParams(fun, ...)
    print(...)
    return fun(...)
end

-- 使用
print(showParams(add, 1, 2, 3, 4, 5))
--> 1	2	3	4	5
--> 15

4、和固定参数混用

可变参数可以和固定参数一起使用,则像 kotlin 、 java 一样,需要将固定参数放在函数参数的最前面。

三、多值返回

函数可以在 return 中返回多个值

  • 函数只是作为一条单独语句使用,则所有返回值会被舍弃。(即不需要其返回值)
  • 如果函数被当作表达式(加法等操作,字符连接),则只会使用第一个返回值。(因为此处只需要一个值,所以只取第一个返回值)
  • 函数被当作多重赋值、函数调用时传入的实参列表、表构造器、return语句时,多值返回才会被获取。

遵循几个规则:

  1. 赋值时,参数多了会被舍弃,少了会用 nil 进行赋值
  2. 多值返回函数只有作为最后一个表达式时,才会使用每个返回值,否则只使用第一个。无论是在多重赋值,还是在函数入参,表构造器都是一样的。

可以使用增加括号,让多值返回变为只有一个值返回。

举亿个例子

lua 复制代码
function f0()
    return
end
function f1()
    return "jiang"
end
function f2()
    return "peng yong", 28
end

获取多值返回

lua 复制代码
-- 多余的返回值会被舍弃
value = f2()
print(value)                        --> a

-- 按顺序进行赋值,多余的返回值会被舍弃,少的则会用 nil
value1, value2, value3 = 10, f2()
print(value1, value2, value3)       --> 10	a	1
value4, value5, value6, value7 = 10, f2()
print(value4, value5, value6, value7)   --> 10	a	1	nil

-- 如果多值返回不是最后一个表达式,则只会使用一个,这个原理同样适用于函数调用入参
value8, value9, value10 = f2(), 10
print(value8, value9, value10)      --> a	10	nil

---- f0 返回 nil 赋值给 value11 , 而 10001 多了一个参数则被舍弃 
value11, value12 = f0(), 10000, 10001
print(value11, value12)             --> nil	10000

将多值返回当作入参

lua 复制代码
function params(param1, param2, param3)
    print("params:" .. param1 .. "," .. param2 .. ",", param3)
end
lua 复制代码
---- 唯一一个入参,则会将所有的返回值作为入参
params(f2())                    --> params:peng yong,28,	nil

-- f2() 虽然返回两个参数,但是因为不是 最后一个参数(也不是唯一一个参数) 所以只会使用第一个返回值,则这里只是入参了两个参数
params(f2(), 10000)             --> params:peng yong,10000,	nil

---- f2() 最后一个参数,所以会进行多返回值展开,将所有的 f2() 返回值当作入参,所以这里就有三个入参值
params(10001, f2())             --> params:10001,peng yong,	28

-- 多值返回和其他的运算操作只会使用第一个返回值
print(f2() .. 1)                --> peng yong1

-- 可以作为表的构造器,但也遵循同样的规则,只有作为最后一个表达式,才会使用所有返回值
t1 = { f2() }
t2 = { f2(), "jiang pengyong" }
showTable(t1)                   --> [1]=peng yong, [2]=28, 
showTable(t2)                   --> [1]=peng yong, [2]=jiang pengyong, 

使用括号屏蔽多值返回,则只会使用第一个返回值

lua 复制代码
-- 使用括号,强制只返回一个值,f2() 则只有第一个返回值被使用,其他的被省略
print((f2()))                   --> peng yong

五、table.pack 和 table.unpack

1、table.pack

如果 {...} 中包含有 nil ,则不再是一个有效的序列。 table.pack (5.2 引入) 会将所有的参数(包括 nil),保存到一个 table 中并返回,而且会有一个额外的字段 "n" 用于保存参数个数 。

lua 复制代码
function noNils(...)
    local arg = table.pack(...)
    local argValue = ""
    for i = 1, arg.n do
        argValue = argValue .. (arg[i] or "nil") .. " ,";
    end
    print("arg [" .. argValue .. "] size:" .. arg.n)
    for i = 1, arg.n do
        if arg[i] == nil then
            return false
        end
    end
    return true
end

-- 使用
print(noNils(2, 3, nil))        -->  arg [2 ,3 ,nil ,] size:3           false
print(noNils(2, 3))             --> arg [2 ,3 ,] size:2                 true
print(noNils(2, nil, 3))        --> arg [2 ,nil ,3 ,] size:3            false
print(noNils())                 --> arg [] size:0                       true
print(noNils(nil, nil))         --> arg [nil ,nil ,] size:2             false

2、table.unpack

作用是将 table 转换为一组返回值,可以作为函数的入参

unpack 函数的重要用途之一体现在泛型调用,泛型调用允许我们动态地调用具有任意参数的任意函数。

两种使用方式

table.unpack(table) 将 table 进行展开,作为多值返回

lua 复制代码
table1 = { "jiang", "peng", "yong", "xiao" }
print(table.unpack(table1))         --> jiang	peng	yong	xiao

table.unpack(table, startIndex, endIndex) 截取 table 的 startIndex 至 endIndex 区间的 item ,进行返回 ( [startIndex, endIndex] )

lua 复制代码
table1 = { "jiang", "peng", "yong", "xiao" }
print(table.unpack(table1, 2, 3))   --> peng	yong

值得注意的是 table.unpack 使用长度操作符获取返回值的个数,因而该函数只能用于序列。

动态调用

这样其实就可以动态的进行调用代码,将上面的稍作修改,可以通过将 print 赋予给变量在进行调用

lua 复制代码
f = print
f(table.unpack(table1))  --> jiang	peng	yong	xiao

用 Lua 实现一个简易的 table.unpack 函数

真正的 table.unpack 是由 c 进行编写,也可以用递归进行编写一个类似的

lua 复制代码
function unpack(t, i, n)
    i = i or 1
    n = n or #t
    if i <= n then
        return t[i], unpack(t, i + 1, n)
    end
end

print(unpack(table1))           --> jiang	peng	yong	xiao
print(unpack(table1, 2, 3))     --> peng	yong

六、尾调用

1、作用

尾调用的作用不会使用额外的栈空间(这种实现称为尾调用消除)

因为当被调的函数执行结束后,程序就不再需要返回最初的调用者。

例如以下函数,当调用了 f 函数,f 函数执行完成 g 函数后,不再需要返回 f 函数,而是直接返回到调用 f 函数的地方。

lua 复制代码
function f(x) 
	x = x + 1; 
	return g(x);
end

由于尾调用函数不会使用栈空间,所以一个程序中能够嵌套的尾调用数量是无限的。

2、如何界定是否为尾调用函数

只有形如 return func(args) 的调用才算。

func 及其参数都可以有复杂的表达式,这些复杂的表达式会被先进行计算完之后才进行调用。

举些例子

下面的代码不是尾调用,因为 f 在返回前,还需要对 g 函数的返回进行结果丢弃。

lua 复制代码
function f(x) 
	g(x)
end   

下面的代码不是尾调用,因为 g 函数的返回值还需要进一步处理(因为加了括号,会只使用一个返回值,所以也会进行舍弃多余的值),才能成为真正的返回值

lua 复制代码
return g(x) + 1     
return x or g(x)
return (g(x))

下面的代码是尾调用,因为这里的 x[j] + a*b, i+j 可以先进行计算,x[i].foo 这里也是先获取,然后最后再进调用,所以无需在返回

lua 复制代码
return  x[i].foo(x[j] + a*b, i+j)

七、写在最后

Lua 项目地址:Github传送门 (如果对你有所帮助或喜欢的话,赏个star吧,码字不易,请多多支持)

公众号搜索 "江澎涌" 可以更快的获取到后续的更新文章

相关推荐
Ajiang28247353041 小时前
对于C++中stack和queue的认识以及priority_queue的模拟实现
开发语言·c++
网易独家音乐人Mike Zhou5 小时前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
‘’林花谢了春红‘’6 小时前
C++ list (链表)容器
c++·链表·list
搬砖的小码农_Sky7 小时前
C语言:数组
c语言·数据结构
机器视觉知识推荐、就业指导7 小时前
C++设计模式:建造者模式(Builder) 房屋建造案例
c++
Yang.999 小时前
基于Windows系统用C++做一个点名工具
c++·windows·sql·visual studio code·sqlite3
熬夜学编程的小王9 小时前
【初阶数据结构篇】双向链表的实现(赋源码)
数据结构·c++·链表·双向链表
zz40_9 小时前
C++自己写类 和 运算符重载函数
c++
六月的翅膀10 小时前
C++:实例访问静态成员函数和类访问静态成员函数有什么区别
开发语言·c++