菜单 学习猿地 - LMONKEY

VIP

开通学习猿地VIP

尊享10项VIP特权 持续新增

知识通关挑战

打卡带练!告别无效练习

接私单赚外块

VIP优先接,累计金额超百万

学习猿地私房课免费学

大厂实战课仅对VIP开放

你的一对一导师

每月可免费咨询大牛30次

领取更多软件工程师实用特权

入驻
0
0

Go 注释的魔力

原创
05/13 14:22
阅读数 552

注释是记录和交流代码信息的宝贵工具。它们几乎是所有编程语言的共同特性,Go也不例外。然而,Go程序中的注释所能做的远不止给读者提供代码的信息。在本文中,我将重点介绍一些不太为人所知的用法,这些用法在Go中具有特殊的几乎是不可思议的功能。

Go 注释语法

继承于 C 语言的语法,Go 支持常见的单行注释和多行注释,使用 // 和 /* ... */ ,如下:

// 行尾的所有内容都是注释
// 其他行需要另一个 `//` 标记

var foo int;    // 注释可以从一行中的任何地方开始

/* 直到结束标记都是注释
   这包括新的一行

   和空行 */

/*多行注释也可以是单行的 */

即使是对Go有一点编程经验的人也会从他们读过的代码甚至是他们自己写的代码中识别出注释语法。尽管Go编译器会忽略注释的内容,但这并不意味着Go工具集会完全忽略注释的内容。本文的提示将显示一组特殊格式的注释,以及它们在Go中的使用方式。

godoc 文档文本

Go 中最常见的「神奇」注释形式可能是对 Go 内置文档工具 godoc 的注释。godoc 的工作原理是扫描所有 .go 的数据。在一个包中的文件(忽略任何 _test.go 文件)中查找声明之前的注释(没有任何中间代码或空行)。godoc 将使用注释的文本来形成包的文档。

例如,要记录一个函数,只需在其声明之前的行上放置一行或多行注释:

// Foo 将是 foo 给定的字符串,如果字符串不能被 foo 赋值
// 返回一个 error
func Foo(s string) error {
    ...
}

导出包级类型、函数、方法、变量或常量声明之前,可以使用相同的注释样式:

package objects

// Object是一个通用的东西
type Object struct {}

// Bar将是bar的Object,如果不是则返回error
func (o Object) Bar() error {
    return nil
}

// List包含所有当前注册的Object
var List []Object

// MaxCount确定允许的最大对象数
const MaxCount = 50

因为只导出包级别的注释,所以开发人员可以自由地在方法/函数体中使用注释,而不必担心注释会被不小心添加到公共文档中。

godoc 也提供一种通过解析包申明前的注释来生成包级别的文档的方法:

// 包对象完成基本对象的叙述
package objects

