Chapter 02

基础语法:变量、类型与控制流

掌握 Zig 的变量声明、完整类型系统与控制流结构

变量声明

const 与 var

Zig 有两种变量声明方式:const(不可变,编译后为常量)和 var(可变)。Zig 默认推荐使用 const,这让代码更安全、意图更清晰。

const x: i32 = 42;      // 不可变常量,类型显式标注
const y = 3.14;         // 类型推断为 comptime_float
var counter: u32 = 0;  // 可变变量
counter += 1;

// 常量必须在声明时初始化(或使用 undefined)
var buf: [256]u8 = undefined;  // 未初始化(在 debug 模式填充 0xaa 用于检测)

// 忽略变量(用 _ 丢弃值)
const result = someFunction();
_ = result;  // 明确表示不使用该值
Zig 中没有"未使用变量"警告的例外

在 Zig 中,声明了但未使用的变量会导致编译错误(不是警告)。如果确实不需要某个值,必须用 _ = value 显式丢弃,这强制程序员明确表达意图。

整数类型

定宽整数

Zig 提供了从 1 位到 65535 位的任意位宽整数类型。最常用的如下:

u8 / i8
无符号/有符号 8 位整数。u8 范围 0-255,i8 范围 -128 到 127。常用于字节数组。
u16 / i16
16 位整数。u16 范围 0-65535。常用于端口号、Unicode 代码点等。
u32 / i32
32 位整数。最常用的通用整数类型,适合大多数计数和索引场景。
u64 / i64
64 位整数。用于大数值、时间戳(纳秒级)、文件偏移量等。
u128 / i128
128 位整数。用于 UUID、加密算法中的大整数运算。
usize / isize
平台相关大小,在 64 位系统上为 u64/i64,在 32 位系统上为 u32/i32。用于内存地址、数组索引。
comptime_int
编译期整数,精度无限。字面量 42 的默认类型。只能在编译期使用,运行时必须转换为具体类型。
u1, u3, u7...
任意位宽整数。例如 u1 只有 0 和 1,用于位操作或 packed struct 的位字段。
// 整数字面量
const decimal = 1_000_000;    // 下划线分隔符,提高可读性
const hex = 0xFF_AA_BB;
const octal = 0o755;
const binary = 0b1010_1010;

// 类型转换(必须显式)
const a: u32 = 300;
const b: u8 = @truncate(a);   // 截断:取低8位 = 44
const c: u64 = @as(u64, a);    // 安全拓宽
const d: i32 = @bitCast(a);   // 按位重新解释

// 整数溢出:Zig 在 debug 模式检测,release 模式使用 wrapping 运算符
var n: u8 = 255;
n +%= 1;  // wrapping add,结果 0(不会 panic)
n -%= 1;  // wrapping sub
n *%= 2;  // wrapping mul

浮点类型

const f32_val: f32 = 3.14;
const f64_val: f64 = 2.718281828459045;
const f128_val: f128 = 1.0;         // 128 位,极高精度
const cf = 3.14;                     // 类型为 comptime_float,精度无限

// 特殊值
const inf = std.math.inf(f64);
const nan = std.math.nan(f64);
const is_nan = std.math.isNan(nan); // true

// 数学函数
const sqrt2 = std.math.sqrt(2.0);
const pi = std.math.pi;

bool 与 void

// bool 只有两个值
const t: bool = true;
const f: bool = false;

// 逻辑运算符
const and_result = t and f;  // false(短路)
const or_result = t or f;   // true(短路)
const not_result = !t;       // false

// void 表示"没有值",不同于 null
fn doNothing() void {}

// void 类型的唯一值是 {} (空结构体字面量)
const nothing: void = {};
_ = nothing;

可选类型(Optional)

Zig 没有 null 指针,但有可选类型 ?T,明确表示一个值可能存在也可能不存在。

var maybe_num: ?i32 = null;
maybe_num = 42;

// 解包可选值
if (maybe_num) |num| {
    std.debug.print("值是: {d}\n", .{num});
} else {
    std.debug.print("是 null\n", .{});
}

