首页
文章
分类
标签
系列
关于
Zig_基本类型
发布于: 2026-8-9 16:54:43   更新于: 2026-8-9 16:54:43   收录于: Zig , 编程语言 , 系统编程
文章字数: 5563   阅读时间: 12 分钟   阅读量:

Zig 学习笔记(v0.16 入门篇)—— 基本类型

适用环境:Windows 11 + Zig 0.16

笔记风格:零基础友好,通俗解释 + 精确概念 + 避坑指南

一、概述

在 Zig 中,一切数据都有明确的类型。Zig 是一门静态类型语言——变量的类型在编译时就已确定,不会在运行时改变。

Zig 类型系统的特点

特点说明
静态类型所有变量的类型在编译期已知
强类型不同类型之间不会自动转换,必须显式转换
类型推断编译器可根据初始值自动推断类型
编译期反射可在编译时检查和操作类型信息

基本类型分类

Zig 基本类型
├── 标量类型(Scalar)—— 单一值
│   ├── 整数(Integer):i8, u8, i32, u32, ...
│   ├── 浮点数(Float):f16, f32, f64, f128
│   ├── 布尔值(Boolean):bool
│   └── 字符(Character):Unicode 码位字面量
├── 复合类型(Compound)
│   ├── 数组(Array):固定长度
│   ├── 切片(Slice):动态长度视图
│   └── 向量(Vector):SIMD 并行计算
├── 特殊类型
│   ├── 可选类型(Optional):?T = null 或 T
│   ├── 错误联合(Error Union):!T = error 或 T
│   ├── void:无值
│   └── 零大小类型(Zero-Sized Types)
└── 类型间的类型
    ├── comptime_int / comptime_float
    ├── type(类型本身也是值)
    └── anytype(编译期泛型参数)

二、整数类型(Integer)

2.1 固定宽度整数

Zig 的整数类型划分非常详细,位宽明确,没有模糊不清的 intlong

类型对应 C 类型描述
i8int8_t有符号 8 位整数(-128 ~ 127)
u8uint8_t无符号 8 位整数(0 ~ 255)
i16int16_t有符号 16 位整数
u16uint16_t无符号 16 位整数
i32int32_t有符号 32 位整数
u32uint32_t无符号 32 位整数
i64int64_t有符号 64 位整数
u64uint64_t无符号 64 位整数
i128__int128有符号 128 位整数
u128unsigned __int128无符号 128 位整数
1
2
3
4
const a: i8 = -128;      // ✅ 最小值
const b: u8 = 255;       // ✅ 最大值
const c: i32 = 42;       // ✅
// const d: u8 = 256;    // ❌ 编译错误:256 超出 u8 范围(0~255)

2.2 指针大小整数

isizeusize 的大小取决于目标 CPU 架构:32 位系统上是 32 位,64 位系统上是 64 位。

类型描述
isize有符号指针大小的整数
usize无符号指针大小的整数,常用于数组索引和长度
1
2
const idx: usize = 10;      // 数组索引通常用 usize
const ptr_diff: isize = -1; // 指针差值可能为负数

2.3 任意位宽整数

Zig 支持任意位宽的整数,在 u(无符号)或 i(有符号)后直接加数字即可:

1
2
const seven_bit: i7 = 64;      // 有符号 7 位整数(-64 ~ 63)
const twelve_bit: u12 = 4095;  // 无符号 12 位整数(0 ~ 4095)

⚠️ 整数类型允许的最大位宽为 65535

2.4 整数字面量(Literals)

整数字面量默认类型为 comptime_int,可以使用多种进制书写:

进制示例说明
十进制98222默认
十六进制0xff0x 开头
八进制0o7550o 开头
二进制0b111100000b 开头

数字中的下划线可作为视觉分隔符,不影响数值:

1
2
3
4
const one_billion = 1_000_000_000;   // 10 亿
const binary_mask = 0b1_1111_1111;   // 二进制掩码
const permissions = 0o7_5_5;         // 权限位
const big_address = 0xFF80_0000_0000_0000;

