Skip to content
zig 版本:0.14.0

切片

切片与数组非常相似。在实际应用中,由于其灵活性,切片的使用比数组更普遍。

你可以从数组、其他切片或数组指针创建新的切片。

接下来,我们来演示如何使用切片:

zig
var array = [_]i32{ 1, 2, 3, 4 };

const len: usize = 3;
const slice: []i32 = array[0..len];

for (slice, 0..) |ele, index| {
    print("第{}个元素为:{}\n", .{ index + 1, ele });
}
print("slice 类型为{}\n", .{@TypeOf(slice)});

const slice_2: []i32 = array[0..array.len];
print("slice_2 类型为{}\n", .{@TypeOf(slice_2)});
zig
const print = @import("std").debug.print;

pub fn main() void {
    var array = [_]i32{ 1, 2, 3, 4 };

    const len: usize = 3;
    const slice: []i32 = array[0..len];

    for (slice, 0..) |ele, index| {
        print("第{}个元素为:{}\n", .{ index + 1, ele });
    }
    print("slice 类型为{}\n", .{@TypeOf(slice)});

    const slice_2: []i32 = array[0..array.len];
    print("slice_2 类型为{}\n", .{@TypeOf(slice_2)});
}

打印结果如下:

sh
第1个元素为:1
第2个元素为:2
第3个元素为:3
slice 类型为[]i32
slice_2 类型为[]i32

创建切片的方式与访问数组成员类似,但方括号 [] 中指定的是索引的边界,遵循“左闭右开”原则。

在上面的例子中,我们从一个数组创建切片,其左边界为 0,右边界为变量 len

注意:如果切片的两个边界值都是编译期常量,编译器会将其优化为数组指针;但如果至少有一个边界值是运行时变量,那么它就是一个真正的切片。

🅿️ 提示

切片的本质是一个“胖指针”(fat pointer),它内部包含一个 [*]T 类型的指针和一个长度值。

虽然 slice.ptr(指针)和 slice.len(长度)都是可以访问和修改的,但在实践中应避免直接操作它们,因为这很容易破坏切片的内部结构,导致不可预期的行为(除非你完全清楚自己在做什么)。

切片指针

切片除了有 len 属性,还有一个 ptr 属性,它是一个指向切片数据起始位置的多项指针。我们可以通过 slice.ptr 来访问它。

当我们对切片中的单个元素取地址(&)时,会得到一个单项指针。

需要注意的是:直接对切片进行索引操作会进行边界检查,而通过 slice.ptr 对指针进行操作则不会有边界检查,需要开发者自行确保安全。

zig
var array = [_]i32{ 1, 2, 3, 4 };

// 边界使用变量,保证切片不会被优化为数组指针
var len: usize = 3;
_ = &len;

var slice = array[0..len];

print("slice 类型为{}\n", .{@TypeOf(slice)});
print("slice.ptr 类型为{}\n", .{@TypeOf(slice.ptr)});
print("slice 的索引 0 取地址,得到指针类型为{}\n", .{@TypeOf(&slice[0])});
zig
const print = @import("std").debug.print;

pub fn main() void {
    var array = [_]i32{ 1, 2, 3, 4 };

    // 边界使用变量,保证切片不会被优化为数组指针
    var len: usize = 3;
    _ = &len;

    var slice = array[0..len];

    print("slice 类型为{}\n", .{@TypeOf(slice)});
    print("slice.ptr 类型为{}\n", .{@TypeOf(slice.ptr)});
    print("slice 的索引 0 取地址,得到指针类型为{}\n", .{@TypeOf(&slice[0])});
}

打印结果如下:

sh
slice 类型为[]i32
slice.ptr 类型为[*]i32
slice 的索引 0 取地址,得到指针类型为*i32

哨兵切片(标记终止切片)

语法 [:x]T 定义了一个哨兵切片。它与普通切片类似,长度在运行时确定,但额外保证在索引 len 处存在一个值为 x 的哨兵元素。该类型不保证哨兵值不会出现在切片内容中。哨兵切片允许访问索引为 len 的元素(即哨兵本身)。

我们也可以通过 data[start..end :x] 语法从多项指针、数组或普通切片中创建一个哨兵切片,其中 x 是哨兵值。

创建哨兵切片时,Zig 会假定在哨兵位置(即 end 索引处)的元素值就是指定的哨兵值。如果实际情况并非如此,将会触发安全保护机制并导致未定义行为。

zig
// 显式声明切片类型
const str_slice: [:0]const u8 = "hello";
print("str_slice类型:{}\n", .{@TypeOf(str_slice)});

var array = [_]u8{ 3, 2, 1, 0, 3, 2, 1, 0 };
const runtime_length: usize = 3;
const slice: [:0]u8 = array[0..runtime_length :0];
print("slice类型:{}\n", .{@TypeOf(slice)});
zig
const print = @import("std").debug.print;

pub fn main() void {
    // 显式声明切片类型
    const str_slice: [:0]const u8 = "hello";
    print("str_slice类型:{}\n", .{@TypeOf(str_slice)});

    var array = [_]u8{ 3, 2, 1, 0, 3, 2, 1, 0 };
    const runtime_length: usize = 3;
    const slice: [:0]u8 = array[0..runtime_length :0];
    print("slice类型:{}\n", .{@TypeOf(slice)});
}

打印结果如下:

sh
str_slice类型:[:0]const u8
slice类型:[:0]u8