变量声明
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 中,声明了但未使用的变量会导致编译错误(不是警告)。如果确实不需要某个值,必须用 _ = value 显式丢弃,这强制程序员明确表达意图。
整数类型
定宽整数
Zig 提供了从 1 位到 65535 位的任意位宽整数类型。最常用的如下:
// 整数字面量
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 是任意精度整数,在编译期进行精确计算。只有当它被赋值给具体运行时类型时,才会检查是否溢出。这意味着 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;
- 数组:长度是类型的一部分(编译期已知),存储在栈上,拷贝时复制所有元素
- 切片:长度是运行时值(胖指针的 len 字段),不拥有数据,拷贝只复制指针和长度
- 函数参数应优先使用切片
[]const T,而非数组引用*const [N]T,这样更灵活(数组可以隐式强制为切片)
字符串处理
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 的字符串是字节序列,没有内置 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
本章小结
- const vs var:默认用 const(不可变),var 只在真正需要修改时使用;未使用的变量是编译错误,必须用
_ = value显式丢弃。 - 类型系统非常严格:所有类型转换必须显式进行(
@as、@truncate、@intFromEnum等),没有任何隐式转换;整数溢出在 Debug 模式检测,wrapping 运算用+%/-%/*%。 - 数组长度是类型的一部分:
[5]u8和[6]u8是不同类型;函数参数优先用切片[]const T(胖指针:指针+长度),更灵活且数组可自动强制转换。 - 字符串是 []const u8:没有内置 String 类型;字符串比较用
std.mem.eql而非==;动态构建用std.fmt.allocPrint(需手动 free)。 - 控制流都是表达式:if/while/switch 都可以返回值,配合 break 实现复杂的值计算;switch 强制穷举所有情况。
- tagged union 是安全的 C union 替代:带枚举标签的联合体在 Debug 模式防止访问错误字段;与 switch 配合是 Zig 中实现"变体类型"的标准模式。