切片
切片与数组非常相似。在实际应用中,由于其灵活性,切片的使用比数组更普遍。
你可以从数组、其他切片或数组指针创建新的切片。
接下来,我们来演示如何使用切片:
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)});
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)});
}
打印结果如下:
第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
对指针进行操作则不会有边界检查,需要开发者自行确保安全。
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])});
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])});
}
打印结果如下:
slice 类型为[]i32
slice.ptr 类型为[*]i32
slice 的索引 0 取地址,得到指针类型为*i32
哨兵切片(标记终止切片)
语法 [:x]T
定义了一个哨兵切片。它与普通切片类似,长度在运行时确定,但额外保证在索引 len
处存在一个值为 x
的哨兵元素。该类型不保证哨兵值不会出现在切片内容中。哨兵切片允许访问索引为 len
的元素(即哨兵本身)。
我们也可以通过 data[start..end :x]
语法从多项指针、数组或普通切片中创建一个哨兵切片,其中 x
是哨兵值。
创建哨兵切片时,Zig 会假定在哨兵位置(即 end
索引处)的元素值就是指定的哨兵值。如果实际情况并非如此,将会触发安全保护机制并导致未定义行为。
// 显式声明切片类型
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)});
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)});
}
打印结果如下:
str_slice类型:[:0]const u8
slice类型:[:0]u8