Ziglings 笔记 94: 借用 C 的数学库

站在巨人的肩膀上

在 Ziglings 的第 94 个练习中,我们继续探索 C Interop (C 互操作性)。 这一次,我们不再只是打印 Hello World,而是要解决一个实际的数学问题:浮点数取模

虽然 Zig 提供了 @mod 内置函数,但为了演示目的,我们假设必须使用 C 语言标准库 math.h 中的 fmod 函数。这模拟了在实际项目中,我们不得不调用那些尚未被 Zig 社区重写的 C 库的场景。

挑战:角度归一化

我们有一个角度 765.2 度。我们需要把它归一化到 0-360 度之间。 这就需要用到浮点数取模运算:765.2 % 360。 我们将调用 C 语言的 fmod 函数来完成计算。

解决方案

我们需要在 @cImport 块中包含正确的头文件:

const std = @import("std");

// 引入 C 库
const c = @cImport({
    // 引入 math.h 以使用 fmod 函数
    @cInclude("math.h");
});

pub fn main() !void {
    const angle = 765.2;
    const circle = 360;

    // 直接调用 C 函数
    // fmod(x, y) 返回 x 除以 y 的浮点余数
    // 765.2 - (2 * 360) = 45.2
    const result = c.fmod(angle, circle);

    // 格式化输出
    // {d: >3.1} 意思是以十进制打印,右对齐宽度 3,保留 1 位小数
    std.debug.print("The normalized angle of {d: >3.1} degrees is {d: >3.1} degrees.\n", .{ angle, result });
}

核心知识点总结

1. @cInclude 是如何工作的?

当你写 @cInclude("math.h") 时,Zig 编译器(利用内置的 Clang)会去系统的标准包含路径下寻找这个文件。它会解析文件中的函数声明、结构体定义和宏,并将它们暴露在 c 这个命名空间下。

2. 跨语言的类型桥接

C 语言的 fmod 接收两个 double。 Zig 并没有强迫我们显式地把参数转为 f64。因为 Zig 的数值字面量是无类型的(comptime_float),编译器会自动将它们“适配”进 C 函数的参数类型中。这种体验非常顺滑。

3. 为什么是 -lc

虽然代码里写了 @import,但在运行这个程序时,我们必须告诉编译器去连接 C 的运行时库。 命令:zig run -lc main.zig。 如果不加 -lc,链接器会报错,因为它找不到 fmod 的具体实现代码(机器码)。


后续预告:我们已经成功调用了 C 函数。但是,C 语言的生态中充满了各种复杂的宏定义。Zig 能理解那些宏吗?下一篇,我们将看看 Zig 如何处理 C Macros。