2.5 ⚠️ 整数溢出与除零:构建模式决定行为

除零检测

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 编译期除零 → 直接报编译错误
comptime {
    const a: i32 = 1;
    const b: i32 = 0;
    const c = a / b;  // ❌ 编译错误:division by zero
}

// 运行时除零 → 行为由构建模式决定(见下表)
pub fn main() void {
    var a: u32 = 1;
    var b: u32 = 0;
    var c = a / b;    // 行为取决于构建模式
}

整数溢出行为 vs 构建模式

构建模式整数溢出 / 除零说明
DebugPanic(程序崩溃并报错)开发时保护你,帮你及早发现问题
ReleaseSafePanic(程序崩溃并报错)保持安全检查,适合生产环境
ReleaseFast未定义行为(UB)追求极致性能,不再检查
ReleaseSmall未定义行为(UB)追求最小体积,不再检查

💀 铁律:永远不要依赖溢出或除法行为来设计程序逻辑。如果需要“回绕”效果,请使用 @addWithOverflow 等显式操作。

1
2
3
4
5
// 如果需要安全的溢出处理:
const result = @addWithOverflow(u8, 255, 1);
if (result[1] != 0) {
    // 溢出发生!
}

三、浮点数类型(Float)

Zig 支持 IEEE 754 标准的浮点数:

类型精度描述
f16半精度16 位浮点数
f32单精度32 位浮点数
f64双精度64 位浮点数
f128四倍精度128 位浮点数
1
2
3
const pi: f32 = 3.14159;
const e: f64 = 2.718281828459045;
const small: f16 = 1.0;

浮点数字面量的默认类型是 comptime_float,内部是 f128

⚠️ 注意comptime_float 不能隐式转换为整数,即使它保存的是整数值(如 3.0)。

1
2
3
4
5
6
7
const x = 3.14;        // 类型:comptime_float
// const y: i32 = 3.0; // ❌ 编译错误:comptime_float 不能转换为整数

// ✅ 正确做法:显式转换
const z: i32 = @intFromFloat(x);
// ✅ 或者直接在字面量阶段指定
const w: i32 = 3;

四、布尔类型(Boolean)

一句话bool 类型只有两个值:truefalse

1
2
3
4
5
6
const is_ready: bool = true;
const is_done: bool = false;

if (is_ready) {
    std.debug.print("准备好了!\n", .{});
}

布尔运算

1
2
3
4
5
6
const a = true;
const b = false;

const and_result = a and b;   // false
const or_result = a or b;     // true
const not_result = !a;        // false

⚠️ 特别警示bool 类型在内存中必须是 0 或 1。如果通过 undefined 或不当的指针操作让 bool 变成其他值,读取时会触发 CPU 异常(SigTrap)!参见上一章笔记第 4.4 节。

五、字符与字符串

5.1 Unicode 码位字面量

Zig 没有专门的 char 类型。最接近的是 Unicode 码位字面量——用单引号包围一个 Unicode 字符,得到的是一个 comptime_int 类型的值,表示该字符的 Unicode 码位:

1
2
3
4
5
6
const ch_zh = '我';        // 类型:comptime_int,值:6211 (U+6211)
const ch_en = 'A';         // 类型:comptime_int,值:65 (U+0041)

// 格式化输出
std.debug.print("{0u} = {0x}\n", .{ch_zh});  // 我 = 6211
std.debug.print("{0u} = {0c} = {0x}\n", .{ch_en});  // 65 = A = 41

⚠️ 注意:某些看似“单个”的字符(如 emoji 🇨🇳、👨‍👩‍👦)实际上由多个码位组合而成,不能写成单个码位字面量。

5.2 字符串字面量(Sentinel 终结符)

