Zig 数组和切片

Zig 编程语言中,数组(Arrays)和切片(Slices)是处理序列数据的核心数据结构。Zig 的数组和切片设计简洁、高效,强调类型安全和性能,特别适合系统编程。数组是固定长度的连续内存块,而切片是动态长度的数组视图,依赖运行时信息。以下是对 Zig 数组和切片的中文讲解,涵盖定义、操作、用法、示例代码及注意事项,基于 Zig 0.14.1(截至 2025 年 5 月的稳定版),力求简洁清晰。


1. 数组和切片概述

  • 数组
  • 固定长度,编译时确定大小。
  • 类型为 [N]TN 是长度,T 是元素类型。
  • 存储在连续内存中,适合静态数据。
  • 切片
  • 动态长度,运行时确定,引用数组或内存的一部分。
  • 类型为 []T[]const T,包含指针和长度。
  • 常用于字符串或子数组操作。
  • 特点
  • 类型安全:编译器检查越界访问。
  • 性能优化:支持编译时初始化和内联。
  • 手动内存管理:切片需小心生命周期。

2. 数组

数组是固定长度的连续元素序列,长度在编译时确定。

2.1 定义与初始化

  • 语法[N]T[_]T(自动推断长度)。
  • 初始化:支持显式值、默认值或重复值。

示例

const std = @import("std");

pub fn main() !void {
    // 显式初始化
    const arr1: [3]u8 = [3]u8{1, 2, 3};
    // 自动推断长度
    const arr2 = [_]u8{4, 5, 6};
    // 重复初始化
    const arr3 = [5]u8{0} ** 5; // [0, 0, 0, 0, 0]

    const stdout = std.io.getStdOut().writer();
    try stdout.print("arr1: {any}\n", .{arr1});
    try stdout.print("arr2: {any}\n", .{arr2});
    try stdout.print("arr3: {any}\n", .{arr3});
}

输出

arr1: {1, 2, 3}
arr2: {4, 5, 6}
arr3: {0, 0, 0, 0, 0}

说明

  • [3]u8:指定长度 3 的 u8 数组。
  • [_]u8:根据初始化列表推断长度。
  • {0} ** 5:重复值 0 五次。

2.2 访问与修改

  • 使用索引(arr[i])访问或修改元素。
  • 编译器默认检查越界。

示例

const std = @import("std");

pub fn main() !void {
    var arr = [_]u8{1, 2, 3};
    arr[1] = 10; // 修改索引 1

    const stdout = std.io.getStdOut().writer();
    try stdout.print("arr[1]: {}, 数组: {any}\n", .{arr[1], arr});
    // arr[3] = 4; // 错误:越界
}

输出

arr[1]: 10, 数组: {1, 10, 3}

2.3 数组长度

  • 使用 .len 获取数组长度(编译时常量):
const arr = [_]u8{1, 2, 3};
const len = arr.len; // 3

2.4 编译时数组

使用 comptime 创建编译时数组:

const size = comptime 3 + 2;
const arr: [size]u8 = [5]u8{1, 2, 3, 4, 5};

3. 切片

切片是数组的动态视图,包含指针和长度,适合处理子数组或字符串。

3.1 定义与创建

  • 语法[]T(可变切片)或 []const T(只读切片)。
  • 通过数组或内存区域创建:
  • arr[start..end]:从 startend-1
  • arr[start..]:从 start 到末尾。

示例

const std = @import("std");

pub fn main() !void {
    const arr = [_]u8{1, 2, 3, 4, 5};
    const slice = arr[1..4]; // 包含索引 1,2,3
    const stdout = std.io.getStdOut().writer();

    try stdout.print("切片: {any}, 长度: {}\n", .{slice, slice.len});
}

输出

切片: {2, 3, 4}, 长度: 3

说明

  • slice 类型为 []const u8,因为 arr 是常量。
  • .len 返回切片长度(运行时确定)。

3.2 修改切片

可变切片([]T)允许修改底层数组:

var arr = [_]u8{1, 2, 3, 4, 5};
var slice = arr[1..3]; // 可变切片
slice[0] = 20; // 修改 arr[1]

3.3 字符串切片

Zig 字符串是 []const u8 类型:

const str: []const u8 = "Hello, Zig!";
const substr = str[0..5]; // "Hello"

3.4 动态切片

