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
到底怎么回事?
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」