// orelse:提供默认值
const value = maybe_num orelse 0;  // 如果 null 则用 0

// orelse unreachable:断言不为 null(若为 null 则 panic)
const guaranteed = maybe_num orelse unreachable;

// .? 语法糖,等价于 orelse unreachable
const unwrapped = maybe_num.?;

控制流

if 表达式

Zig 的 if表达式,可以返回值:

const x = 10;

// 普通 if 语句
if (x > 5) {
    std.debug.print("大于5\n", .{});
} else if (x == 5) {
    std.debug.print("等于5\n", .{});
} else {
    std.debug.print("小于5\n", .{});
}

// if 作为表达式(三元运算符的替代)
const abs = if (x >= 0) x else -x;

// 带可选解包的 if
var opt: ?i32 = 42;
if (opt) |v| {
    std.debug.print("有值: {d}\n", .{v});
}

// 带指针解包(用于修改值)
if (opt) |*v| {
    v.* += 1;  // 修改可选值内部
}

while 循环

// 基本 while
var i: u32 = 0;
while (i < 10) : (i += 1) {
    std.debug.print("{d} ", .{i});
}
// : (i += 1) 是 continue 表达式,每次循环(包括 continue 后)都执行

// while 作为表达式(带 break 返回值)
var j: u32 = 0;
const found = while (j < 100) : (j += 1) {
    if (j * j > 50) break j;
} else 0;  // else 在循环正常结束时执行

// while 解包可选值(类似迭代器)
var iter = SomeIterator{};
while (iter.next()) |item| {
    // item 是解包后的值
    _ = item;
}

for 循环

const nums = [_]i32{ 1, 2, 3, 4, 5 };

// 遍历数组/切片
for (nums) |n| {
    std.debug.print("{d} ", .{n});
}

// 带索引
for (nums, 0..) |n, idx| {
    std.debug.print("[{d}]={d} ", .{ idx, n });
}

// 遍历范围(0..10 是半开区间 [0, 10))
for (0..10) |k| {
    std.debug.print("{d} ", .{k});
}

// 同时遍历两个切片(长度必须相同)
const keys = [_][]const u8{ "a", "b", "c" };
const vals = [_]i32{ 1, 2, 3 };
for (keys, vals) |k, v| {
    std.debug.print("{s}={d} ", .{ k, v });
}

switch 表达式

const day: u8 = 3;

// switch 必须穷举所有可能(或使用 else)
const name = switch (day) {
    1 => "Monday",
    2 => "Tuesday",
    3 => "Wednesday",
    4, 5 => "Thu or Fri",  // 多值匹配
    6..7 => "Weekend",        // 范围匹配
    else => "Unknown",
};

// switch 枚举(会检查穷举性)
const Direction = enum { north, south, east, west };
const dir = Direction.north;
const opposite = switch (dir) {
    .north => Direction.south,
    .south => Direction.north,
    .east  => Direction.west,
    .west  => Direction.east,
};
_ = opposite;

块表达式与 unreachable

// 带标签的块,可以用 break 返回值
const result = blk: {
    const a = 10;
    const b = 20;
    break :blk a + b;  // 块的"返回值"是 30
};

// unreachable:告知编译器此处不可能到达(若到达则 panic)
fn divide(a: f64, b: f64) f64 {
    if (b == 0.0) unreachable;  // 调用者保证 b != 0
    return a / b;
}

// comptime_int 与 _ 类型推断
const inferred = 42;  // 类型是 comptime_int
var runtime: i32 = inferred;  // 运行时变量,编译期值强制转换
_ = runtime;
comptime_int 的精度

comptime_int 是任意精度整数,在编译期进行精确计算。只有当它被赋值给具体运行时类型时,才会检查是否溢出。这意味着 const big = 1 << 200 在编译期完全合法(如果只在 comptime 上下文中使用)。

数组与切片

数组(Array)

Zig 的数组长度是类型的一部分,在编译期已知。[5]u8[6]u8 是不同的类型,无法互相赋值。

