【Zig 入门】对象、数据类型与代码块
Zig 的哲学
Zig 是一门现代、底层的通用编程语言,被许多程序员视为 C 语言的现代化改进版本。Zig 的核心理念可以用"少即是多"来概括——它不是通过不断增加新特性来变得现代,而是通过移除 C 和 C++ 中令人困扰的特性来实现改进。
正如 Zig 官网所言:
“Focus on debugging your application rather than debugging your programming language knowledge”
(专注于调试你的应用程序,而不是调试你对编程语言的知识)
Zig 强调显式优于隐式:
- 没有预处理器宏,你写的代码就是实际编译的代码
- 没有隐藏的控制流
- 没有标准库函数在背后偷偷进行内存分配
这种设计哲学使得 Zig 代码更易读、更易调试,同时在边缘情况下表现出更一致和稳健的行为。
在 Zig 中创建对象
Zig 中最重要的两种类型便是可变(mutable)与不可变(immutable)。可变类型允许你修改其值,而不可变类型则不允许。不可变类型通常用于存储常量数据,而可变类型则用于存储变量数据。
1 | const age = 24; |
使用 const 声明的变量是不可变的,而使用 var 声明的变量是可变的。
变量必须初始化
在 Zig 中,所有变量默认都需要有初始化值。如果你暂时不想给变量赋具体值,可以使用 undefined 来初始化:
1 | var age: u8 = undefined; |
但需要注意,undefined 是一个特殊的值,表示变量尚未被初始化。应当在代码中尽量减少使用 undefined,因为这会降低代码的可读性和安全性。
类型推断
Zig 支持类型推断,编译器可以根据初始值自动推断变量类型:
1 | const inferred = 24; // 编译器推断为 comptime_int |
特别注意:Zig 的严格检查
Zig 编译器有两个非常严格的检查规则:
1. 可变变量必须被修改
如果你创建了一个可变变量(使用 var),但没有对其进行修改,编译时会发生错误:
1 | // 错误:可变变量未被修改 |
正确做法:如果不需要修改,应该使用 const:
1 | const x: i32 = 10; |
2. 不允许未使用的变量
如果你创建了一个变量但是没有使用,编译时也会报错:
1 | const unused = 15; // 错误:未使用的局部变量 |
解决方案是使用下划线语法来显式忽略:
1 | const age = 15; |
这种设计强制程序员保持代码整洁,避免遗留无用的变量。
代码块与作用域
在 Zig 中,**代码块(block)**是用花括号 {} 包围的代码区域。代码块可以包含其他代码块,形成嵌套结构。
1 | const std = @import("std"); |
代码块作为表达式
Zig 的一个强大特性是代码块可以作为表达式使用,并且可以有返回值:
1 | const result = blk: { |
使用 blk: 为代码块添加标签,然后用 break :标签名 返回值 的形式返回结果。这在需要根据条件计算初始值时非常有用:
1 | const value = blk: { |
原生数据类型
Zig 提供了丰富的原生数据类型:
整数类型
无符号整数(Unsigned integers):只能表示非负数
u8- 8 位整数(0 到 255)u16- 16 位整数(0 到 65,535)u32- 32 位整数(0 到 4,294,967,295)u64- 64 位整数u128- 128 位整数
有符号整数(Signed integers):可以表示负数
i8- 8 位整数(-128 到 127)i16- 16 位整数(-32,768 到 32,767)i32- 32 位整数i64- 64 位整数i128- 128 位整数
浮点数类型
f16- 16 位浮点数f32- 32 位浮点数(单精度)f64- 64 位浮点数(双精度)f128- 128 位浮点数
其他基本类型
bool- 布尔类型,true或falsevoid- 空类型,表示没有值noreturn- 表示函数不会返回(如无限循环或 panic)
C ABI 兼容类型
Zig 提供了与 C 语言 ABI 兼容的类型,方便与 C 代码交互:
c_char,c_short,c_ushortc_int,c_uint,c_long,c_ulongc_longlong,c_ulonglongc_float,c_double
指针大小整数
isize- 有符号指针大小整数usize- 无符号指针大小整数(常用于数组索引和内存大小)
查阅 Zig 官方文档 获取完整的类型表格。
数组
数组是一种存储相同类型元素的数据结构。Zig 数组的关键特点是:
数组大小必须在编译时确定,一旦创建就不能改变。
这与 C 语言的设计一致。
创建数组
1 | // 显式指定大小 |
访问数组元素
Zig 使用零索引,即第一个元素的索引是 0:
1 | const std = @import("std"); |
数组切片
Zig 支持类似 Python 的切片语法,可以创建数组的视图(不复制数据):
1 | const ns = [4]u8{48, 24, 12, 6}; |
切片可以看作是一对数据:指向数据的指针
[*]T和元素数量usize。
数组运算符
Zig 提供了两个强大的数组运算符,只能在编译时确定长度的情况下使用:
++ 数组连接
将两个数组拼接成新数组:
1 | const a = [_]u8{1, 2, 3}; |
** 数组重复
将数组重复指定次数:
1 | const a = [_]u8{1, 2}; |
编译时 vs 运行时
理解 Zig 中**编译时(comptime)与运行时(runtime)**的区别非常重要:
| 特性 | 数组 | 切片 |
|---|---|---|
| 大小 | 编译时确定 | 运行时确定 |
| 类型 | [N]T |
[]T |
| 存储位置 | 栈或全局 | 指向现有数据 |
数组大小必须在编译时确定,这意味着你不能在运行时动态创建数组。但你可以使用切片来处理运行时才知道大小的数据:
1 | fn printSlice(data: []const u8) void { |
Ziglings 练习
接下来继续我们的 Ziglings 之旅,这次从第 2 个练习开始。
Exercise 002: 导入标准库
1 | > zig build |
解答:需要正确导入 std 模块:
1 | const std = @import("std"); |
Exercise 003: 数据类型
这个练习深入理解数据类型,包括:
- 使用
var声明可变变量 - 理解
u8、u64、i8等类型的取值范围
1 | const std = @import("std"); |
Exercise 004: 数组索引
涉及数组索引和可变变量的修改:
1 | const std = @import("std"); |
Exercise 005: 数组运算符
练习使用 ++ 和 ** 运算符:
1 | const std = @import("std"); |
通过本文,我们深入了解了 Zig 中变量声明、数据类型、代码块和数组的核心概念。Zig 的严格编译检查虽然初看起来有些繁琐,但它们帮助我们在编译期就发现潜在问题,从而编写出更健壮、更可靠的代码。下一篇文章我们将继续探索 Zig 的控制流和函数。