需要注意的是 godoc 在生成包的索引(请看示例 https://golang.org/pkg/ )的时候使用第一句包文档注释,因此一定要写相应的包描述。

如你所见,注释产生一种简单的方法来为开发者和使用者提供文档,而不需要复杂的语法和额外的文档文件。

构建约束

Go 语言中注释的第二个特殊用途是构建约束。

Go 作为一种编程语言的关键特性是它支持各种操作系统和体系结构。通常情况下,相同的代码可以用于多个平台。但是在某些情况下,特定于操作系统或体系结构的代码应该只用于特定的目标。标准的 go build 工具可以通过理解以操作系统名称和/或体系结构结尾的程序应该只用于匹配这些标记的目标来处理某些情况。例如,一个名为 foo_linux.go 的文件,将只会被 Linux 系统编译。 foo_amd64.go 用于 AMD64 架构,foo_windows_amd64.go 用于运行在 AMD64 架构上的64位 Windows 系统。

然而,这些命名约束在更复杂的情况下会失败,例如当同一代码可以用于多个(但不是所有)操作系统时。在这些情况下,Go 有构建约束的概念——在编译 Go 程序时,go build 将读取经过特殊设计的注释,以确定要引用哪些文件。

构建约束的注释遵循以下规则:

  • 以前缀 +build 开始,后跟一个或多个空格
  • 位于文件顶部在包声明之前
  • 在它和包声明之间至少有一个空行,以防止它被视为包文档

而不是将文件命名为 foo_linux.go。我们可以把下面的注释放在文件 foo.go 的开头:

// +build linux

然而,当引用多个体系结构和/或操作系统时,构建约束的威力就显现出来了。Go为组合构建约束制定了以下规则:

  • 以 !开头的构建标记是无效的
  • 用空格分隔的构建标记在逻辑上是或
  • 用逗号分隔的构建标记在逻辑上是和
  • 在多行上构建约束是逻辑和

根据上面的规则,下面的约束将把文件限制为 Linux 或 Darwin (MacOS):

// +build linux darwin

而这个约束同时需要 Windows 和 i386:

// +build windows,386

上面的约束也可以写在下面的两行中:

// +build windows
// +build 386

除了指定操作系统和体系结构之外,构建约束可以通过 ignore 标记的常见用法来完全忽略文件(任何不匹配有效架构或操作系统的文本是可以工作的):

// +build ignore

应该注意的是,这些构建约束(以及前面提到的命名约定)也适用于测试文件,因此可以以类似的方式执行特定于体系结构/操作系统的测试。

构建约束的全部功能在这里的 go build 文档中有详细说明。

生成代码

Go 语言注释的另一个有趣的用法是通过 go generate 命令生成代码。 go generate 是 Go 语言标准工具包的一部分,它通过运行用户指定的外部命令以编程方式生成源(或其他)文件。go generate 的工作方式是扫描 .go 程序,寻找其中包含要运行的命令的特殊注释,然后执行它们。

具体来说,go generate 查找以 go:generate 开头的注释(注释标记和文本开始之间没有空格),如下:

//go:generate <command> <arguments>

与构建约束不同,go:generate 注释可以位于 .go 源文件中的任何位置(尽管典型的 Go 语言习惯用法是将它们放在文件的开头)。

go generate 的常见用法是通过这里提供的 stringer 工具提供人类可读的常量数值。stringer 文档提供了下面的示例来解释它的操作。给定一个自定义类型,Pill ,枚举常量:

type Pill int

const (
  Placebo Pill = iota
  Aspirin
  Ibuprofen
  Paracetamol
  Acetaminophen = Paracetamol
)

运行命令 stringer -type Pill 将会创建一个新的源文件 pill_string.go,它提供了以下方法:

func (p Pill) String() string

例如,它允许打印常量的名称:

fmt.Printf("pill type: %s", pill)

但是需要记住包中每个适用类型的正确命令和参数可能很复杂,因此我们可以将以下注释添加到包的.go 文件中。

//go:generate stringer -type=Pill

然后运行 go generate 将会触发 stringer 的调用,使用正确的参数来创建我们的 Pill 字符串方法。在这种情况下,我们可以看到 stringer 和 go generate 对程序员带来的巨大好处,尤其是在包中有多个自定义类型的情况下。

Cgo

我将讲述的 Go 语言中注释的最后一个特殊用法是 C 语言集成工具 Cgo。 Cgo 允许 Go 程序直接调用 C 语言代码,允许在Go 中重用已建立的 C 语言库。要在 Go 程序中使用 Cgo,首先要导入伪包「C」。一旦导入,Go 程序就可以引用像 C.size_t 这样的原生 C 类型和 C.putchar() 这样的函数。

然而,C 语言编程的某些方面是很难转换的。为了处理这些问题,Cgo 特别使用了 import 「C」 语句之前的注释(在Cgo术语中称为 序言 )来提供各种 C 语言特定的配置项。

其中一项是 #include 指令。几乎每个 C 程序都需要 #include 指令来指示头文件的位置。Go 语言没有任何本地对应的命令(import 在包上工作,而不是头文件),所以 Cgo 解析序言中的 #include 语句。例如:

// #include <stdio.h>
// #include <errno.h>
import "C"

序言部分的注释不仅仅限于 #include语句。实际上,import 语句之前的任何注释都将被视为标准的 C 代码,然后可以通过 C 包进行引用。比如,序言:

// #include <stdio.h>
//
// static void myprint(char *s) {
//     printf("%s\n", s)
// }
import "C"

然后我们可以在 Go 中引用这个新定义的 C 函数,如下所示:

C.myprint(C.String("foo"))

最后,为了处理编译器和类似的选项,Cgo 引入了 # Cgo 指令,该指令可用于设置环境变量、编译器标记和运行 pkg-config 命令,如下所示:

// #cgo CFLAGS: -DPNG_DEBUG=1
// #cgo amd64 386 CFLAGS: -DX86=1
// #cgo LDFLAGS: -lpng
// #cgo pkg-config: png cairo
// #include <png.h>
import "C"

所有这些序言的定义帮助使用 Cgo 的程序与 go 构建工具(几乎)无缝集成,而不需要额外复杂的的创建文件或其他脚本。

发表评论

0/200
0 点赞
0 评论
收藏
为你推荐 换一批