使用分配器创建动态切片:

const std = @import("std");

pub fn main() !void {
    const allocator = std.heap.page_allocator;
    var buffer = try allocator.alloc(u8, 5);
    defer allocator.free(buffer);

    buffer[0] = 1;
    buffer[1] = 2;
    const slice = buffer[0..3]; // 动态切片
    try std.io.getStdOut().writer().print("动态切片: {any}\n", .{slice});
}

输出

动态切片: {1, 2, 0}

4. 数组与切片的操作

4.1 迭代

使用 for 循环迭代:

for (arr) |item| {
    try std.io.getStdOut().writer().print("项: {}\n", .{item});
}
for (slice, 0..) |item, i| {
    try std.io.getStdOut().writer().print("索引: {}, 项: {}\n", .{i, item});
}

4.2 复制

  • 数组可直接赋值(值复制)。
  • 切片需小心指针共享:
var arr1 = [_]u8{1, 2, 3};
var arr2 = arr1; // 复制数组
arr2[0] = 10; // 不影响 arr1

var slice1 = arr1[0..2];
var slice2 = slice1; // 共享底层数组
slice2[0] = 20; // 修改 arr1[0]

4.3 标准库支持

std.ArrayList 提供动态数组功能:

var list = try std.ArrayList(u8).init(allocator);
defer list.deinit();
try list.append(42);

5. 注意事项

  • 类型安全
  • 数组长度编译时确定,切片长度运行时确定。
  • 越界访问触发编译时或运行时错误:
    zig var arr = [_]u8{1, 2}; // arr[2]; // 错误:越界
  • 内存管理
  • 数组存储在栈或静态内存,自动管理。
  • 切片需注意底层数组的生命周期,使用 defer 释放动态分配内存。
  • 性能
  • 数组适合静态数据,访问效率高。
  • 切片适合动态数据,需避免频繁分配。
  • 调试
  • 使用 std.debug.print 检查数组/切片内容:
    zig std.debug.print("数组: {any}\n", .{arr});
  • 检查切片边界,确保 start <= end <= len
  • 与 C 互操作
  • 数组兼容 C 数组([*]T)。
  • 切片需转换为 C 指针和长度:
    zig const c = @cImport(@cInclude("stdio.h")); _ = c.printf("%s\n", slice.ptr);

6. 综合示例

以下示例结合数组、切片和错误处理:

const std = @import("std");
const MyError = error{InvalidIndex};

fn get_slice(arr: []const u8, start: usize, end: usize) MyError![]const u8 {
    if (end > arr.len or start > end) return MyError.InvalidIndex;
    return arr[start..end];
}

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    // 数组
    const arr = [_]u8{10, 20, 30, 40, 50};
    try stdout.print("数组: {any}, 长度: {}\n", .{arr, arr.len});

    // 切片
    const slice = arr[1..4];
    try stdout.print("切片: {any}, 长度: {}\n", .{slice, slice.len});

    // 动态分配
    const allocator = std.heap.page_allocator;
    var buffer = try allocator.alloc(u8, 3);
    defer allocator.free(buffer);
    buffer[0] = 1; buffer[1] = 2; buffer[2] = 3;
    try stdout.print("动态切片: {any}\n", .{buffer[0..2]});

    // 错误处理
    const result = get_slice(&arr, 2, 4) catch |err| {
        try stdout.print("错误: {}\n", .{err});
        return;
    };
    try stdout.print("有效切片: {any}\n", .{result});
}

运行

zig run example.zig

输出

数组: {10, 20, 30, 40, 50}, 长度: 5
切片: {20, 30, 40}, 长度: 3
动态切片: {1, 2}
有效切片: {30, 40}

说明

  • 展示数组初始化、切片创建和动态分配。
  • 使用错误处理验证切片边界。

7. 总结

Zig 的数组和切片设计高效且安全:

  • 数组[N]T):固定长度,编译时确定,适合静态数据。
  • 切片[]T):动态长度,运行时确定,适合子数组和字符串。
  • 支持迭代、复制和标准库操作(如 std.ArrayList)。
    注意类型安全、内存管理和越界检查,结合 std.debug.print 调试。Zig 的数组和切片适合系统编程,兼顾性能和灵活性。

如果你需要更复杂的示例(如多维数组、切片操作优化)或有其他问题,请告诉我!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注