Zig从XX到放弃(3)数组与指针
数组
数组是一组固定大小的元素,数组的大小必须是常量表达式。数组的大小是usize类型,用数组的len获得。
为了创建一个数组,可以使用常量表达式的数组字面量,或者使用std.mem.zeroes或std.mem.alloc。
const a = [3]i32{1,2,3};
const b = [_]i32{4, 5, 6};
const c: [3]i32 = .{7, 8, 9};
看看上面的三个表达式,其含义和目的都是一样的。第一个表达式省略了变量的类型,编译器会根据数组字面量的内容推断出类型。第二个表达式使用了_作为数组的大小,这个_会被替换成数组字面量的长度。第三个表达式使用了类型注解,这个类型注解必须和数组字面量的内容匹配,后面的.{}是一个结构体字面量,这个结构体字面量的类型是[3]i32。
此外还能创建一个内容为空的数组,这个数组的大小必须是常量表达式。
var d: [3]i32 = undefined;
d = .{ 10, 11, 12 };
数组常量有两个运算符很常用,**和++。**是数组的拷贝,++是数组的连接。
const e = .{1} ** 3;
const f = .{1} ** 3 ++ .{2} ** 2 ++ .{3};
**比++优先级高,所以a ** b ++ c会被解析成(a ** b) ++ c。
输出上面的数组,值得注意的是,这段代码里演示了多维数组,数组的里面是[]const i32类型,外面是一个长度为6的数组。里面的这个而类型其实是一个叫slice的东西,这个东西本质上是一个结构体,里面有一个指针和一个长度,这个指针[*]T指向了一个数组的一部分(这里就是指向第一个元素&a),这个长度就是这个slice的长度。
这个东西一开始不好理解,后面用着用着就习惯。下面一节也做一点小小的探索。
for ([_][]const i32{ &a, &b, &c, &d, &e, &f }) |arr| {
print("{d:<4}{any}", .{ arr.len, arr });
print("\n", .{});
}
得到数组的大小和内容。
3 { 1, 2, 3 }
3 { 4, 5, 6 }
3 { 7, 8, 9 }
3 { 10, 11, 12 }
3 { 1, 1, 1 }
6 { 1, 1, 1, 2, 2, 3 }
指针与slice
指针在Zig中有两类:
- *T:指向T类型的指针
- [*]T:指向T类型的数组的指针
前者的访问方式是ptr.*,后者的访问方式有几种:
ptr[i]ptr[start_index..end_index]ptr+x,ptr-x
这里唯一需要注意的是,T的尺寸必须在编译时已知。
与指针不同,[]T是一个slice,上面说过,slice是一个结构体,里面有一个指针和一个长度,这个指针指向了一个数组的一部分,这个长度就是这个slice的长度。slice的访问方式有几种:
slice[i]slice[start_index..end_index]slice.len
还有一个概念就是*[N]T,这个是一个指向T类型的数组的指针,这个数组的大小是也是已知的。比如前面for循环里构造的那个[_][]const i32,它的每个元素就是一个*[N]T类型的指针。所以需要用&取地址。这个指针由于尺寸已知,也能用下面的集中访问方式:
ptr[i]ptr[start_index..end_index]ptr.len
下面用一段代码来探索一下这几个概念。定义函数,输出类型的大小和位数。
fn show_ptr_arr_size(comptime T: type) void {
const array_typs = [_]type{ T, []T, [][]T, [][][]T, *T, *[]T, *[][]T, *[][][]T, [10]T, *[10]T, usize };
print("|{s:>12}|{s:>10}|{s:>10}|\n", .{ "type", "size", "bitSize" });
print("|{s:>12}|{s:>10}|{s:>10}|\n", .{ "-" ** 8, "-" ** 10, "-" ** 10 });
inline for (array_typs) |typ| {
print("|{s:>12}|{d:>10}|{d:>10}|\n", .{ @typeName(typ), @sizeOf(typ), @bitSizeOf(typ) });
}
}
```zig
在主程序里面调用这个函数。
```zig
pub fn main() void {
show_ptr_arr_size(u8);
show_ptr_arr_size(u16);
}
得到下面的结果,与前面的说法时相符的。数组和slice包含了一个指针,一个usize,所以它的大小是16字节,位数是128位。而指针则不包含长度,所以它的大小是8字节,位数是64位。
| type | size | bitSize |
|---|---|---|
| u8 | 1 | 8 |
| []u8 | 16 | 128 |
| [][]u8 | 16 | 128 |
| [][][]u8 | 16 | 128 |
| *u8 | 8 | 64 |
| *[]u8 | 8 | 64 |
| *[][]u8 | 8 | 64 |
| *[][][]u8 | 8 | 64 |
| [10]u8 | 10 | 80 |
| *[10]u8 | 8 | 64 |
| usize | 8 | 64 |
| type | size | bitSize |
|---|---|---|
| u16 | 2 | 16 |
| []u16 | 16 | 128 |
| [][]u16 | 16 | 128 |
| [][][]u16 | 16 | 128 |
| *u16 | 8 | 64 |
| *[]u16 | 8 | 64 |
| *[][]u16 | 8 | 64 |
| *[][][]u16 | 8 | 64 |
| [10]u16 | 20 | 160 |
| *[10]u16 | 8 | 64 |
| usize | 8 | 64 |
这两个表中唯一不理解的是,*[10]T的大小为什么是8字节,而不是16字节。按照文档的描述,这个指针可以操作array_ptr.len,那么这个长度记录在哪里呢?
结论
- 数组在Zig中是一个结构体,它包含了一个指针和一个长度。
- 数组和指针十分相关,但是访问方式有一点点不同。
*[N]T到底怎么回事?

浙公网安备 33010602011771号