Skip to content

【Zig 日报】为什么 Zig 这么酷? #273

@jiacai2050

Description

@jiacai2050

前言

Why is Zig so Cool?一文中,作者认为Zig不仅仅是一种新的编程语言,而是一种全新的编程方式,它远不止是C或C++的替代品。本文将介绍Zig最吸引人的特性,并提供一个简要概述,旨在帮助程序员快速入门。

编译器

Zig编译器最令人称道的特性之一是编译C代码的能力,以及交叉编译到不同架构的能力,这在业界产生了巨大影响。

安装编译器

安装Zig编译器很简单,从Ziglang的下载页面可以找到适用于不同处理器和操作系统的版本。

构建 "Hello World!" 程序

建议按照网站 "Getting Started" 部分的说明构建 "Hello World!" 程序,并参考 "Language" Basis 部分以深入了解Zig语言。

主要概念和命令

以下部分概述了Zig语言,旨在通过少量基本概念和命令指导程序员快速开始编程。随后,简要介绍了如何构建程序和测试模块,以及如何在Zig中进行底层编程。

变量声明

Zig中的变量声明通常包含三个部分:可访问性(pub 或无)、var 或 const 关键字、变量名,以及变量类型声明和初始化。只有第一部分和第三部分是必需的,变量类型可以从初始化中推断出来。pub 变量对模块是局部的,建议尽量减少 pub 函数的数量,以鼓励内聚和减少耦合。

Structs, 匿名 structs 和测试块

在 Zig 中, .{ closed with a } 是一个 "匿名 struct 字面量" - 主要用于初始化另一个结构的元素或创建一个新的初始化结构的匿名结构. 一个 .{ } 是一个空的匿名 struct 字面量。struct 关键字后跟一个 { 并以 } 结尾是一个 struct 声明.可以使用类型初始化变量,该类型充当类型的别名。注意允许编译和执行测试而不需要可执行文件的 "测试块"。

位域

位域是在打包的结构中声明的具有特定大小的字段。指针可以指向特定的位域。

For 循环

Zig的for循环语法比C更清晰,类型声明、初始化、测试和递增都是自动的。

数组

[_] 定义了一个大小未知的数组,后面必须跟元素的类型(例如 u8,无符号字节)和用 {} 括起来的初始化。数组的大小可以通过初始化推断出来。

Zig 中的多项指针

指向数组的指针只有在显式声明为多项指针时才能使用指针算术,例如 [*]const i32

解引用指针

当属性是个体数组位置的地址时,无法使用指针算术更新指针。 在这种情况下,const 仅防止使用另一个直接地址属性更改其值。要在 Zig 中解引用指针,可以使用 ptr.*,如下所示:

带标签的 break

Zig 可以在编译时做很多事情。比如初始化一个数组,这时可以使用带标签的 break。代码块用其名称后的 : 标记,然后使用 break 从该块返回一个值。

Zig 中的函数

Zig 中的函数使用 fn 声明,并且默认是静态的 (不能在其他文件中导入),除非 fn 前面有 pub。一个函数可以被 "内联"。 函数指针前面有 const,后面是函数原型。

Zig 中的 OOP

Struct 可以有函数。这里实现了一个简单的堆栈。此堆栈仅在其定义的模块内部声明和使用,并且访问和修改模块中与此示例无关的其他数据结构。堆栈最多可以有 81 个元素 (如在 stk 声明中看到的),每个元素都是 StkNode 类型。注意 Zig 中不存在 ++-- 运算符。应该改用等效的 +=-=。堆栈指针只是一个用作 stk 数组中索引的整数。注意指针 self (self 不是保留字,而只是一种约定) 没有像通常期望的那样作为参数显式传递。间接假定它是指向函数从中调用的堆栈实例的指针。在下面的示例中,堆栈弹出将使用 stack.pop(); 调用。在这种情况下,指针 self 对应于指向堆栈的指针。然后,该指针有点等同于 Java 或 C++ 中的 this。 函数 init() 是堆栈构造函数。 另请注意,函数 pop 和 push 是 "内联的"。

构建和执行 Zig 程序

构建可执行文件

