Lua


5.2 函数的调用


文档摘要

5.2 函数的调用 Lua 函数调用详解:代码实践与深度解析 1. 函数调用的基本形式 在 Lua 中,函数调用使用一对圆括号 跟在函数名之后。如果函数需要参数,则参数列表放在圆括号内,参数之间用逗号 分隔。 1.1 无参数函数调用 对于不接受任何参数的函数,调用时只需在函数名后加上空括号 即可。 代码解析: 定义了一个名为 的函数,它不接受任何参数,函数体内部使用 函数输出问候语。 这行代码调用了 函数。由于函数不需要参数,因此括号内为空。Lua 解释器执行函数体内的代码,从而打印出 "Hello, Lua!"。 1.2 带参数函数调用 当函数定义时声明了参数,调用函数时需要提供相应数量和类型的参数。 代码解析: 定义了一个名为 的函数,它接受两个参数 和 ,并返回它们的和。

5.2 函数的调用

Lua 函数调用详解:代码实践与深度解析

1. 函数调用的基本形式

在 Lua 中,函数调用使用一对圆括号 () 跟在函数名之后。如果函数需要参数,则参数列表放在圆括号内,参数之间用逗号 , 分隔。

1.1 无参数函数调用

对于不接受任何参数的函数,调用时只需在函数名后加上空括号 () 即可。

-- 定义一个无参数函数 function greet() print("Hello, Lua!") end -- 调用无参数函数 greet() -- 输出: Hello, Lua!

代码解析:

  • function greet() ... end 定义了一个名为 greet 的函数,它不接受任何参数,函数体内部使用 print 函数输出问候语。

  • greet() 这行代码调用了 greet 函数。由于函数不需要参数,因此括号内为空。Lua 解释器执行函数体内的代码,从而打印出 "Hello, Lua!"。

1.2 带参数函数调用

当函数定义时声明了参数,调用函数时需要提供相应数量和类型的参数。

-- 定义一个带两个参数的函数 function add(a, b) return a + b end -- 调用带参数函数 local sum = add(5, 3) print("Sum:", sum) -- 输出: Sum: 8 local product = add(2.5, 4) print("Product:", product) -- 输出: Product: 6.5

代码解析:

  • function add(a, b) ... end 定义了一个名为 add 的函数,它接受两个参数 ab,并返回它们的和。

  • local sum = add(5, 3) 调用 add 函数,传递参数 53。函数执行后返回结果 8,赋值给局部变量 sum

  • local product = add(2.5, 4) 再次调用 add 函数,这次传递了浮点数参数 2.5 和整数 4。Lua 是动态类型语言,函数可以接受不同类型的参数,只要参数支持函数内部的操作即可。

2. 参数传递机制

Lua 函数的参数传递机制是 按值传递,但对于表(table)类型的数据,则是 按引用传递 的语义。

2.1 按值传递 (Value Passing)

对于基本数据类型(如数字、字符串、布尔值),以及 nil,函数调用时会将参数值的副本传递给函数内部的形参。在函数内部对形参的修改不会影响到函数外部实参的值。

function modify_number(num) num = num + 10 print("Inside function:", num) -- 打印函数内部修改后的值 end local original_num = 5 modify_number(original_num) print("Outside function:", original_num) -- 打印函数外部原始值 -- 输出: -- Inside function: 15 -- Outside function: 5

代码解析:

  • modify_number 函数接收一个参数 num。在函数内部,num 的值被加 10。

  • modify_number(original_num) 调用函数时,original_num 的值 5 被复制一份传递给形参 num

  • 函数内部对 num 的修改 (num = num + 10) 只影响函数内部的 num 变量,不会改变函数外部的 original_num 的值。因此,函数外部 original_num 的值仍然是 5

2.2 按引用传递语义 (Reference Semantics for Tables)

当参数类型为表(table)时,传递的是表的引用(或者说地址)。函数内部对表形参的修改会影响到函数外部实参所指向的表。这被称为 "按引用传递语义",虽然 Lua 实际上仍然是按值传递,但传递的值是表的引用,因此表现出按引用传递的效果。

function modify_table(tbl) tbl.value = tbl.value + 10 print("Inside function:", tbl.value) end local original_table = {value = 5} modify_table(original_table) print("Outside function:", original_table.value) -- 输出: -- Inside function: 15 -- Outside function: 15

