Day 10: 函数声明与参数
欢迎来到第十天!函数是组织代码、实现复用和抽象的核心单元。在Zig中,函数是一等公民,这意味着它们可以像任何其他值一样被传递、赋值给变量和作为其他函数的返回值。今天,我们将深入学习如何声明函数、理解其参数传递机制,并探索Zig中一些强大的函数特性,如多返回值和可变参数。
2. 函数声明
Section titled “2. 函数声明”一个基本的Zig函数声明包含可见性修饰符(可选)、fn关键字、函数名、参数列表、返回类型和函数体。
// 语法: [pub] fn functionName(param1: Type1, ...) ReturnType { ... }
/// 一个简单的加法函数pub fn add(a: i32, b: i32) i32 { return a + b;}pub: 使函数在当前文件外部可见。如果省略,函数默认为文件私有。fn: 声明这是一个函数。add: 函数的名称。(a: i32, b: i32): 参数列表。每个参数都必须有明确的类型。i32: 返回类型。如果函数不返回值,则为void。如果可能返回错误,则为!ReturnType。{ ... }: 函数体,包含要执行的代码。return: 关键字,用于从函数返回值。
3. 参数传递
Section titled “3. 参数传递”在Zig中,参数默认按值传递(Pass-by-Value)。这意味着函数接收的是原始值的一个副本。在函数内部修改参数,不会影响到函数外部的原始变量。
fn increment(mut val: i32) void { val += 1; // 修改的是副本}
var x: i32 = 10;increment(x);// x 的值仍然是 10如果你想让函数能够修改外部的变量,你必须传递一个指针。
fn incrementPtr(ptr: *i32) void { ptr.* += 1; // 解引用指针,修改原始值}
var y: i32 = 10;incrementPtr(&y);// y 的值现在是 114. 多返回值
Section titled “4. 多返回值”与其他许多语言不同,Zig的函数可以方便地返回多个值。这是通过返回一个匿名的结构体(或元组)来实现的。
fn divRem(numerator: i32, denominator: i32) struct { div: i32, rem: i32 } { return .{ .div = numerator / denominator, .rem = denominator, };}
const result = divRem(10, 3);// result.div 是 3// result.rem 是 1接收返回值时,可以使用 const { .div, .rem } = divRem(10, 3); 这样的解构语法,但这在Zig 0.12.0中已被移除,推荐直接访问返回的结构体字段。
5. 可变参数函数
Section titled “5. 可变参数函数”Zig支持类型安全的可变参数(variadic functions),这与C语言的 ... 非常不同。在Zig中,你必须使用 ... 语法,并且所有可变参数必须是同一种类型。
fn sum(numbers: []const i32) i32 { var total: i32 = 0; for (numbers) |n| { total += n; } return total;}
// 调用时,使用 .{} 语法创建一个匿名切片const total = sum(&.{ 1, 2, 3, 4, 5 });在函数定义中使用 ... 语法是另一种方式,它在底层会被转换成一个切片。
fn format(comptime fmt_str: []const u8, args: anytype) []const u8 { // ...}// `anytype` 允许 `args` 是一个包含多种类型的匿名结构体// `std.fmt.bufPrint` 就是这样实现的6. 示例:递归计算斐波那契数列
Section titled “6. 示例:递归计算斐波那契数列”递归是函数调用自身的一种技术,非常适合解决斐波那契数列这类问题。
const std = @import("std");
fn fib(n: u32) u32 { if (n <= 1) { return n; } return fib(n - 1) + fib(n - 2);}
pub fn main() !void { const result = fib(10); std.debug.print("Fib(10) = {d}\n", .{result}); // 输出: Fib(10) = 55}注意: 这种朴素的递归实现效率很低,仅用于演示函数递归调用。
7. 实践练习:GCD/LCM 库
Section titled “7. 实践练习:GCD/LCM 库”创建一个名为 math.zig 的文件,在其中实现两个函数:
gcd(a: u32, b: u32) u32: 使用欧几里得算法计算两个数的最大公约数(Greatest Common Divisor)。lcm(a: u32, b: u32) u32: 使用公式lcm(a, b) = (|a * b|) / gcd(a, b)计算最小公倍数(Least Common Multiple)。
将这两个函数都声明为 pub。然后,在 main.zig 中使用 @import 导入你的 math.zig 模块,并调用这两个函数进行测试。
8. 常见问题
Section titled “8. 常见问题”问:为什么我的函数参数类型与传入的实参类型完全一样,编译器还是报“类型不匹配”?
答:最常见的原因是指针的可变性问题。一个期望 *T (可变指针) 的函数不能接受 *const T (常量指针) 类型的参数。请仔细检查你的指针类型,确保可变性匹配。另一个可能的原因是数字类型不完全匹配,例如函数期望 i32,但你传入了一个 u32 或 i64。记住,Zig没有隐式类型转换。
问:函数可以嵌套定义吗?
答:不可以。Zig不支持在函数内部定义另一个函数。所有的 fn 都必须在顶层作用域或 struct/union/enum 的内部定义。
今天,我们掌握了Zig函数的核心知识。我们学习了如何定义函数、按值传递和按指针传递的区别、强大的多返回值能力以及类型安全的可变参数。函数是构建任何程序的基本模块,理解其工作原理至关重要。
明天,我们将正式深入Zig最受赞誉的特性之一:显式错误处理。我们将学习 error 类型和 try/catch 表达式。