zig 语法精记
基础:打印/循环/数组
https://ziglings.org 的语法练习,详列建议0基础入坑zig的,先过一遍,实践带来思考。
打印: https://zig.guide/standard-library/formatting-specifiers/ {!any}
可打印错误的万金油
选择:if (条件) {...}
switch (i) { 1=>"one", else=>unreachable, }
循环:for (array, 0..) |a,i| {}
while (i<array.len):(i++) {}
数组: const le = [_]u8{ 1, 3 };
const std=@import("std");
pub fn main() void {
printer();
}
fn print() !void { // 自动推断:错误联合类型;函数内有try则必须有❗感叹号
std.debug.print("Hello {}", .{"world"} );
const stdout = std.io.getStdOut().writer();
try stdout.print("Hello {}\n", .{"world"}); // 打印,作用同下一行
stdout.print("Hello {}\n", .{"world"}) catch |err| { return err; }; // 显式错误处理,{}可省略
}
fn loop(numbers: [4]u16) void {
for (numbers) |n| {
n+|1; // 饱和加法,等价于下一行
n=std.math.min(n+1, std.math.pow(2,16)); //(1<<(16+1))-1)
while (n < number) : (n += 1) {
continue;
}
}
}
fn array() void {
const le = [_]u8{ 1, 3 }; // _ :自动推测长度
const et = [_]u8{ 3, 7 };
const leet = le ++ et;
const bit_pattern = [_]u8{ 1, 0, 0, 1 } ** 3;
const lyrics =
\\Ziggy played guitar
\\Jamming good with Andrew Kelley
; // 多行字符串
}
fn switcher(n: u1) !void {
switch (n) {
0 => std.debug.print("false", .{}),
1 => std.debug.print("true", .{}),
else => unreachable, // 不可能的分支,什么都不做。
}
if (n) |value| {
std.debug.print("n={}. ", .{value});
} else |err| switch (err) {
MyNumberError.TooLarge => std.debug.print(">4. ", .{}),
MyNumberError.TooSmall => std.debug.print("<4. ", .{}),
}
}
const Color = enum(u32) {
red = 0xff0000,
green = 0x00ff00,
blue = 0x0000ff,
};
pub fn stringFormat() void {
// {x:0>6}
// ^
// x 类型 ('x'/'X' 小写/大写16进制,'d'/'o'/'b' 10/8/2进制,'e'科学计数;
// "any"自动格式,'s'字符串,'c' ascii;'*'指针)
// : 分隔符(编译器语法的硬性要求)
// 0 填充补齐字符 (默认为单个空格' ')
// > 对齐方式 ('>'右对齐,'<'左对齐,)
// 6 强制总宽度为6 (用补齐字符来填充空白)
std.debug.print(
\\<p>
\\ <span style="color: #{x:0>6}">Red</span>
\\ <span style="color: #{x:0>6}">Green</span>
\\ <span style="color: #{x:0>6}">Blue</span>
\\</p>
\\
, .{
@intFromEnum(Color.red),
@intFromEnum(Color.green),
@intFromEnum(Color.blue),
});
}
匿名变量_
- 解构占位符,来跳过不想要的值:
var myTuple = .{ "Zig", 42, true };
const _, value, _ = myTuple;
- 告诉编译器:别报错;
告诉程序员同事:这个值暂时没用:
fn quack(self: RubberDuck) void {
_ = self; // 如果没有这一行,编译器会认为没用到self参数而报错
print("\"Squeek!\" ", .{});
}
if解构
只读:if (nullableVar) |notNullVal| {...notNullVal只读...} else {...nullableVar为null...}
用指针,可写:if (nullableVar) |*writableVar| {writableVar.*=1;}
仅检测错误:if (val) |_| {} else |err| { try expect(err == error.BadValue); }
先检测anyerror!
再检测nullable?
const errorableVar: anyerror!?u32 = 0;
if (errorableVar) |nullableVar| {
try expect(nullableVar.? == 0);
if (nullableVar) |notNullVal| {
_ = notNullVal;
}
} else |err| {
_ = err;
}
循环表达式
https://course.ziglang.cc/basic/process_control/loop#作为表达式使用
如果退出for循环,仍没有break返回值
,则执行else
分支:var a = for () || { break value; } else null;
(真奇怪,orelse就不通过编译)
显式错误处理
2种错误处理模式:
- 🔌暴露型/摆烂型,不可恢复错误:交给上级函数处理错误
- 📦封装型,可恢复错误:内部处理错误
在main()里 | try func(); |
func() catch "指定值"; |
func() catch |err| {...} |
---|---|---|---|
赋值var ans= | ans: Error!String | ans的值可能为:"my_str" 或 "指定值" | {return err;} 或{return "指定值"} |
语句 | main()退出,返回Error | 忽略返回值 | {可恢复错误,做一些事情;} 或{ans=err;} |
何时用unreachable
reddit区别探讨: unreachable
@panic()
-
抵消编译器无畏的检查。
比如上方的switch (b)
,b只有0~1的可能性,但必须还有个else
才能通过语法检查。 -
- 告诉写代码的同事:这是不可能发生的,并且什么事都不会做
- 告诉编译器:此处选择什么策略做优化。
视频还介绍了可恢复/不可恢复错误,值得一听。
-
原stackoverflow回答: 如果您想控制发生的事情,请永远不要使用 unreachable。类似
@setRuntimeSafety(true)
undefined/null
https://www.cnblogs.com/withstand/p/17695504.html
这2个值,是很多错误引发的根源,因为这2个值都不能参与数学计算。比如null+1
、undefined * 3
该等于多少呢?
截止v0.14.0,undefined的比较功能zig还没做好,所以先不要滥用undefined:https://github.com/ziglang/zig/issues/8056
-
undefined用于实际赋值前初始化: undefined的类型可以是任何类型,不需要可空
?
;在这个值被访问时,就会报错。
debug下,undefined会将0xaa写入对应内存;类似于其他语言的final
声明:延后初始化值。
多用于并行线程-读写锁/ui预加载-必须提前声明:当一个变量已经被声明,但还没有被赋值时,它的值就是undefined,表示变量的值还未知。 -
null用于返回值: 需要类型是可空类型。
多用于IPC/远程请求(404资源不存在):例如一个函数在某些条件下返回一个对象,但在其他条件下不返回任何东西。在这种情况下,你可能会选择返回null,以表示函数没有返回对象。
const std = @import("std");
fn workerFn(addr: *?u8) void {
const tid = std.Thread.getCurrentId();
std.debug.print("{any}\tvalue={any}\t", .{ tid, addr.* });
if (addr.* == 0xaa) { // undefined的比较功能zig还没做好:https://github.com/ziglang/zig/issues/8056
addr.* = null;
std.debug.print("初始化值,读者变成“写者”(或者说,服务器决定返回null)", .{});
} else if (addr.* == null) {
std.debug.print("其他线程的读者:读到null了,没机会变“写者”,没意思就走了。", .{});
} else {
std.debug.print("\tbug: unreachable but reachable", .{});
}
std.debug.print("\n", .{});
}
pub fn main() !void {
var x: ?u8 = undefined;
std.debug.print("初始化变量,预分配内存:isFinal={any}\n", .{x});
var threads: [4]std.Thread = undefined;
for (&threads) |*thread| thread.* = try std.Thread.spawn(.{}, workerFn, .{&x}); //注意这里的解引用
for (threads) |thread| thread.join();
}
预期输出:
❯ zig run hello.zig -O ReleaseSafe
初始化变量,预分配内存:isFinal=170
1624116 value=170 初始化值,读者变成“写者”(或者说,服务器决定返回null)
1624117 value=null 其他线程的读者:读到null了,没机会变“写者”,没意思就走了。
1624118 value=null 其他线程的读者:读到null了,没机会变“写者”,没意思就走了。
1624119 value=null 其他线程的读者:读到null了,没机会变“写者”,没意思就走了。
thread多线程
良好习惯:多一层{}语句块,专门初始化线程。
{
const handle = try std.Thread.spawn(.{}, thread_function, .{arg1, arg2...});
defer handle.join(); // 退出语句块后执行线程
}
null?
orelse: const notNull : u8 = null orelse 1;
const notError :u8 = anyerror.Unreachable catch 1;
.?
: 值为null时,返回运行时错误。类似的成员有.*
const Elephant = struct {
letter: u8,
tail: ?*Elephant = null, // ?*
visited: bool = false,
};
fn visitElephants(first_elephant: *Elephant) void {
var e = first_elephant;
while (!e.visited) {
std.debug.print("Elephant {u}. ", .{e.letter});
e.visited = true;
e = e.tail orelse break; //orelse
}
}
error/void
2个返回值不正常的空类型。BIBOO BIBOO BIBOO……
- undefined - 目前没有值,暂时无法读取
- null - 有一个 "无值"的明确值
- error - 没有值,因为出错了
- void - 这里永远不会存储值
哨兵sentinel()函数
意义:防越界访问,给下标+1,不影响len
。
用法:data[start..end :x]
,其中 data 是多项指针、数组或切片,x 是哨兵值。
- 哨兵数组
[N:x]T
https://course.ziglang.cc/basic/advanced_type/array#哨兵数组 - 哨兵切片
[:x]T
https://course.ziglang.cc/basic/advanced_type/silce#哨兵切片 - 哨兵指针
[*:x]T
https://course.ziglang.cc/basic/advanced_type/pointer#哨兵指针
const sentinel = @import("std").meta.sentinel;
const print = @import("std").debug.print;
fn printSequence(my_seq: anytype) void {
const my_typeinfo = @typeInfo(@TypeOf(my_seq));
switch (my_typeinfo) {
.Array => {
print("Array:", .{});
for (my_seq) |s| {
print("{}", .{s});
}
},
.Pointer => {
const my_sentinel = sentinel(@TypeOf(my_seq)); // 类似c中返回string的null值
print("Many-item pointer:", .{});
var i: usize = 0;
while (my_seq[i] != my_sentinel) { // 如果是Sting类型,则直接用str.isEmpty()判断更好。arr[0..arr.len]
print("{}", .{my_seq[i]});
i += 1;
}
},
else => unreachable,
}
print(". ", .{});
}
pub fn main() void {
var nums = [_:0]u32{ 1, 2, 3, 4, 5, 6 }; // nums哨兵值为0
const ptr: [*:0]u32 = &nums; // ptr哨兵值为0
nums[3] = 0;
printSequence(nums);
printSequence(ptr);
}
defer
退出语句块{}
后执行
pub fn main() !void {
errdefer std.debug.print("1️⃣当在main()中返回anyerror时,会执行这一行,相当于except{{...}}\n", .{});
defer std.debug.print("②然后,退出main()后执行本语句。相当于finally{{...}}\n", .{});
std.debug.print("③首先,打印这一行。相当于 try{{...}}\n", .{});
return anyerror.Unreachable;
}
预期输出:③②1️⃣
errdefer
与defer
混合时,执行顺序取决于代码中的顺序,但反向(栈:先进后出)
pub fn main() !void {
defer std.debug.print("①然后,退出main()后执行本语句。相当于finally{{...}}\n", .{});
errdefer std.debug.print("2️⃣当在main()中返回anyerror时,会执行这一行,相当于except{{...}}\n", .{});
std.debug.print("③首先,打印这一行。相当于 try{{...}}\n", .{});
return anyerror.Unreachable;
}
预期输出:③2️⃣①
allocator 内存申请
defer的执行顺序的设计,应该是跟内存申请相匹配。
但C++的智能指针比zig更灵活,可以在{}
语句块外释放内存。
pub fn main() !void {
// 假装这就是用户输入
const arr: []const f64 = &[_]f64{ 0.3, 0.2, 0.1, 0.1, 0.4 };
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); // initialize the allocator
defer arena.deinit(); // free the memory on exit
const allocator = arena.allocator(); // initialize the allocator
const avg: []f64 = try allocator.alloc(f64, arr.len); // allocate memory for this array
runningAverage(arr, avg);
std.debug.print("Running Average: ", .{});
for (avg) |val| {
std.debug.print("{d:.2} ", .{val});
}
std.debug.print("\n", .{});
}
struct结构/union联合类型/enum枚举
const Role = enum {
wizard,
thief,
bard,
warrior,
};
const Number = union {
Int: i32,
Float: f32,
};
const Character = struct {
role: Role,
gold: u32,
health: Number,
experience: u32,
};
// 初始化
var chars: [2]Character = undefined;
chars[0] = Character{
.role = Role.wizard, // 成员前面加点
.gold = 20,
.health = 100,
.experience = 10,
};
chars[1] = Character{
.role = Role.bard,
.gold = 10,
.health = -0.1,
.experience = 20,
};
struct(OOP面向对象)
const Alien = struct {
health: u8,
pub fn hatch(strength: u8) Alien {
// We hate this method
return Alien{
.health = strength * 5,
};
}
};
const HeatRay = struct {
damage: u8,
pub fn zap(self: HeatRay, alien: *Alien) void {
// We love this method
alien.health -= if (self.damage >= alien.health) alien.health else self.damage;
}
};
pub fn main() void {
var aliens = [_]Alien{Alien.hatch(1), Alien.hatch(2), Alien.hatch(3), Alien.hatch(5),};// 不同体力的👽来了!
var aliens_alive = aliens.len;
const heat_ray = HeatRay{ .damage = 7 }; // 我们手中有热射线武器
while (aliens_alive > 0) {
aliens_alive = 0;
for (&aliens) |*alien| { // 注意,通过&引用循环遍历每个外星人(* 使指针捕获值)
heat_ray.zap(alien); // “报告,可以直接瞄准alien,不用加星号*alien” 李云龙连长:开炮!
if (alien.health > 0) aliens_alive += 1; // 若外星人的体力仍大于 0,则它还活着。
}
std.debug.print("👽x{}", .{aliens_alive});
}
std.debug.print("🌍🕊✌️!\n", .{});
}
struct可省略的第0参数self
详见官方讨论: https://github.com/ziglang/zig/issues/17105
const Foo = struct {
fn doSomething(self: *Foo, arg1: u8) void {
//...
}
};
Foo.doSomething(arg1); // 可以省略self参数,因为self就是结构体本身。
第0参数名也可以自己起,比如fooSelf
,防止命名冲突,在不修改代码的情况下直接移植代码。
匿名结构体
.{x,}
与MyStruct{.member=x,}
区别:前者匿名结构体,后者MyStruct结构体
.{}
为什么前面加个点.
:花括号前面的点的含义,是让zig自动推断这个匿名结构体的类型。
const std = @import("std");
pub fn main() void {
// 用法1:一次性结构体
var myStruct = .{ .message = "Hello, Zig!", .id = 42 };
std.debug.print("Message: {}, ID: {}\n", .{ myStruct.message, myStruct.id });
// 用法2:嵌套
const ComplexStruct = struct {
inner: struct {
field1: i32,
field2: bool,
},
};
const complexInstance = ComplexStruct{ .inner = .{ .field1 = 123, .field2 = true } };
std.debug.print("Field1: {}, Field2: {}\n", .{ complexInstance.inner.field1, complexInstance.inner.field2 });
// 用法3:anytype返回匿名结构体
const result = createAnonStruct();
std.debug.print("Result: {}\n", .{result});
}
fn createAnonStruct() anytype {
return .{ .a = 10, .b = true };
}
匿名列表
实质上就是下标为0~N的匿名结构体,只是隐藏了key,只需写value。
与元组区别:因多指定了[_]u32
,所以类型必须相同。
const foo: [3]u32 = .{10, 20, 30}; // anonymous list 匿名列表
const bar = .{10, 20, 30}; // anonymous tuple 匿名元组
匿名元组
与列表的区别:可以包含不同类型的值。
打印时需要用{any}
而不是{s}
来避免错误。
interface接口
union(enum)
inline else
const Insect = union(enum) {
ant: Ant,
bee: Bee,
grasshopper: Grasshopper,
// 得益于 "inline else",我们可以将 print() 视为一个接口方法。外部代码无需了解任何其他细节,就能统一处理该联盟中任何带有 print() 方法的成员。
pub fn print(self: Insect) void {
switch (self) {
inline else => |case| return case.print(),
}
}
};
enum
enum也能有方法:多用于返回分类值。(很灵活的语言,♥️来自⛰️💃の🐶)
来源048_methods2.zig:
Zig 的枚举也可以有方法!这条评论最初是问是否有人能在野外找到枚举方法的实例。前五个拉取请求已被接受:
- drforester - I found one in the Zig source:
https://github.com/ziglang/zig/blob/041212a41cfaf029dc3eb9740467b721c76f406c/src/Compilation.zig#L2495- bbuccianti - I found one!
https://github.com/ziglang/zig/blob/6787f163eb6db2b8b89c2ea6cb51d63606487e12/lib/std/debug.zig#L477- GoldsteinE - Found many, here's one
https://github.com/ziglang/zig/blob/ce14bc7176f9e441064ffdde2d85e35fd78977f2/lib/std/target.zig#L65- SpencerCDixon - Love this language so far 😃
https://github.com/ziglang/zig/blob/a502c160cd51ce3de80b3be945245b7a91967a85/src/zir.zig#L530- tomkun - here's another enum method
https://github.com/ziglang/zig/blob/4ca1f4ec2e3ae1a08295bc6ed03c235cb7700ab9/src/codegen/aarch64.zig#L24
ps: error是特殊的enum类型。
union
多类型,只能选其一。
枚举联合,语法糖,左边枚举是自定义类型,右边联合是基元类型:https://course.ziglang.cc/basic/union#标记联合
const ComplexNum = union(enum) { //复数
R: i64, //实数
I: i32, //虚数
};
// const willError = ComplexNum{ .R = 1, .I = 2 }; // bug: 这个zls居然不报错,只在zig run才报错
const x = ComplexNum{ .R = 2 };
std.debug.print("{any}\n", .{x}); // 预期输出:文件名.函数名.ComplexNum{ .R = 1 }
switch (x) { // 预期输出:r=2
.R => |i_64| print("r={}", .{i_64}), // switch捕获union
.I => |i_32| print("i={any}", .{i_32}),
}
推断枚举
union(enum){}
非常简洁,它代表了枚举enum与联合union之间关系的冰山一角。实际上,你可以将一个联合项强制为一个枚举项(这样就可以将联合项中的活动字段作为一个枚举项)。更神奇的是,你还可以将一个枚举强制转换为一个联合!但别高兴得太早,这只有在联合类型是 void 等奇怪的零类型时才有效!
正如计算机科学中的大多数想法一样,标记联合的历史可以追溯到 20 世纪 60 年代。不过,它们最近才成为主流,尤其是在系统级编程语言中。你可能还见过它们被称为 "变体"、"总和类型",甚至 "枚举"!
FP函数式编程:函数做参数传入+递归
数据与函数方法分离。ECS模式。
指针
类型:anyerror!*?const T
错误联合类型内容可空只读指针(函数返回值类型,可以省略anyerror来自动推导)
用法:value=pointer.*
addr=&pointer
pointer: *const T
双只读指针:指向常量的指针,需要使用 *const
类型,而不是 *
类型
p.*
只读:*const
导致→大块内存数据的只读引用,避免深拷贝。p.*=13;
是非法的。p
只读:const p
导致→本身存储的内存地址也不可变。p=0x12345678;
也是非法的。
pub fn main() void {
const a: u8 = 12;
//const p: *u8 = &a; //错误的
const p: *const u8 = &a;
std.debug.print("a: {}, b: {}\n", .{ a, p.* }); //p.*
}
指向变量的指针,才是*
类型。
pub fn main() void {
var a: u8 = 12;
const p: *u8 = &a;
std.debug.print("a: {}, b: {}\n", .{ a, p.* }); //p.*
}
🤯恐怖如斯:!**??**??bool
是可以编译通过的。
切片slice
左闭右开,数学语言[0,3)
→zig语言"UwU&Cham"[0..3]
,学python的。
[]const u8
代表 切片常量类型:const noCute: []const u8 = "UwU&Cham"[3..];
切片指针
切片与指针都能用下标
类型 | 意义 |
---|---|
[]u8 |
经常用,u8的可写切片,包含指向u8数组的指针和数组长度 |
[]const u8 |
只读用,u8的只读切片,包含指针和数组长度 |
[*]u8 |
野蛮用,指向未知数量u8的指针,参考[_]u8 语法糖 |
[*]const u8 |
类似[*]u8 ,但指向的元素是只读的 |
*[4]u8 |
不灵活,指向包含4个u8的数组的指针 |
*[]u8 |
|
*[*]u8 |
const std = @import("std");
var foo: [4]u8 = [4]u8{ 1, 2, 3, 4 };
var foo_slice: []u8 = foo[0..];
var foo_ptr: [*]u8 = &foo;
var foo_slice_from_ptr: []u8 = foo_ptr[0..4];
pub fn main() !void {
var arr: [?]u8 = [_]u8{ 0, 1, 2, 3, 4, 5 };
var slice: []u8 = arr[1..4];
slice[1] = 22; // 可写切片,会影响arr
for (slice) |elem| {
std.debug.print("{d} ", .{elem}); // 预期输出:1 22 3
}
std.debug.print("\n", .{});
for (arr) |elem| {
std.debug.print("{d} ", .{elem}); // 预期输出:0 1 22 3 4 5
}
}
foo_slice 和 foo_ptr 的区别:切片有已知长度;指针没有,由您来跟踪foo_ptr 指向的 u8 数量!
留意下面的类型是怎么写的
const std = @import("std");
pub fn main() void {
const zen12: *const [21]u8 = "Memory is a resource."; // 强制切片也可以:const zen12: []const u8
const zen_manyptr: [*]const u8 = zen12;
const zen12_string: []const u8 = zen_manyptr[0..21]; // zen_manyptr[0..]就不行,因为zen_manyptr没有数组长度信息
std.debug.print("{s}\n", .{zen12_string});
}
值
值类型
- @import()函数:
const printf=@import("std").debug.print;
- 常量(const)和变量(var):在Zig中,常量的值和地址都不能改变,而变量的值可以改变。
- 结构体(Struct):方便内存管理。
const Const_struct=struct{member:u1=0;}
情况下,const_struct.member=1;
是非法的,const struct的所有成员都是只读的,若要可写,需要传入&const_struct
,函数参数为arg_in: *const_struct
,然后就可写了:arg_in.member=1;
深拷贝,慢而独立:var deepCopy: MyStruct = my_struct;
;仅引用,会修改源:var refCopy: *MyStruct = &my_struct;
(可以自己写一个fork_copy函数,仅不同的值才分配内存空间。等到所有的值都不同时,就互相独立了。) - 指针(Pointer):指针是一种存储变量地址的数据类型。
- 函数(Function):是存储在特定地址的指令代码。Zig中的函数输入参数总是不可变的常量,存储在栈中。
值存储在哪儿
- 函数外:声明的变量、常量&常量的值,其相对内存位置是硬编码的,不会改变,通常存储在硬盘的程序数据段。
变量的值,其内存地址是临时的,会随机改变,分配到堆/栈上。 - 函数内:声明的结构体变量,通常存储在内存堆Heap上。
常量,则取决于编译器,既可以放数据段,也可以内联inline
- 栈(Stack):栈是一种数据结构,用于存储函数的输入参数和局部变量。CPU对栈有特殊的支持,使得内存存储非常高效。
- 堆(Heap):堆是另一种内存存储区域,用于存储程序运行时动态分配的内存。堆内存的管理相对于栈来说效率较低,因为没有CPU的特殊支持。
数
整数表达:160xf
, 80o7
, 20b1
,字符'C', Unicode\u{0x200000}
浮点表达:科学计数法 1.2*10^31.2e+3
, 16进制e→p0x2A.F7p+3
整数类型:u32, i32, comptime_int。位数为8~128中间的2阶乘数。
浮点类型:f64,数太大会±inf。
运算时,你必须手动显示转换类型@float(int)+float
。Zig 不会背着你执行不安全的类型强制转换:
var foo: f16 = 5; // NO ERROR
var foo: u16 = 5; // 数字相同,类型不同
var bar: f16 = foo; // ERROR
浮点速查表
f16 | f32 | f64 | f80 | f128 | |
---|---|---|---|---|---|
(有效位数)₂ | 11 | 24 | 53 | 64 | 113 |
(有效位数)₁₀ | 3 | 7 | 15 | 19 | 34 |
有效位数十进制 | 三 | 七 | 要我 | 三思 |
log数学含义
- log_10(2):10进制中有几位数,能等于2进制中的1位数。
- log_2(10):2进制中有几位数,能等于10进制中的1位数。
类型转换
- 更严格、范围更大、单指针→切片/多指针
- 单指针→指向长度为1的单指针
- undefined→anyType
comptime_int
,comptime_float
强制为兼容类型。- 标记联合强制为当前标记枚举。
- 当标记字段是只有一个值的零长类型(如 void)时,枚举会强制到标记联合。
- 零位类型(如 void)可以被强制为单指针。
后三点相当深奥,但我们非常欢迎你在官方 Zig 语言文档中阅读更多相关内容,并编写自己的实验。
pub fn main() void {
var letter: u8 = 'A';
const my_letter: ??????? = &letter; // 类型是什么?提示:上方第2点
print("Letter: {u}\n", .{my_letter.?.*[0]});
}
位运算
二进制状态压缩
swap xor
我长这么大,第一次见能用异或xor做交换的。
var x=1; var y=0;
x ^= y; // t=y;
y ^= x; // y=x;
x ^= y; // x=t;
comptime
https://course.ziglang.cc/advanced/comptime#编译期表达式
zig允许使用相同的zig语法来编写宏,即编译期代码。
const const_int = 12345; // const默认是comptime_int
comptime var var_int: u32 = 12345; // 前面加上comptime,此时可参与comptime代码片段的(编译期)预运算
如果移除 comptime,var_int 将成为一个运行时变量,它的值可以在运行时改变(尽管在这个例子中它被定义为 var,但初始化为一个字面量值,看起来像是常量)。这样,var_int 就不能用在需要编译时常量
的上下文中了。
用法1:编译时已知size
类型,返回size
类型
fn makeSequence(comptime T: type, comptime size: usize) [size]T {
var sequence: [size]T = undefined;
var i: usize = 0;
while (i < size) : (i += 1) {
sequence[i] = @as(T, @intCast(i)) + 1;
}
return sequence;
}
什么情况下不应使用comptime
- 容器级作用域(在源文件中的任何函数之外)
- 类型声明
- 变量
- 函数(参数和返回值的类型)
- 结构体
- 统一体
- 枚举
- for 和 while 循环中的测试表达式
- 传递给 @cImport() 内置函数的表达式
⚠️感染原则:一旦与comptime_类型
和@builtin()函数
打交道,其传染链就都得使用comptime语法inline for
。
并且zig经可能会自动加comptime,自己手动加comptime的地方一般不多。
什么情况下comptime会失败
- 类型推断失败:如果 comptime_var 的值来自于一个复杂的表达式,且该表达式的结果类型依赖于运行时的数据或条件,编译器可能无法在编译期推断出确切的类型。
- 依赖于外部输入:如果 comptime_var 的值依赖于编译时无法获取的外部输入(例如,环境变量、文件内容等),则编译器无法在编译期确定其类型。
- 泛型编程:如果 comptime_var 作为泛型参数传递给函数或类型,且没有足够的上下文来约束其泛型,编译器可能无法在编译期确定其具体类型。
- 条件编译:如果 comptime_var 的值依赖于编译时条件(如 @if 语句),且这些条件涉及到编译器无法确定的逻辑,那么 count 的类型可能也无法在编译期确定。
@builtin内建函数
const Narcissus = struct {
me: *Narcissus = undefined,
myself: *Narcissus = undefined,
echo: void = undefined, // Alas, poor Echo!
fn fetchTheMostBeautifulType() type {
return @This(); // 预期返回:"文件名.Narcissus"。返回该结构体类型名
}
};
- String:
@This()
:返回当前作用域的类型。
自引用:https://course.ziglang.cc/basic/advanced_type/struct#自引用 - String:
@TypeOf(...)
:返回(都能转换为的类型)类型名。
反射:https://course.ziglang.cc/more/reflection#typeof - Struct:
@typeInfo(comptime T: type)
@import("std").builtin.Type
@typeInfo(T).Struct.fields[0].name。
反射:https://course.ziglang.cc/more/reflection#typeinfo - bool:
@hasDecl(T,"var_name/func_name")
declare
可实现“鸭子类型”(duck typing)检查。 - anytype:
@ptrCast(value: anytype)
:指针转指针。返回类型是推断的结果类型。 - T:
@as(f32, 3.141592)
:强制转类型 - (field):
@field(value: anytype, field_name: String)
反射:https://course.ziglang.cc/more/reflection#field
(注意到返回类型的两个函数是以大写字母开头的吗?这是 Zig 的标准命名方式)
@"非法变量名"
→合法变量名
@ptrCast
const data: [*]const u8 = "Weird Data!";
const printable: [*:0]const u8 = @ptrCast(data); // [*]const u8转[*:0]const u8
@field
打印匿名结构体,就像python的dict字典一样。
const print = @import("std").debug.print;
pub fn main() void {
const foo = .{
true,
false,
@as(i32, 42),
@as(f32, 3.141592),
};
printTuple(foo);
const nothing = .{};
print("\n", nothing);
}
fn printTuple(tuple: anytype) void {
const fields = @typeInfo(@TypeOf(tuple)).Struct.fields;
inline for (fields) |field| {
print("\"{s}\"({any}):{any} ", .{ field.name, field.type, @field(tuple, field.name) });
}
}
预期输出:"0"(bool):true "1"(bool):false "2"(i32):42 "3"(f32):3.141592e0
ps: 这个很重要,自适应打印。
inline
https://ziglang.org/documentation/master/#inline-fn
当一个函数被声明为"inline"时,编译器会尝试将该函数的代码直接插入到代码中,而不是通过常规的函数调用机制(即跳转到函数代码,然后返回)。可以通过反汇编代码来查看:call
jmp
这样做的主要优点是可以消除函数调用的开销,包括参数传递、堆栈帧管理等。这对于那些非常小、调用频繁的函数来说,可以提高程序的运行效率。
然而,过度使用inline函数也可能增加程序的大小,并可能影响缓存效率。因为每个函数调用都被替换为实际的函数代码
inline for (fields) |field| {
if (field.type != void) {
print(" {s}", .{field.name});
}
}
async异步
https://course.ziglang.cc/advanced/async
与undefined一样,都是网络编程中常用的关键字。
官方暂未敲定语法,未来可能仍有重大变化,讨论链接:https://github.com/ziglang/zig/issues/6025
关键字:suspend{}
resume{}
async
await
suspend vs return
- 栈帧:为调用函数及其所有数据而分配给程序的内存空间
return
关键字:将当前函数调用的堆栈栈帧弹出(不再需要) ,并将控制权返回到调用函数的位置:
fn foo() void { return; 弹出栈帧并返回控制权 }
- 异步函数:与
return
一样,suspend
关键字也会将控制权返回到调用函数的位置,但函数调用的栈帧会保留下来,以便以后可以重新获得控制权:
fn fooThatSuspends() void { suspend {} 返回控制权,但保留栈帧 }
async
关键字:要在异步上下文中调用任何函数,并获取其栈帧的引用以供以后使用:
var foo_frame = async fooThatSuspends();
- 自动传染:如果调用异步函数时没有使用 "async "关键字,那么调用异步函数的函数本身就会变成异步函数!在本例中,bar()函数现在是异步的,因为它调用了 fooThatSuspends(),而 fooThatSuspends() 是异步的。
fn bar() void { fooThatSuspends(); }
- main() 函数不可能是异步的!
// TODO: 即使编译器还没实现异步,这部分也能有练习。未来填坑吧。
@cImport
@cInclude
const c = @cImport({
@cInclude("unistd.h");
});
const c_res = c.write(2, "Hello C from Zig!", 17);
test测试
test "divide" {
try testing.expect(divide(2, 2) catch unreachable == 1);
try testing.expect(divide(-1, -1) catch unreachable == 1);
try testing.expect(divide(10, 2) catch unreachable == 5);
try testing.expect(divide(1, 3) catch unreachable == 0.3333333333333333);
try testing.expectError(error.DivisionByZero, divide(15, 0));
}
文件处理
符号切割
std.mem.tokenizeAny(u8, poem, " ;,.!\n");
const std = @import("std");
const print = std.debug.print;
pub fn main() !void {
const poem =
\\My name is Ozymandias, King of Kings;
\\Look on my Works, ye Mighty, and despair!
;
var it = std.mem.tokenizeAny(u8, poem, " ;,.!\n"); // 分词
// print all words and count them
var cnt: usize = 0;
while (it.next()) |word| {
cnt += 1;
print("{s}\n", .{word});
}
print("This little poem has {d} words!\n", .{cnt});
}
写文件byte
defer file.close();
,各种try
const std = @import("std");
pub fn main() !void {
const cwd: std.fs.Dir = std.fs.cwd();
cwd.makeDir("output") catch |e| switch (e) {
error.PathAlreadyExists => {},
else => return e,
};
var output_dir: std.fs.Dir = try cwd.openDir("output", .{});
defer output_dir.close();
const file: std.fs.File = try output_dir.createFile("zigling.txt", .{});
defer file.close();
const byte_written = try file.write("It's zigling time!");
std.debug.print("Successfully wrote {d} bytes.\n", .{byte_written});
}
读文件byte
const bytes_read = try file.read(&content);
107_files2.zig还教你要看文档
const std = @import("std");
pub fn main() !void {
const cwd = std.fs.cwd(); // 获取当前工作目录
var output_dir = try cwd.openDir("output", .{});
defer output_dir.close();
const file = try output_dir.openFile("zigling.txt", .{});
defer file.close();
var content = [_]u8{'A'} ** 64;
std.debug.print("{s}\n", .{content}); // 64个A
const bytes_read = try file.read(&content); // 读取文件内容到content,返回读取的字节数
std.debug.print("Successfully Read {d} bytes: {s}\n", .{
bytes_read,
content[0..bytes_read],
});
}