代码解析:

  • modify_table 函数接收一个表 tbl 作为参数。

  • modify_table(original_table) 调用函数时,original_table 的引用被传递给形参 tbltbloriginal_table 指向内存中的同一个表。

  • 函数内部 tbl.value = tbl.value + 10 修改了表 tbl 中键为 value 的元素的值。由于 tbloriginal_table 指向同一个表,因此函数外部访问 original_table.value 时,看到的是修改后的值 15

3. 函数的返回值

Lua 函数可以返回零个、一个或多个值。使用 return 语句来指定返回值。

3.1 单个返回值

如果函数只返回一个值,直接在 return 语句后跟上要返回的值即可。

function square(x) return x * x end local result = square(7) print("Square:", result) -- 输出: Square: 49

3.2 多个返回值

Lua 函数可以返回多个值,返回值之间用逗号 , 分隔。调用函数时,可以使用多个变量来接收多个返回值。

function divide_and_remainder(a, b) local quotient = math.floor(a / b) -- 商 local remainder = a % b -- 余数 return quotient, remainder end local q, r = divide_and_remainder(17, 5) print("Quotient:", q, "Remainder:", r) -- 输出: Quotient: 3 Remainder: 2 local only_quotient = divide_and_remainder(17, 5) -- 只接收第一个返回值 print("Only Quotient:", only_quotient) -- 输出: Only Quotient: 3 local _, only_remainder = divide_and_remainder(17, 5) -- 使用 _ 忽略第一个返回值 print("Only Remainder:", only_remainder) -- 输出: Only Remainder: 2

代码解析:

  • divide_and_remainder 函数计算两个数的商和余数,并使用 return quotient, remainder 返回两个值。

  • local q, r = divide_and_remainder(17, 5) 调用函数,并将返回的两个值分别赋值给变量 qr

  • local only_quotient = divide_and_remainder(17, 5) 只接收函数的第一个返回值,第二个返回值被忽略。

  • local _, only_remainder = divide_and_remainder(17, 5) 使用下划线 _ 作为一个占位符变量,表示忽略函数的第一个返回值,只接收第二个返回值。 _ 在 Lua 中通常用作匿名变量,用于表示不关心的返回值。

3.3 无返回值

如果函数没有显式的 return 语句,或者 return 语句后没有跟任何值,则函数默认返回 nil

function print_message(msg) print("Message:", msg) -- 没有 return 语句 end local return_value = print_message("Hello again!") print("Return Value:", return_value) -- 输出: Return Value: nil function empty_return() return -- 空 return 语句 end return_value = empty_return() print("Return Value (empty return):", return_value) -- 输出: Return Value (empty return): nil

4. 变长参数 (Variadic Arguments)

Lua 函数支持变长参数,允许函数接受数量不定的参数。变长参数使用 ... (三个点) 表示。在函数内部,可以使用 arg 表(在 Lua 5.1 及更早版本中)或 ... 表达式结合 select 函数(在 Lua 5.2 及更高版本中)来访问变长参数。

4.1 Lua 5.1 及更早版本 (使用 arg 表)

在 Lua 5.1 及更早版本中,变长参数会被收集到一个名为 arg 的特殊表中。arg 表的索引从 1 开始,arg[1] 是第一个变长参数,arg[2] 是第二个,以此类推。arg.n 存储了变长参数的数量。

-- Lua 5.1 示例 (不推荐在新版本中使用) function sum_variadic_old(...) local total = 0 for i = 1, arg.n do total = total + arg[i] end return total end local sum_old = sum_variadic_old(1, 2, 3, 4, 5) print("Sum (old method):", sum_old) -- 输出: Sum (old method): 15

4.2 Lua 5.2 及更高版本 (使用 ...select 函数)

在 Lua 5.2 及更高版本中,不再使用 arg 表。变长参数仍然用 ... 表示,但访问变长参数需要使用 select 函数。

  • select('#', ...) 返回变长参数的数量。

  • select(n, ...) 返回从第 n 个开始的所有变长参数。