// 数组字面量(编译期长度推断)
const arr = [_]i32{ 1, 2, 3, 4, 5 };  // 类型: [5]i32
const len = arr.len;                   // 编译期常量 5

// 固定大小数组
var buf: [256]u8 = undefined;    // 256 字节缓冲区(未初始化)
var zeros = std.mem.zeroes([64]u8);  // 零初始化

// 数组访问:越界在 Debug 模式 panic,ReleaseFast 是 UB
buf[0] = 65;  // 'A'
buf[1] = 66;  // 'B'

// 数组拼接(comptime)
const a = [_]u8{ 1, 2 };
const b = [_]u8{ 3, 4 };
const c = a ++ b;  // [4]u8{ 1, 2, 3, 4 },编译期操作

// 数组重复
const repeated = [_]u8{0} ** 8;  // [8]u8{ 0,0,0,0,0,0,0,0 }

切片(Slice)

切片是指向数组某段的胖指针,包含指针和长度两个字段。[]u8 是可变切片,[]const u8 是不可变切片(字符串通常用 []const u8)。

var arr = [_]i32{ 10, 20, 30, 40, 50 };

// 创建切片(半开区间 [start, end))
const slice = arr[1..4];  // { 20, 30, 40 },类型: []i32
const all = arr[0..];   // 整个数组的切片
const all2 = &arr;       // 等价写法

// 切片的两个字段
std.debug.print("ptr={*}, len={d}\n", .{ slice.ptr, slice.len });

// 修改切片会修改原数组
arr[1..3][0] = 99;  // arr[1] 变为 99

// 切片作为函数参数(不复制数据)
fn sum(nums: []const i32) i64 {
    var total: i64 = 0;
    for (nums) |n| total += n;
    return total;
}
const result = sum(&arr);    // 传入整个数组的切片
const partial = sum(arr[1..3]); // 传入子切片
_ = result;
_ = partial;
切片 vs 数组的关键区别

字符串处理

Zig 中的字符串

Zig 没有内置的 String 类型。字符串就是 []const u8(字节切片)。字符串字面量是以 null 结尾的字节数组,但通常通过切片使用(不包含 null 终止符)。

// 字符串字面量:类型是 *const [5:0]u8(指向以null结尾的5字节数组)
const hello = "Hello";

// 转为切片使用(常见用法)
const s: []const u8 = "Hello, Zig!";
std.debug.print("{s}\n", .{s});
std.debug.print("length: {d}\n", .{s.len});

// 多行字符串(\\ 开头,不支持转义)
const multiline =
    \\第一行
    \\第二行
    \\第三行
;

// 字符字面量(单字符,类型是 comptime_int)
const ch = 'A';        // 65
const newline = '\n';  // 10

// 字符串比较(不能用 ==,要用 std.mem.eql)
const a = "hello";
const b = "hello";
const equal = std.mem.eql(u8, a, b);  // true

// 字符串格式化(动态构建字符串)
const allocator = std.heap.page_allocator;
const formatted = try std.fmt.allocPrint(allocator, "Hello, {s}! You are {d} years old.", .{ "Alice", 30 });
defer allocator.free(formatted);
std.debug.print("{s}\n", .{formatted});
Zig 字符串的 UTF-8 注意事项

Zig 的字符串是字节序列,没有内置 Unicode 支持。s.len 返回的是字节数,不是 Unicode 字符数。对于中文等多字节字符,需要使用 std.unicode 模块处理。索引操作 s[0] 获取的是第一个字节(u8),不是 Unicode 代码点。

// UTF-8 字符串处理
const chinese = "你好";
std.debug.print("bytes: {d}\n", .{chinese.len});  // 6(每个汉字3字节)

// 迭代 Unicode 代码点
var view = try std.unicode.Utf8View.init(chinese);
var iter = view.iterator();
while (iter.nextCodepoint()) |codepoint| {
    std.debug.print("codepoint: U+{X:0>4}\n", .{codepoint});
}

// 字符串分割
var parts = std.mem.splitSequence(u8, "a,b,c", ",");
while (parts.next()) |part| {
    std.debug.print("{s}\n", .{part});
}

结构体(Struct)