字符串字面量用双引号包围,存储的是 UTF-8 编码的字节序列,以 NUL(\0 结尾:

1
2
3
const bytes = "Hello, 世界!";
std.debug.print("{}\n", .{@TypeOf(bytes)});  // *const [16:0]u8
std.debug.print("{d}\n", .{bytes.len});      // 16(含结尾的 NUL)

💡 *const [N:0]u8:0 称为 Sentinel(哨兵),代表末尾有一个值为 0 的字节。这个哨兵让字符串可以直接传给 C 函数而不需要额外复制。

隐式转换规则

1
2
3
4
5
const bytes: *const [5:0]u8 = "Hello";
const slice: []const u8 = bytes;  // ✅ 字符串 → 切片(丢失哨兵信息)

// ❌ 普通切片不能隐式转回 Sentinel 指针
// const ptr: *const [5:0]u8 = slice;  // 编译错误

一句话记忆:字符串字面量是“自带 NUL 终结符的只读数组指针”,而 []const u8 是“单纯的字节视图”。

5.3 C 语言交互:c_char

Zig 提供了 c_char 类型,对应 C 语言的 char 类型,仅在需要与 C 交互时使用

⚠️ 跨平台注意c_char 的符号性(是有符号还是无符号)取决于目标平台(ABI)。在 Windows 上通常是 u8,在 Linux/macOS 上通常是 i8。编写跨平台 C 库绑定时,请始终使用 c_char 而不是 u8i8

六、可选类型(Optional)

一句话?T 表示“这个值要么是 T 类型,要么是 null”。

6.1 为什么需要可选类型?

null(空引用)是许多运行时异常的根源。Zig 通过可选类型,在类型系统中区分“可能有值”和“一定有值”,让编译器帮你检查。

6.2 基本用法

1
2
3
4
5
6
// 普通整数:一定有一个值
const normal: i32 = 1234;

// 可选整数:可能没有值(null)
const optional: ?i32 = 5678;   // ✅ 有值
const optional_null: ?i32 = null;  // ✅ 无值

⚠️ Zig 保证 null 不会被赋值给非可选类型的变量。

6.3 解构可选类型

方式一:orelse —— 提供默认值(最推荐)

1
const value = optional orelse 0;  // 如果有值则取值,否则用 0

推荐理由orelse 是专门为“有值取值,无值取默认”这个场景设计的,代码最简短、意图最清晰。

方式二:if 语句解构 —— 只在有值时执行

1
2
3
4
5
6
if (optional) |val| {
    // 这里 val 是可靠的非空值
    std.debug.print("值是:{}\n", .{val});
} else {
    std.debug.print("没有值\n", .{});
}

方式三:if 语句 + else 的变体(不推荐用于简单默认值)

1
2
// ⚠️ 过于冗长,不如 orelse 简洁
const value = if (optional) |val| val else 0;

七、错误联合类型(Error Union)

一句话!T 表示“这个操作要么成功返回 T 类型的值,要么返回一个错误”。

7.1 定义错误

使用 error 关键字定义错误集合:

1
2
3
4
5
const MyError = error{
    OutOfMemory,
    InvalidInput,
    FileNotFound,
};

7.2 返回错误联合

1
2
3
4
5
fn readFile(path: []const u8) ![]u8 {
    if (path.len == 0) return error.InvalidInput;
    // ... 读取文件 ...
    return &[_]u8{};
}

7.3 处理错误联合

方式一:try —— 出错时向上层抛出

1
2
3
4
fn process() !void {
    const data = try readFile("data.txt");  // 出错则 process 也返回错误
    // ... 使用 data ...
}

方式二:catch —— 出错时提供替代值

1
const data = readFile("data.txt") catch &[_]u8{};  // 出错时用空数组

方式三:if 语句解构

1
2
3
4
5
6
if (readFile("data.txt")) |data| {
    // 成功:使用 data
} else |err| {
    // 失败:处理错误 err
    std.debug.print("读取失败:{}\n", .{err});
}

八、数组(Array)

一句话:数组是固定长度的、同类型元素的集合。

8.1 定义数组

1
2
3
4
5
6
7
8
// 显式指定类型和长度
const arr1: [5]i32 = [5]i32{1, 2, 3, 4, 5};

// 使用 [_] 让编译器推断长度
const arr2 = [_]i32{1, 2, 3, 4, 5};  // 长度为 5

// 所有元素相同值
const arr3 = [_]i32{10} ** 5;  // {10, 10, 10, 10, 10}

8.2 访问数组元素

1
2
3
4
5
6
7
8
const arr = [_]i32{10, 20, 30, 40, 50};
const first = arr[0];   // 10
const last = arr[4];    // 50

// 遍历数组
for (arr) |val| {
    std.debug.print("{}\n", .{val});
}

8.3 数组是值类型

数组是值类型——赋值或传参时会复制整个数组:

1
2
3
4
const a = [_]i32{1, 2, 3};
var b = a;  // 复制了整个数组
b[0] = 100;
// a[0] 仍然是 1,不受影响

九、切片(Slice)

一句话:切片是数组的动态视图——它不拥有数据,只是“指向一段连续数据的指针 + 长度”。

9.1 为什么用切片?

数组长度在编译期固定,不够灵活。切片让你可以在运行时决定要操作的数据范围。

9.2 创建切片

从数组创建切片,使用 [start..end] 语法,遵循 “左闭右开” 原则:

1
2
3
var array = [_]i32{ 1, 2, 3, 4 };
const slice: []i32 = array[0..3];  // 取索引 0, 1, 2 → {1, 2, 3}
const all: []i32 = array[0..array.len];  // 全部元素

9.3 切片 vs 数组指针

情况结果类型说明
边界值都是编译期常量*[N]T(数组指针)长度在编译期已知
至少一个边界值是运行时变量[]T(切片)长度在运行时确定
1
2
3
4
5
var array = [_]i32{1, 2, 3, 4};

const slice1 = array[0..2];      // 2 是常量 → *[2]i32(数组指针)
const len: usize = 2;
const slice2 = array[0..len];    // len 是变量 → []i32(切片)

9.4 切片的内部结构

切片是一个 “胖指针” ,包含:

  • ptr:指向数据的多项指针(many-item pointer)
  • len:数据长度
1
2
3
4
5
6
7
var array = [_]i32{1, 2, 3, 4};
var len: usize = 3;
var slice = array[0..len];

std.debug.print("{}\n", .{@TypeOf(slice)});       // []i32
std.debug.print("{}\n", .{@TypeOf(slice.ptr)});   // [*]i32  ← 多项指针
std.debug.print("{}\n", .{@TypeOf(&slice[0])});   // *i32   ← 单项指针

💡 理解 [*]i32 vs *i32

  • [*]i32(many-item pointer):指向多个连续i32,可以执行指针算术(如 ptr + 1),但不携带长度信息——这就是 C 语言里的 int*
  • *i32(single-item pointer):指向单个 i32,不能执行指针算术。
  • slice.ptr 暴露的是底层的多项指针,而 &slice[0] 取的是第一个元素的地址(单项指针)。虽然它们的值(内存地址)相同,但类型语义不同

⚠️ 警告:虽然 slice.ptrslice.len 可以访问和修改,但实践中应避免直接操作它们,否则容易破坏切片内部结构。

十、向量(Vector)

一句话:向量是固定长度、同类型的集合,用于 SIMD(单指令多数据)并行计算,直接映射 CPU 硬件加速指令。

10.1 定义向量

使用 @Vector(N, T) 语法:

1
const v: @Vector(3, i32) = .{10, 20, 30};

10.2 向量运算

向量重载了算术、位运算和比较运算符,所有运算按元素独立执行,自动编译为 SIMD 指令:

1
2
3
4
5
const a: @Vector(4, f32) = .{1.0, 2.0, 3.0, 4.0};
const b: @Vector(4, f32) = .{5.0, 6.0, 7.0, 8.0};

const c = a + b;  // .{6.0, 8.0, 10.0, 12.0}
const d = c * @as(@Vector(4, f32), @splat(2.0));  // .{12.0, 16.0, 20.0, 24.0}

10.3 向量的广播(splat)

@splat 将一个标量扩展为向量:

1
2
const scalar: f32 = 2.0;
const vec = @as(@Vector(4, f32), @splat(scalar));  // .{2.0, 2.0, 2.0, 2.0}

10.4 向量比较

比较结果是一个布尔向量

1
2
const a: @Vector(4, i32) = .{1, 5, 3, 8};
const cond = a > @as(@Vector(4, i32), @splat(2));  // {false, true, true, true}

10.5 💡 使用建议

场景是否适合用 Vector
数值计算、图形处理、批量数据处理(利用 CPU SIMD 加速)适合
通用业务逻辑不适合
长度不固定的数据(长度必须在编译期已知)不适合
需要运行时确定长度的批量数据请使用切片(Slice) + 循环

十一、零大小类型(Zero-Sized Types)

一句话:某些类型在内存中不占用任何空间(0 bit)

11.1 void

void 是最典型的零大小类型,常用于表示“无返回值”:

1
2
3
fn doNothing() void {
    // 不返回任何值
}

void 还可用于初始化泛型实例,例如构建一个集合(Set):

1
2
var set = std.AutoHashMap(i32, void).init(allocator);
// 只存键不存值 → 相当于集合

11.2 其他零大小类型

类型条件
u0 / i00 位整数
数组 / 切片长度为 0,或元素类型是零大小类型
枚举只有一个成员
结构体空结构体,或所有字段都是零大小类型
联合类型仅包含一种可能类型且该类型是零大小类型
1
2
const empty_struct = struct {};  // 大小 = 0 bit
const zero_array: [0]u8 = .{};   // 大小 = 0 bit

十二、类型转换

Zig 是强类型语言,不会自动进行隐式类型转换。你需要显式地进行转换。

12.1 基本转换:@as

1
2
const x: i32 = 42;
const y: u8 = @as(u8, @intCast(x));  // 显式转换并截断

12.2 整数转换

内建函数用途
@intCast在整数类型之间转换(可能截断或符号变化)
@intFromFloat将浮点数转为整数(需配合 @floor/@ceil 等)
@floatFromInt将整数转为浮点数
1
2
3
const a: i32 = 42;
const b: f32 = @floatFromInt(a);     // 整数 → 浮点数
const c: i32 = @intFromFloat(@floor(b));  // 浮点数 → 整数(向下取整)

12.3 指针转换

内建函数用途
@ptrCast在不同指针类型之间转换
@alignCast改变指针的对齐方式

📌 总复习:核心要点速查

知识点一句话总结
整数i8/u8/i32/u32/… 位宽明确;usize 用于索引
任意位宽整数i7u12 等,最大 65535 位
整数溢出Debug/ReleaseSafe 会 panic;ReleaseFast/ReleaseSmall 是 UB
浮点数f16/f32/f64/f128
布尔值bool = true / false,必须为 0 或 1
字符没有 char 类型,用 'A' 表示 Unicode 码位(comptime_int
字符串双引号 "Hello",类型 *const [N:0]u8(带哨兵),UTF-8 编码
c_char仅在 C 交互时使用,符号性依赖目标平台
可选类型?T = nullT,用 orelseif 解构
错误联合!T = errorT,用 trycatch 处理
数组[N]T 固定长度,值类型(赋值会复制)
切片[]T 动态视图,“胖指针”([*]T + len
向量@Vector(N, T),SIMD 并行计算;长度必须编译期已知
零大小类型voidu0、空结构体等,占用 0 bit
类型转换必须显式转换,用 @as@intCast

下一步建议

掌握基本类型后,建议继续学习:

  1. 复合类型进阶:结构体(struct)、枚举(enum)、联合(union
  2. 指针:单项指针(*T)、多项指针([*]T)、切片指针([]T
  3. 控制流ifwhileforswitch
  4. 内存管理:分配器(Allocator)、堆内存、栈内存
  5. 编译期编程comptime、泛型(anytype

💡 调试技巧:如果不确定某个变量的类型,可以用 @compileLog(@TypeOf(x)) 在编译时打印类型信息(会中断编译,仅用于调试)。