-- Lua 5.2+ 示例 (推荐使用) function sum_variadic_new(...) local total = 0 for i = 1, select('#', ...) do total = total + select(i, ...) end return total end local sum_new = sum_variadic_new(1, 2, 3, 4, 5) print("Sum (new method):", sum_new) -- 输出: Sum (new method): 15 function format_message(format_str, ...) return string.format(format_str, ...) -- 直接将变长参数传递给 string.format end local formatted_msg = format_message("Name: %s, Age: %d", "Alice", 30) print("Formatted Message:", formatted_msg) -- 输出: Formatted Message: Name: Alice, Age: 30

代码解析:

  • sum_variadic_new 函数使用 select('#', ...) 获取变长参数的数量,并使用 select(i, ...) 在循环中逐个访问变长参数进行求和。

  • format_message 函数展示了变长参数的一个常见应用场景:将变长参数直接传递给其他接受变长参数的函数,如 string.format

5. 函数作为值 (First-Class Functions)

在 Lua 中,函数是 一等公民,这意味着函数可以像其他任何值(如数字、字符串)一样被赋值给变量、作为参数传递给其他函数,以及作为函数的返回值。

5.1 将函数赋值给变量

可以将函数赋值给变量,然后通过变量名来调用函数。

function say_hello() print("Hello!") end local greeting_func = say_hello -- 将函数赋值给变量 greeting_func() -- 通过变量调用函数,输出: Hello! local another_greeting = greeting_func another_greeting() -- 也可以通过另一个变量调用,输出: Hello!

5.2 函数作为参数 (高阶函数 - Higher-Order Functions)

函数可以作为参数传递给其他函数。接受函数作为参数的函数被称为 高阶函数

function apply_operation(func, a, b) return func(a, b) -- 调用作为参数传递的函数 end function multiply(x, y) return x * y end local result_multiply = apply_operation(multiply, 5, 4) -- 将 multiply 函数作为参数传递 print("Multiply Result:", result_multiply) -- 输出: Multiply Result: 20 local result_add = apply_operation(function(x, y) return x + y end, 10, 2) -- 使用匿名函数 print("Add Result:", result_add) -- 输出: Add Result: 12

代码解析:

  • apply_operation 是一个高阶函数,它接受一个函数 func 以及两个参数 ab。函数内部调用 func(a, b),即执行作为参数传递进来的函数。

  • apply_operation(multiply, 5, 4) 调用 apply_operation,将 multiply 函数作为第一个参数传递。apply_operation 内部会调用 multiply(5, 4)

  • apply_operation(function(x, y) return x + y end, 10, 2) 展示了使用 匿名函数 作为参数。匿名函数是在函数调用时直接定义的函数,没有函数名。

5.3 函数作为返回值 (闭包 - Closures)

函数可以作为另一个函数的返回值。当一个函数返回另一个函数时,返回的函数可以访问其创建时所在作用域的局部变量,即使外部函数已经执行完毕。这种现象称为 闭包

function create_counter() local count = 0 -- 局部变量 return function() -- 返回一个匿名函数 (闭包) count = count + 1 return count end end local counter1 = create_counter() -- 创建第一个计数器 local counter2 = create_counter() -- 创建第二个计数器 print("Counter 1:", counter1()) -- 输出: Counter 1: 1 print("Counter 1:", counter1()) -- 输出: Counter 1: 2 print("Counter 2:", counter2()) -- 输出: Counter 2: 1 print("Counter 1:", counter1()) -- 输出: Counter 1: 3 print("Counter 2:", counter2()) -- 输出: Counter 2: 2

代码解析:

  • create_counter 函数内部定义了一个局部变量 count,并返回一个匿名函数。

  • 返回的匿名函数形成一个闭包,它可以访问并修改 create_counter 函数作用域内的局部变量 count

  • counter1 = create_counter()counter2 = create_counter() 分别创建了两个独立的计数器闭包。每个闭包都维护着自己的 count 变量,互不影响。

  • 每次调用 counter1()counter2(),都会递增各自闭包内部的 count 变量并返回新的计数值。

6. 方法调用 (Method Calls)

Lua 支持面向对象编程的风格,可以使用冒号 : 语法糖来调用 "方法"。方法实际上是函数,但第一个参数是隐含的 self 参数,指向调用方法的对象(通常是一个表)。

