📘 第02课:基础语法

📋 本课目录变量声明 基本类型 函数 控制流 defer与errdefer 内置函数 练习

变量声明

Zig只有两种变量声明方式:const(不可变)和var(可变)。没有letmutstatic等关键字。

const std = @import("std");

pub fn main() !void {
    // const: 不可变绑定(编译器可优化为编译期常量)
    const x: i32 = 42;
    const y = 100;  // 类型推断为 comptime_int

    // var: 可变绑定
    var counter: usize = 0;
    counter += 1;

    // 必须初始化!Zig没有未初始化变量
    // var z: i32;  // 编译错误!
    var z: i32 = 0;  // 必须给初始值

    // undefined: 显式声明未定义值(Debug模式会填充0xAA)
    var buf: [1024]u8 = undefined;

    // 常量必须在使用前赋值
    const computed = x + y;

    // 短常量声明(块内)
    const sum = add(3, 4);

    try std.io.getStdOut().writer().print(
        "x={}, y={}, counter={}, sum={}\n",
        .{ x, y, counter, sum },
    );
}

fn add(a: i32, b: i32) i32 {
    return a + b;
}
⚠️ 重要规则:

基本类型

整数类型

// 有符号整数: i2 ~ i65535 (2的幂)
const a: i8 = -128;       // -128 ~ 127
const b: i32 = -2147483648; // 常见32位
const c: i64 = -1;        // 常见64位
const d: i128 = 0;       // 128位整数

// 无符号整数: u1 ~ u65535
const e: u8 = 255;        // 0 ~ 255, 字节
const f: usize = 0;       // 指针大小的无符号整数
const g: u1 = 1;          // 1位!只能是0或1

// comptime_int: 编译期任意精度整数
const big = 123456789012345678901234567890;

// 平台相关
const ptr_size = @sizeOf(usize); // 4 或 8

浮点类型

const pi: f16 = 3.141;   // 半精度
const e: f32 = 2.718;    // 单精度
const phi: f64 = 1.618;  // 双精度
const big_f: f80 = 1.0;   // 扩展精度(x86)
const huge: f128 = 1.0;  // 四精度

// comptime_float: 编译期任意精度浮点
const exact_pi = 3.14159265358979323846;

其他类型

// 布尔
const t: bool = true;
const f: bool = false;

// 可选类型(类似Rust的Option)
var maybe: ?i32 = 42;    // 可以是i32或null
maybe = null;            // 可以赋null

// 错误联合类型
var result: anyerror!i32 = 42;
result = error.NotFound;

// 类型本身是一等值
const IntType = i32;
const x: IntType = 42;

类型转换

// 显式转换——Zig没有隐式类型转换!
const a: i32 = 42;
const b: f64 = @floatFromInt(a);  // i32 → f64
const c: i32 = @intFromFloat(b);  // f64 → i32 (截断)
const d: u8 = @intCast(a);       // i32 → u8 (安全检查)
const e: u32 = @intCast(a);       // i32 → u32 (值域检查)
const ptr: *const i32 = &a;
const int_val: usize = @intFromPtr(ptr); // 指针→整数

函数

// 基本函数
fn add(a: i32, b: i32) i32 {
    return a + b;
}

// 函数可以返回错误联合类型
fn divide(a: f64, b: f64) !f64 {
    if (b == 0.0) return error.DivisionByZero;
    return a / b;
}

// 函数可以返回可选类型
fn findItem(items: []const Item, id: u32) ?Item {
    for (items) |item| {
        if (item.id == id) return item;
    }
    return null;
}

// defer: 函数退出时执行(类似Go的defer)
fn processFile(path: []const u8) !void {
    const file = try std.fs.cwd().openFile(path, .{});
    defer file.close();  // 无论如何都关闭

    var buf: [1024]u8 = undefined;
    const n = try file.readAll(&buf);
    // ... 处理buf ...
}