基本结构体

结构体是 Zig 中最重要的自定义类型,支持方法(通过将函数与类型关联)、默认字段值、和 packed 内存布局。

// 基本结构体定义
const Point = struct {
    x: f64,
    y: f64,
    z: f64 = 0.0,  // 带默认值的字段

    // 方法(第一个参数为 self)
    pub fn distanceTo(self: Point, other: Point) f64 {
        const dx = self.x - other.x;
        const dy = self.y - other.y;
        const dz = self.z - other.z;
        return std.math.sqrt(dx * dx + dy * dy + dz * dz);
    }

    // 修改方法(self 为指针)
    pub fn translate(self: *Point, dx: f64, dy: f64) void {
        self.x += dx;
        self.y += dy;
    }
};

// 创建结构体实例
const p1 = Point{ .x = 1.0, .y = 2.0 };         // z 使用默认值 0.0
var p2 = Point{ .x = 4.0, .y = 6.0, .z = 3.0 };

// 调用方法
const dist = p1.distanceTo(p2);  // 编译器自动传入 self
p2.translate(1.0, -1.0);       // p2 是 var,可修改

// 结构体更新语法
const p3 = Point{ .x = p1.x, .y = p1.y };  // 复制 p1 的 x/y
_ = dist;
_ = p3;

packed struct 与 extern struct

// packed struct:按位紧密排列,用于硬件寄存器、网络协议头
const IpHeader = packed struct {
    version: u4,       // 4 位
    ihl: u4,           // 4 位
    dscp: u6,          // 6 位
    ecn: u2,           // 2 位
    total_length: u16, // 16 位
    // 共 32 位 = 4 字节
};

const hdr = IpHeader{
    .version = 4,
    .ihl = 5,
    .dscp = 0,
    .ecn = 0,
    .total_length = 1500,
};
std.debug.print("IP header size: {d} bytes\n", .{@sizeOf(IpHeader)});  // 4

// extern struct:与 C ABI 兼容的内存布局
const CPoint = extern struct {
    x: c_int,
    y: c_int,
};

枚举(Enum)

// 基本枚举
const Color = enum { red, green, blue };

const c = Color.red;
// 枚举比较
if (c == .red) {
    std.debug.print("red!\n", .{});
}

// 带整数标签的枚举
const HttpStatus = enum(u16) {
    ok = 200,
    not_found = 404,
    internal_error = 500,

    // 枚举方法
    pub fn isSuccess(self: HttpStatus) bool {
        return @intFromEnum(self) < 300;
    }
};

const status = HttpStatus.ok;
std.debug.print("success: {}\n", .{status.isSuccess()});  // true

// 整数与枚举转换
const code: u16 = @intFromEnum(HttpStatus.not_found);  // 404
const from_int = @as(HttpStatus, @enumFromInt(200));         // HttpStatus.ok
_ = code;
_ = from_int;

// switch 枚举(编译器检查穷举性,添加值时需同步更新 switch)
const msg = switch (status) {
    .ok => "OK",
    .not_found => "Not Found",
    .internal_error => "Internal Server Error",
};

联合体(Union)

// 普通联合体(同一时刻只有一个字段有效,类似 C union)
const Number = union {
    int: i64,
    float: f64,
};

// 标签联合体(tagged union):带枚举标签,安全访问
const Value = union(enum) {
    int: i64,
    float: f64,
    string: []const u8,
    boolean: bool,

    pub fn print(self: Value) void {
        switch (self) {
            .int => |v| std.debug.print("int: {d}\n", .{v}),
            .float => |v| std.debug.print("float: {d}\n", .{v}),
            .string => |v| std.debug.print("str: {s}\n", .{v}),
            .boolean => |v| std.debug.print("bool: {}\n", .{v}),
        }
    }
};

const v1 = Value{ .int = 42 };
const v2 = Value{ .string = "hello" };
v1.print();   // int: 42
v2.print();   // str: hello

// 错误:访问错误的字段会在 Debug 模式 panic
// const wrong = v1.float;  // PANIC: inactive union field

本章小结

本章核心要点