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),
    });
}

匿名变量_

  1. 解构占位符,来跳过不想要的值:
var myTuple = .{ "Zig", 42, true };
const _, value, _ = myTuple;
  1. 告诉编译器:别报错;
    告诉程序员同事:这个值暂时没用:
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才能通过语法检查。

  • ytb官方视频探讨:

    1. 告诉写代码的同事:这是不可能发生的,并且什么事都不会做
    2. 告诉编译器:此处选择什么策略做优化。
      1. 测试遇到unreachable时,告诉你应该去处理这个错误。
      2. ReleaseSafe模式下,panic以保证程序,宁可报错退出,也不继续执行。
      3. ReleaseFast/ReleaseSmall模式下,编译器会忽略该分支,让汇编代码更小。这会导致程序继续运行,并进入未定义状态。

    视频还介绍了可恢复/不可恢复错误,值得一听。

  • 原stackoverflow回答: 如果您想控制发生的事情,请永远不要使用 unreachable。类似@setRuntimeSafety(true)

undefined/null

https://www.cnblogs.com/withstand/p/17695504.html
这2个值,是很多错误引发的根源,因为这2个值都不能参与数学计算。比如null+1undefined * 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 是哨兵值

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️⃣


errdeferdefer混合时,执行顺序取决于代码中的顺序,但反向(栈:先进后出)

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 };
}

相关技巧:利用@field(),自适应打印匿名结构体

匿名列表

实质上就是下标为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 的枚举也可以有方法!这条评论最初是问是否有人能在野外找到枚举方法的实例。前五个拉取请求已被接受:

  1. drforester - I found one in the Zig source:
    https://github.com/ziglang/zig/blob/041212a41cfaf029dc3eb9740467b721c76f406c/src/Compilation.zig#L2495
  2. bbuccianti - I found one!
    https://github.com/ziglang/zig/blob/6787f163eb6db2b8b89c2ea6cb51d63606487e12/lib/std/debug.zig#L477
  3. GoldsteinE - Found many, here's one
    https://github.com/ziglang/zig/blob/ce14bc7176f9e441064ffdde2d85e35fd78977f2/lib/std/target.zig#L65
  4. SpencerCDixon - Love this language so far 😃
    https://github.com/ziglang/zig/blob/a502c160cd51ce3de80b3be945245b7a91967a85/src/zir.zig#L530
  5. 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切片的指针
*[*]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});
}

值类型

  1. @import()函数:const printf=@import("std").debug.print;
  2. 常量(const)和变量(var):在Zig中,常量的值和地址都不能改变,而变量的值可以改变。
  3. 结构体(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函数,仅不同的值才分配内存空间。等到所有的值都不同时,就互相独立了。)
  4. 指针(Pointer):指针是一种存储变量地址的数据类型。
  5. 函数(Function):是存储在特定地址的指令代码。Zig中的函数输入参数总是不可变的常量,存储在中。

值存储在哪儿

  1. 函数:声明的变量、常量&常量的值,其相对内存位置是硬编码的,不会改变,通常存储在硬盘的程序数据段。
    变量的值,其内存地址是临时的,会随机改变,分配到/上。
  2. 函数:声明的结构体变量,通常存储在内存堆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. 更严格范围更大单指针切片/多指针
  2. 单指针→指向长度为1的单指针
  3. undefined→anyType
  4. comptime_int,comptime_float强制为兼容类型。
  5. 标记联合强制为当前标记枚举。
  6. 当标记字段是只有一个值的零长类型(如 void)时,枚举会强制到标记联合
  7. 零位类型(如 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会失败

  1. 类型推断失败:如果 comptime_var 的值来自于一个复杂的表达式,且该表达式的结果类型依赖于运行时的数据或条件,编译器可能无法在编译期推断出确切的类型。
  2. 依赖于外部输入:如果 comptime_var 的值依赖于编译时无法获取的外部输入(例如,环境变量、文件内容等),则编译器无法在编译期确定其类型。
  3. 泛型编程:如果 comptime_var 作为泛型参数传递给函数或类型,且没有足够的上下文来约束其泛型,编译器可能无法在编译期确定其具体类型。
  4. 条件编译:如果 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"。返回该结构体类型名
    }
};
  1. String: @This():返回当前作用域的类型。
    自引用:https://course.ziglang.cc/basic/advanced_type/struct#自引用
  2. String: @TypeOf(...):返回(都能转换为的类型)类型名。
    反射:https://course.ziglang.cc/more/reflection#typeof
  3. Struct: @typeInfo(comptime T: type) @import("std").builtin.Type @typeInfo(T).Struct.fields[0].name
    反射:https://course.ziglang.cc/more/reflection#typeinfo
  4. bool: @hasDecl(T,"var_name/func_name")declare
    可实现“鸭子类型”(duck typing)检查
  5. anytype: @ptrCast(value: anytype):指针转指针。返回类型是推断的结果类型。
  6. T: @as(f32, 3.141592):强制转类型
  7. (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

  1. 栈帧:为调用函数及其所有数据而分配给程序的内存空间
  2. return关键字:将当前函数调用的堆栈栈帧弹出(不再需要) ,并将控制权返回到调用函数的位置:
    fn foo() void { return; 弹出栈帧并返回控制权 }

  1. 异步函数:与return一样,suspend关键字也会将控制权返回到调用函数的位置,但函数调用的栈帧会保留下来,以便以后可以重新获得控制权:
    fn fooThatSuspends() void { suspend {} 返回控制权,但保留栈帧 }
  2. async关键字:要在异步上下文中调用任何函数,并获取其栈帧的引用以供以后使用:
    var foo_frame = async fooThatSuspends();
  3. 自动传染:如果调用异步函数时没有使用 "async "关键字,那么调用异步函数的函数本身就会变成异步函数!在本例中,bar()函数现在是异步的,因为它调用了 fooThatSuspends(),而 fooThatSuspends() 是异步的。
    fn bar() void { fooThatSuspends(); }
  4. 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],
    });
}
posted @ 2024-06-19 11:45  Nolca  阅读(28)  评论(0编辑  收藏  举报