要构建可执行程序,需要一个 main 函数,它指示程序的入口点,就像在 Java 或 C 程序中一样。当此函数存在时,可以将模块集编译为可执行代码。一个简单的程序可以将 main 函数放在与程序其余部分相同的文件中。在许多情况下,还可以在模块末尾插入一个 main 函数来创建一个可执行文件,以便独立调试该模块。一旦调试完成,可以注释掉此函数。

执行模块中的测试块

这可能是 Zig 作为编程语言的最佳特性。此环境通常用于测试,但也可以用于原型设计。

Zig 中的块类似于 C 或 Java 中的块,即 {} 之间的一些代码。测试块是以 test "message" { 开头并以 } 结尾的块,其中 "message" 是一个包含在执行测试时要显示的消息的字符串 (在这种情况下只有单词消息)。

测试块独立于可执行文件执行。最终的可执行文件不执行测试。给定 module.zig 中的测试块与该文件中的整个代码一起编译,并通过以下命令执行:

zig test module.zig

打印

std.debug.print 语句实际上是对标准 Zig 库 stddebug.zig 中的 print 函数的调用。第一个参数是格式字符串,第二个参数是一个匿名结构 (前面有一个点),其中包含要使用格式字符串显示的变量列表。由于格式字符串中没有格式化,因此该结构为空。这就是完成格式化打印的方式。默认情况下,这将在 stderr 中显示,如上所示。

这看起来就像在 C 语言中一样,但是这里有一个根本的区别。在 C 中,printf 函数在执行时动态解释格式字符串,而在 Zig 中,可以在编译时处理文字字符串和变量列表。一开始很难掌握这个原则。可以在编译时执行许多事情。

调试可执行文件

使用调试器通常不是一项简单的任务,除非在已经集成调试器的 IDE 中 (如在 Eclipse 或 Intellij IDEA 等 Java IDE 中) 或在集成开发工具包中 (如 C/C++ 的 w64devkit 中)。

以这种方式使用调试器的一个巨大不便是必须集成符号,这不仅使代码膨胀了对程序无用的信息,而且还需要在调试模式下编译,这会生成效率众所周知较低的可执行代码。 具有复杂系统实践经验的人知道,这可能是一项非常耗时的任务。

Zig 提供了一个非常方便的技巧来避免这些麻烦。

@breakpoint 内置函数

此内置函数在程序在调试器中运行时停止在源代码中插入 @breakpoint(); 的点。这实际上是一个有用的功能,可以调试优化的 Zig 代码而不需要符号。

所有需要的是在使用 @breakpoint(); 之前使用 std.debug.print 跟踪想要监视的变量。 这样就可以知道该时刻变量的值是多少。

Zig 中的底层编程

通过前面章节中的介绍和示例,已经可以开始编程 Zig 来编写通用应用程序。对于更高级的程序员,以下是对示例中已经使用但尚未明确注释的一些有趣的底层功能的更深入的分析。

所示示例的思想是构建一个设置 (初始化) 和显示 9x9 矩阵的模块。此矩阵将保存一个数独网格,也就是说,它将只包含带十进制数字的元素。网格的初始化应保证网格满足数独游戏的规则,因此它将不包含错误。

同时,这也是展示 Zig 底层功能的绝佳机会,至少是最引人注目的功能,而这些示例非常适合此目标。

这些示例背后的整个假设是将网格数字表示为一个位,该位位于其值给定的位置。 这种表示非常方便,可以检测网格中是否已存在数字 (这些是数独网格的基本规则)。 尽管如此,它是如此加密,因此只能在模块内部使用。

结论

总而言之,Zig 是一门强大的编译型语言,它融合了解释型语言的优点,例如编译时执行。Zig 在原型设计、鲁棒性和底层编程方面都表现出色,虽然性能是目标,但 Zig 在某些情况下牺牲了性能以换取鲁棒性。

加入我们

Zig 中文社区是一个开放的组织,我们致力于推广 Zig 在中文群体中的使用,有多种方式可以参与进来:

  1. 供稿,分享自己使用 Zig 的心得
  2. 改进 ZigCC 组织下的开源项目
  3. 加入微信群Telegram 群组

Metadata

Metadata

Assignees

No one assigned

    Labels

    日报daily report

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions