Zig 基础语法详解
Zig 是一门现代化的系统级编程语言,由 Andrew Kelley 创建。它的设计理念强调显式优于隐式、编译时代码执行、零成本抽象以及与 C 语言的完美互操作性。本文将全面介绍 Zig 的基础语法。
1. 变量与常量
1.1 变量声明
Zig 使用 var 声明可变变量,使用 const 声明常量:
const std = @import("std");
pub fn main() void {
// 可变变量
var x: i32 = 10;
x = 20; // 可以修改
// 常量
const y: i32 = 30;
// y = 40; // 错误:不能修改常量
// 类型推断
var z = 50; // 自动推断为 comptime_int
}1.2 基本数据类型
Zig 提供了丰富的整数和浮点类型:
// 有符号整数
const a: i8 = -128; // 8位
const b: i16 = 1000; // 16位
const c: i32 = 100000; // 32位
const d: i64 = 1000000; // 64位
// 无符号整数
const e: u8 = 255;
const f: u32 = 4000000000;
// 浮点数
const g: f32 = 3.14;
const h: f64 = 3.14159265359;
// 布尔类型
const flag: bool = true;
// 字符类型(UTF-8 码点)
const ch: u21 = '中';1.3 未定义值
Zig 要求显式处理未初始化的变量:
var x: i32 = undefined; // 显式标记为未定义
x = 10; // 使用前必须初始化2. 数组与切片
2.1 数组
数组是固定大小的同类型元素集合:
// 固定大小数组
const arr = [5]i32{ 1, 2, 3, 4, 5 };
// 使用 _ 让编译器推断大小
const arr2 = [_]i32{ 1, 2, 3 };
// 重复初始化
const zeros = [_]i32{0} ** 10; // 10个0
// 获取数组长度
const len = arr.len; // 52.2 切片
切片是对数组的引用,包含指针和长度:
var arr = [_]i32{ 1, 2, 3, 4, 5 };
// 创建切片
const slice = arr[1..4]; // 元素 2, 3, 4
// 切片可以修改原数组
slice[0] = 100; // arr 变为 { 1, 100, 3, 4, 5 }
// 字符串是 u8 切片的常量
const str: []const u8 = "Hello, Zig!";2.3 多维数组
// 2D 数组
const matrix = [3][3]i32{
[_]i32{ 1, 2, 3 },
[_]i32{ 4, 5, 6 },
[_]i32{ 7, 8, 9 },
};3. 控制流
3.1 条件语句
const x: i32 = 10;
if (x > 5) {
std.debug.print("x is greater than 5\n", .{});
} else if (x == 5) {
std.debug.print("x equals 5\n", .{});
} else {
std.debug.print("x is less than 5\n", .{});
}
// if 作为表达式
const y = if (x > 5) 100 else 0;
// 可选类型的 if
const maybe_value: ?i32 = 42;
if (maybe_value) |value| {
std.debug.print("Value: {}\n", .{value});
} else {
std.debug.print("No value\n", .{});
}3.2 循环
// while 循环
var i: i32 = 0;
while (i < 5) : (i += 1) {
std.debug.print("{} ", .{i});
}
// 带 continue 表达式的 while
var j: i32 = 0;
while (j < 10) : (j += 2) {
if (j == 4) continue;
std.debug.print("{} ", .{j});
}
// for 循环(用于数组、切片)
const arr = [_]i32{ 1, 2, 3, 4, 5 };
for (arr) |elem| {
std.debug.print("{} ", .{elem});
}
// 带索引的 for
for (arr, 0..) |elem, idx| {
std.debug.print("[{}] = {}\n", .{ idx, elem });
}
// 带标签的循环(用于 break/continue 到外层循环)
outer: for (0..3) |a| {
for (0..3) |b| {
if (a + b == 3) break :outer;
}
}3.3 switch 语句
Zig 的 switch 是详尽的(exhaustive):
const number = 5;
const result = switch (number) {
1 => "one",
2 => "two",
3 => "three",
4...10 => "between 4 and 10",
else => "other",
};
// switch 与枚举
const Color = enum { red, green, blue };
const color = Color.red;
const color_name = switch (color) {
.red => "Red",
.green => "Green",
.blue => "Blue",
};4. 函数
4.1 函数定义
// 基本函数
fn add(a: i32, b: i32) i32 {
return a + b;
}
// 无返回值(void)
fn printHello() void {
std.debug.print("Hello!\n", .{});
}
// 多返回值(通过结构体或元组)
fn divide(dividend: i32, divisor: i32) struct { quotient: i32, remainder: i32 } {
return .{
.quotient = @divTrunc(dividend, divisor),
.remainder = @rem(dividend, divisor),
};
}4.2 递归函数
fn factorial(n: u32) u32 {
if (n <= 1) return 1;
return n * factorial(n - 1);
}4.3 函数指针
const MathOp = fn (a: i32, b: i32) i32;
fn calculate(a: i32, b: i32, op: MathOp) i32 {
return op(a, b);
}
fn multiply(x: i32, y: i32) i32 {
return x * y;
}
// 使用
const result = calculate(5, 3, multiply); // 155. 结构体
5.1 基本结构体
const Point = struct {
x: f32,
y: f32,
// 方法
pub fn distance(self: Point, other: Point) f32 {
const dx = self.x - other.x;
const dy = self.y - other.y;
return std.math.sqrt(dx * dx + dy * dy);
}
};
// 创建实例
const p1 = Point{ .x = 0, .y = 0 };
const p2 = Point{ .x = 3, .y = 4 };
const dist = p1.distance(p2); // 5.05.2 默认字段值
const Config = struct {
host: []const u8 = "localhost",
port: u16 = 8080,
debug: bool = false,
};
const default_config = Config{}; // 使用所有默认值
const custom_config = Config{ .port = 3000 }; // 覆盖特定字段5.3 自引用结构体
const Node = struct {
value: i32,
next: ?*Node = null,
};6. 联合体与枚举
6.1 枚举
const Status = enum {
pending,
running,
completed,
failed,
// 枚举方法
pub fn isDone(self: Status) bool {
return self == .completed or self == .failed;
}
};
// 带关联值的枚举(类似 Rust 的枚举)
const Message = union(enum) {
text: []const u8,
number: i32,
quit: void,
};6.2 联合体
const Value = union {
int: i32,
float: f64,
boolean: bool,
};
var v = Value{ .int = 42 };
// v.float = 3.14; // 运行时错误:当前激活的字段是 int
// 标签联合体(安全的联合体)
const TaggedValue = union(enum) {
int: i32,
float: f64,
boolean: bool,
};7. 错误处理
Zig 使用错误联合类型(Error Union Types)进行错误处理:
7.1 定义错误
const FileError = error{
NotFound,
PermissionDenied,
OutOfMemory,
};7.2 错误联合类型
// 可能返回 i32 或 FileError
fn mightFail(condition: bool) FileError!i32 {
if (!condition) {
return FileError.NotFound;
}
return 42;
}
// 捕获错误
const result = mightFail(true) catch |err| {
std.debug.print("Error: {}\n", .{err});
return;
};7.3 try 和 catch
// try:如果出错则立即返回错误
const value = try mightFail(true);
// catch:处理错误
const value_or_default = mightFail(false) catch 0;
// catch 带错误信息
const value2 = mightFail(false) catch |err| blk: {
std.debug.print("Got error: {}\n", .{err});
break :blk -1;
};7.4 if-else 处理错误
if (mightFail(true)) |value| {
std.debug.print("Success: {}\n", .{value});
} else |err| {
std.debug.print("Failed: {}\n", .{err});
}8. 可选类型(Optionals)
Zig 使用 ?T 表示类型 T 的可选值:
// 可选类型
const maybe_number: ?i32 = 42;
const no_number: ?i32 = null;
// if 解构
if (maybe_number) |n| {
std.debug.print("Number: {}\n", .{n});
}
// while 解构(常用于迭代器模式)
var iter = RangeIterator{ .current = 0, .end = 5 };
while (iter.next()) |value| {
std.debug.print("{} ", .{value});
}
// orelse 提供默认值
const value = maybe_number orelse 0;
// .? 操作符(断言非空,不安全)
const definite = maybe_number.?;9. 编译时计算(comptime)
Zig 的强大特性之一是在编译时执行代码:
9.1 comptime 关键字
// 编译时变量
comptime var count: i32 = 0;
// 编译时函数参数
fn compileTimeFunction(comptime T: type, comptime size: usize) type {
return struct {
data: [size]T,
pub fn get(idx: usize) T {
return data[idx];
}
};
}
// 使用
const IntArray10 = compileTimeFunction(i32, 10);
var my_array: IntArray10 = undefined;9.2 类型作为参数
fn max(comptime T: type, a: T, b: T) T {
return if (a > b) a else b;
}
const m1 = max(i32, 5, 10); // i32 版本
const m2 = max(f64, 3.14, 2.71); // f64 版本9.3 编译时反射
const std = @import("std");
fn printStructInfo(comptime T: type) void {
const info = @typeInfo(T);
std.debug.print("Type: {s}\n", .{@typeName(T)});
if (info == .Struct) {
inline for (info.Struct.fields) |field| {
std.debug.print(" Field: {s} ({s})\n", .{
field.name,
@typeName(field.type),
});
}
}
}10. 内存管理
Zig 不提供垃圾回收,需要显式管理内存:
10.1 分配器模式
const std = @import("std");
pub fn main() !void {
// 获取 GPA(通用分配器)
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// 动态分配数组
const arr = try allocator.alloc(i32, 10);
defer allocator.free(arr);
// 使用数组
for (arr, 0..) |*item, i| {
item.* = @intCast(i);
}
}10.2 固定缓冲区分配器
var buffer: [1024]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buffer);
const allocator = fba.allocator();
// 在小缓冲区上分配,无堆分配
const slice = try allocator.alloc(u8, 100);10.3 Arena 分配器
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
// 所有内存一起释放
_ = try allocator.alloc(u8, 100);
_ = try allocator.alloc(u8, 200);11. 指针
11.1 基本指针
var x: i32 = 42;
const ptr: *i32 = &x; // 单一项指针
ptr.* = 100; // 解引用
// 可选指针
const maybe_ptr: ?*i32 = &x;11.2 切片指针
var arr = [_]i32{ 1, 2, 3, 4, 5 };
const slice: []i32 = &arr; // 切片
const const_slice: []const i32 = &arr; // 常量切片
// 哨兵终止切片(以特定值结尾)
const str: [:0]const u8 = "hello"; // 以 0 结尾11.3 多指针
// [*]T - 未知长度的多指针(类似 C 指针)
// [*c]T - C 风格的指针
// 示例:C 互操作
extern fn c_function(ptr: [*c]const u8) void;12. 与 C 语言互操作
Zig 可以无缝与 C 代码集成:
12.1 导入 C 头文件
const c = @cImport({
@cInclude("stdio.h");
@cInclude("stdlib.h");
});
pub fn main() void {
_ = c.printf("Hello from C!\n");
}12.2 导出 Zig 函数给 C
export fn zig_add(a: i32, b: i32) i32 {
return a + b;
}
// 使用 C 调用约定
export "C" fn zig_multiply(a: i32, b: i32) i32 {
return a * b;
}12.3 类型映射
// C 类型映射
const c_int: c_int = 42;
const c_char: c_char = 'a';
const c_void: c_void = undefined;
// C 字符串
const c_str: [*c]const u8 = "hello";
const zig_str: []const u8 = std.mem.span(c_str); // 转换为 Zig 切片13. 泛型编程
使用 comptime 实现泛型:
fn Stack(comptime T: type) type {
return struct {
const Self = @This();
items: []T,
capacity: usize,
len: usize = 0,
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator, capacity: usize) !Self {
const items = try allocator.alloc(T, capacity);
return Self{
.items = items,
.capacity = capacity,
.allocator = allocator,
};
}
pub fn deinit(self: *Self) void {
self.allocator.free(self.items);
}
pub fn push(self: *Self, item: T) !void {
if (self.len >= self.capacity) return error.OutOfCapacity;
self.items[self.len] = item;
self.len += 1;
}
pub fn pop(self: *Self) ?T {
if (self.len == 0) return null;
self.len -= 1;
return self.items[self.len];
}
};
}
// 使用
const IntStack = Stack(i32);
var stack = try IntStack.init(allocator, 10);
defer stack.deinit();14. 构建系统
Zig 自带构建系统,使用 build.zig:
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// 可执行文件
const exe = b.addExecutable(.{
.name = "myapp",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
b.installArtifact(exe);
// 运行命令
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
// 测试
const unit_tests = b.addTest(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
const run_unit_tests = b.addRunArtifact(unit_tests);
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_unit_tests.step);
}15. 测试
Zig 内置测试框架:
const std = @import("std");
const testing = std.testing;
fn add(a: i32, b: i32) i32 {
return a + b;
}
// 测试函数
test "basic addition" {
try testing.expect(add(2, 3) == 5);
try testing.expectEqual(@as(i32, 5), add(2, 3));
}
test "expect error" {
const result = mightFail(false);
try testing.expectError(error.NotFound, result);
}
// 在文档中测试
/// 计算阶乘
/// ```
/// const fact = factorial(5);
/// try std.testing.expect(fact == 120);
/// ```
fn factorial(n: u32) u32 {
if (n <= 1) return 1;
return n * factorial(n - 1);
}运行测试:
zig test file.zig16. 包管理
Zig 使用 build.zig.zon 管理依赖:
.{
.name = "myproject",
.version = "0.1.0",
.dependencies = .{
.zap = .{
.url = "https://github.com/zigzap/zap/archive/refs/tags/v0.1.0.tar.gz",
.hash = "1220...",
},
},
}17. 最佳实践
- 错误处理:始终使用错误联合类型而非返回错误码
- 内存管理:使用 defer 确保资源释放
- 编译时计算:利用 comptime 进行泛型和元编程
- 显式控制:避免隐式行为,所有操作都应清晰可见
- 与 C 互操作:使用 Zig 逐步替换 C 代码
18. 总结
Zig 是一门设计精良的系统编程语言,主要特点包括:
- 显式优于隐式:没有隐藏的控制流或内存分配
- 编译时代码执行:强大的元编程能力
- 零成本抽象:高性能的同时保持代码清晰
- C 语言互操作:无缝集成现有 C 代码
- 安全第一:通过编译时检查和显式错误处理避免运行时错误
Zig 适合系统编程、嵌入式开发、游戏引擎、编译器等需要高性能和精细控制的场景。