local rectangle = { width = 10, height = 5, area = function(self) -- 定义 area 函数 return self.width * self.height end, perimeter = function(self) return 2 * (self.width + self.height) end } -- 使用点号 . 调用函数 (传统函数调用) print("Area (dot):", rectangle.area(rectangle)) -- 输出: Area (dot): 50 print("Perimeter (dot):", rectangle.perimeter(rectangle)) -- 输出: Perimeter (dot): 30 -- 使用冒号 : 调用方法 (方法调用) print("Area (colon):", rectangle:area()) -- 输出: Area (colon): 50 print("Perimeter (colon):", rectangle:perimeter()) -- 输出: Perimeter (colon): 30

代码解析:

  • rectangle 是一个表,模拟一个矩形对象。它包含了 widthheight 属性以及 areaperimeter 函数。

  • areaperimeter 函数的定义中,第一个参数是 self

  • rectangle.area(rectangle) 使用点号 . 调用 area 函数,需要显式地将 rectangle 表作为参数传递给 area 函数的 self 参数。

  • rectangle:area() 使用冒号 : 调用 area 方法。冒号语法糖会自动将调用方法的对象 rectangle 作为第一个参数传递给 area 函数的 self 参数。

本质上,object:method(args) 等价于 object.method(object, args) 冒号语法使面向对象风格的代码更加简洁易读。

7. 函数调用与错误处理

在函数调用过程中,可能会发生错误,例如参数类型错误、访问不存在的变量等。Lua 提供了 pcall (protected call) 和 xpcall (extended protected call) 函数来进行错误处理,防止错误导致程序崩溃。

7.1 pcall (Protected Call)

pcall 函数在一个保护模式下调用函数。它接收一个函数和要传递给该函数的参数作为参数。pcall 返回两个值:

  • 第一个返回值是一个布尔值,表示函数调用是否成功。true 表示成功,false 表示失败。

  • 如果函数调用成功,第二个返回值是函数的返回值。

  • 如果函数调用失败,第二个返回值是错误信息。

function unsafe_function(x) if x > 10 then error("Input value too large!") -- 手动抛出错误 end return x * 2 end local status, result = pcall(unsafe_function, 5) if status then print("Call successful, result:", result) -- 输出: Call successful, result: 10 else print("Call failed, error:", result) end status, result = pcall(unsafe_function, 15) if status then print("Call successful, result:", result) else print("Call failed, error:", result) -- 输出: Call failed, error: Input value too large! end

7.2 xpcall (Extended Protected Call)

xpcall 函数与 pcall 类似,也用于在保护模式下调用函数,但 xpcall 允许指定一个错误处理函数。当被调用的函数发生错误时,Lua 解释器会调用指定的错误处理函数,并将错误对象作为参数传递给错误处理函数。xpcall 也返回两个值,与 pcall 相同。

function error_handler(err) print("Custom error handler caught an error:", err) -- 可以进行更详细的错误日志记录或处理 return "Recovered from error" -- 错误处理函数可以返回一个值,作为 xpcall 的第二个返回值 end local status, result = xpcall(unsafe_function, error_handler, 20) if status then print("Call successful, result:", result) else print("Call failed, error:", result) -- 输出: Call failed, error: Recovered from error (因为 error_handler 返回了值) end status, result = xpcall(unsafe_function, error_handler, 8) if status then print("Call successful, result:", result) -- 输出: Call successful, result: 16 else print("Call failed, error:", result) end

总结

函数调用是 Lua 编程的核心操作。本文详细介绍了 Lua 函数调用的各种形式和机制,包括:

  • 基本调用形式: 无参数和带参数函数的调用语法。

  • 参数传递机制: 按值传递和表类型的按引用传递语义。

  • 返回值处理: 单个、多个和无返回值的情况。

  • 变长参数: ...select 函数的使用。

  • 函数作为值: 函数作为变量、参数和返回值,以及闭包的概念。

  • 方法调用: 冒号 : 语法糖及其与点号 . 语法的区别。

  • 错误处理: pcallxpcall 函数的使用。

掌握函数调用的各个方面,能够帮助开发者编写更灵活、健壮和可维护的 Lua 代码,充分利用 Lua 函数作为一等公民的特性,构建模块化和可复用的程序结构。


发布者: 作者: 转发
评论区 (0)
U