// errdefer: 只在错误返回时执行
fn createResource() !Resource {
    const r = try allocateResource();
    errdefer freeResource(r);  // 只有出错时才释放

    if (!initResource(r)) {
        return error.InitFailed;  // 会触发errdefer
    }
    return r;
}

// comptime参数:编译期泛型
fn max(comptime T: type, a: T, b: T) T {
    return if (a > b) a else b;
}

// 使用
const m1 = max(i32, 3, 7);    // 7
const m2 = max(f64, 3.14, 2.71); // 3.14

控制流

// if是表达式,有返回值
const abs_x = if (x >= 0) x else -x;

// if + 可选类型解包
if (maybe_value) |val| {
    // val 是非null的值
    std.debug.print("got: {}\n", .{val});
} else {
    std.debug.print("null\n", .{});
}

// while循环
var i: usize = 0;
while (i < 10) : (i += 1) {
    // i从0到9
}

// while + 可选类型(类似迭代器)
var it = list.iterator();
while (it.next()) |item| {
    // 处理item
}

// for循环(遍历序列)
const items = [_]i32{ 1, 2, 3, 4, 5 };
for (items, 0..) |item, idx| {
    std.debug.print("[{}] = {}\n", .{ idx, item });
}

// switch(必须穷举所有情况)
const tag: enum { a, b, c } = .b;
const result = switch (tag) {
    .a => "first",
    .b => "second",
    .c => "third",
};

// 带范围和合并分支的switch
fn classify(n: i32) u8 {
    return switch (n) {
        0...9 => 1,          // 范围
        10...99 => 2,
        100...999 => 3,
        else => 4,           // 兜底
    };
}

defer与errdefer

defererrdefer是Zig资源管理的核心机制:

const std = @import("std");

pub fn main() !void {
    // defer: 作用域退出时执行,无论成功还是失败
    {
        const file = try std.fs.cwd().openFile("test.txt", .{});
        defer file.close();
        // 无论如何都会close
    }

    // 多个defer按LIFO顺序执行
    {
        std.debug.print("1\n", .{});
        defer std.debug.print("4\n", .{});
        std.debug.print("2\n", .{});
        defer std.debug.print("3\n", .{});
        std.debug.print("end\n", .{});
        // 输出: 1 2 end 3 4
    }

    // errdefer: 只在错误退出时执行
    const result = allocAndInit() catch |err| {
        std.debug.print("init failed: {}\n", .{err});
        return err;
    };
    _ = result;
}

fn allocAndInit() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();

    const buf = try allocator.alloc(u8, 1024);
    errdefer allocator.free(buf);  // 仅出错时释放

    // 如果这行失败,errdefer会释放buf
    if (!initBuffer(buf)) {
        return error.InitFailed;
    }

    // 成功路径:buf由调用者管理
    allocator.free(buf);
}

fn initBuffer(buf: []u8) bool {
    @memset(buf, 0xAA);
    return true;
}

内置函数

Zig的内置函数以@开头,提供编译期和运行时的核心操作:

函数说明示例
@import导入模块@import("std")
@cImport导入C头文件@cImport(@cInclude("stdio.h"))
@sizeOf类型大小(字节)@sizeOf(u64) → 8
@alignOf类型对齐@alignOf(u64) → 8
@TypeOf获取表达式类型@TypeOf(42) → comptime_int
@intCast安全整数转换@intCast(i32, x)
@floatFromInt整数转浮点@floatFromInt(f64, 42)
@panic不可恢复错误@panic("unreachable")
@compileError编译期报错@compileError("not impl")
@setRuntimeSafety开关安全检查@setRuntimeSafety(false)

练习

📝 课后练习

  1. 类型探索:打印各种类型的大小和对齐:@sizeOf, @alignOf, @bitSizeOf
  2. 温度转换:实现Celsius↔Fahrenheit转换函数,使用f64
  3. defer链:写3个defer,观察LIFO执行顺序
  4. errdefer:写一个函数分配资源,成功时返回,失败时用errdefer清理
  5. 泛型max:实现max(comptime T: type, a: T, b: T) T