1.4 第一个Go程序
上面对Go语言的特性进行了介绍,并从头开始搭建Go语言的开发环境。当Go语言开发环境配置完成后,我们就可以着手编写第一个Go程序了。根据惯例,这里给出一个最基本的Go程序,输出字符串“Hello World”。
为了提高编程效率,编写Go程序,一般来说都需要借助集成开发工具。这里我们先用Windows的“记事本”来编写,并用go build命令来编译Go程序。
1.4.1 搭建本书项目代码结构
在编写第一个Go程序之前,首先构建本书的项目代码结构。顶级目录为go.introduce,放于%GOPATH%目录下的src目录中。在go.introduce目录中,分别创建各章的子目录,如chapter01和chapter02。目录结构如图1.7所示。
图1.7 Go项目的目录结构示意图
工作区目录%GOPATH%(C:\GoWork)有3个子目录:
· src目录
用于以代码包的形式组织并保存Go源代码文件。这里的代码包与src下的子目录一一对应。例如,一个源代码文件被声明为属于代码包logging,那么它就应当被保存在src目录下名为logging的子目录中。
· pkg目录
用于存放经由go install命令构建安装后的代码包(包含Go库源代码文件)的.a归档文件。该目录与GOROOT目录下的pkg功能类似。区别在于,工作区中的pkg目录专门用来存放用户代码的归档文件。构建和安装用户源代码的过程一般会以代码包为单位进行,比如mylog包被编译安装后,将生成一个名为mylog.a的归档文件,并存放在当前工作区的pkg目录下的平台相关目录中。
· bin目录
与pkg目录类似,在通过go install命令完成安装后,保存由Go命令源代码文件生成的可执行文件。
说明
在Linux操作系统下,这个可执行文件一般是一个与源代码文件同名的文件。在Windows操作系统下,这个可执行文件的名称是源代码文件名称加.exe后缀。
1.4.2 创建并运行第一个Go程序
新建一个hello.go文本文件,并用“记事本”程序打开,编写脚本,参考示例程序1-1。
示例程序1-1 第一个Go程序:chapter01\code01\hello.go
每一个可独立运行的Go程序必定包含一个packagemain,在这个main包中必定包含一个入口函数main,而这个函数既没有参数也没有返回值。
Go语言的函数定义用关键词func进行定义。其中,import关键词用于导入包,由于第05行用到了fmt包中的Println方法,因此需要在第03行提前导入。fmt包中的Println方法可以将值打印到控制台中,即输出“Hello World”字符串。
注意
如果用“记事本”进行编辑,特别是出现中文的情况下,就需要将“记事本”的编码改成UTF-8,否则编译文件时会报错。保存文件名可以是任意名称,但必须以" .go "后缀结尾。
“记事本”可以用另存为的方式来选择UTF-8编码,如图1.8所示。
图1.8 “记事本”以另存为的方式选择文件的UTF-8编码
在目录go.introduce\chapter01\code01\中打开“命令提示符”窗口。输入“go build”命令进行编译。若编译成功则会在code01中生成code01.exe文件。如果输入“go run hello.go”,则会编译并运行程序,输出“Hello World”字符串。
如果输入“go install”,就会编译成code01.exe,并复制到%GOPATH%\bin目录下,由于此目录配置在PATH变量中,因此可以直接调用。例如,输入“code01”则调用code01.exe程序,输出“Hello World”字符串,如图1.9所示。
图1.9 GO编译命令
注意
go install能生成包,而go build不能生成包。
1.4.3 Go程序的编译
在Go语言1.9版本之后,默认情况下,Go的编译器会利用并发特性进行并发编译,这样可以充分利用多核的优势,因此编译速度非常快。go build命令在源代码编译过程中会根据源代码的依赖情况自动编译源代码依赖的包,并链接生成一个完整的文件。
go build常用的编译方法如下:
· 无参数编译:go build命令后面不跟任何参数,即无参数。首先Go编译器会在执行命令的当前目录下搜索*.go源代码文件,成功编译后会在当前目录下生成与当前目录名同名的文件。
· 指定包名编译:go build命令后面跟着包名,包名是相对于环境变量%GOPATH%下的src目录而言的。这种编译的好处是包内的文件数量的变化不需要调整编译命令。
· 文件列表编译:go build命令后面跟着文件名,文件名可以是多个,中间用空格隔开,比如go build main.go sum.go。这种编译方式对于一个目录当中有多个包的文件并需要指定文件来编译是比较适合的,但是需要注意文件列表的顺序,不同的文件列表顺序会影响编译结果。
另外,go build命令还有一些参数,下面罗列一些分别进行说明:
· -v:编译时显示包名。
· -p n:开启并发编译,默认情况下该值为计算机的逻辑核数。
· -a:强制重新构建。
· -n:打印编译时会用到的所有命令,但不真正执行。
· -x:打印编译时会用到的所有命令。
· -race:开启竞态检测,常用于并发模式下的共享变量检测。
· -o:后接文件名,强制对输出的文件进行重命名。
· -work:打印编译工作的临时目录。
· -gcflags:后面的参数可以是多个,用空格进行分隔,并用""进行包裹,这些参数将传递到go tool compile工具中进行调用。例如,go build -gcflags "-l -m"。
· -ldflags:后面的参数可以是多个,用空格进行分隔,并用""进行包裹,这些参数将传递到go tool link工具中进行调用。例如,go build -ldflags "-w -s"。这个命令可以隐藏所有代码实现相关的信息,并减少生成文件的大小。其中,-w可以移除调试信息(无法使用gdb调试),-s可以移除符号表。
Go支持交叉编译,比如我们在Windows操作系统上进行开发,但是需要部署到Linux操作系统上,由于不同操作系统的底层实现有差异,因此为了更好地提升性能,可以在Windows操作系统上通过配置环境变量来生成Linux操作系统下的文件。例如,在Windows操作系统的命令窗口执行:
set GOOS=linux set GOARCH=amd64 go build -o linux-main
1.4.4 Go的帮助系统
我们学习任何一种语言都可以参考其内置的帮助系统。Go语言也不例外,打开“命令提示符”窗口,输入“go help”后按Enter键,则会显示go相关命令,如图1.10所示。
图1.10 go help帮助信息
从图1.10中可以看出,go可以支持的命令有bug、build、clean、doc、env、fmt、generate、install、test、get、run、tool和vet等。下面重点介绍几个。
(1)fmt可以用gofmt对源代码进行格式化,这个工具非常有用。我们在提交代码之前,建议都用这个官方提供的代码格式化工具对代码进行格式化,然后提交代码到Git或者TFS服务器上。
Git或者TFS是分布式版本控制系统,可以实现团队对于代码的版本管理工作,防止代码版本不一致的问题。另外,后面介绍的Go语言代码编辑器中会用到此工具来格式化代码,可以说这个工具非常实用,而且可以让代码更加规范和优雅。
(2)test命令可以测试包中的函数,其中分为单元(unit)测试和基准(benchmark)测试,单元测试一般验证函数的正确性,基准测试用于测试性能。test可以指定参数go test -cover来查看测试覆盖率。测试覆盖率通过执行某包的测试用例来确认代码被执行的程度。如果覆盖率是100%,那么证明单元测试的时候所有的代码路径都会执行一遍,如果运行结果都正确,则该函数发生bug的可能性将大大降低。
强烈建议
在命令行中执行go help testflag来查看一些test命令的细节信息,这个命令可以让我们知道-memprofile、-mutexprofile、-trace和-cpuprofile等参数的作用。
(3)tool命令允许特定的go工具,比如compile工具,我们可以在命令行中输入go doc cmd/compile来查看具体的说明信息。compile工具的基本用法为:
go tool compile [flags] file...
注意,这个命令的file文件必须是Go源代码文件,并且属于同一个包,[flags]参数主要有以下几个:
· -N:禁止编译器优化。
· -S:打印汇编语言列表到标准输出窗口(只打印代码code部分)。
· -S –S:打印汇编语言列表到标准输出窗口(只打印代码code和数据data)。
· -blockprofile file:将编译期间采样的block profile信息写入文件file中。
· -cpuprofile file:将编译期间采样的cpu profile信息写入文件file中。
· -dynlink:实验特性,允许共享库引用Go symbols。
· -e:移除错误报告的数量限制(默认限制是10)。
· -h:在检测到第一个错误时停止栈跟踪。
· -l:禁止函数内联。
· -lang version:设置Go语言的版本。
· -m:打印编译器的优化策略信息。
· -memprofile file:将编译期间采样的memory profile信息写入文件file中。
· -memprofilerate rate:设置runtime.MemProfileRate的内存采集频率。
· -mutexprofile file:将编译期间采样的mutex profile信息写入文件file中。
· -race:开启竞争检测。
· -traceprofile file:将执行的跟踪信息execution trace信息写入文件file中。
go tool compile还有一些参数,这里就不一一阐述了,感兴趣的读者可以直接阅读帮助文档进行学习。如果需要继续查看某个命令的帮助信息,如build命令,则可以用go help build查看,如图1.11所示。
图1.11 go build帮助信息
从图1.11可以看出,前面列出的go build参数实际上都是可以利用此命令查看得到的,其实官方的英文对参数的描述更加准确,因此建议多阅读官方的帮助文档来学习。
(4)vet命令是非常有用的一个工具。每个Go语言的开发者都应该了解并会使用这个内置的工具。vet命令会对Go源代码进行静态检测分析,以发现可能的bug或者异常并进行提示,它是Go tool套件工具中go tool vet工具的封装,和Go编译器一起发布,不依赖任何第三方库,因此可以很方便地调用。
可以用go vet hello.go对文件hello.go进行静态分析,用go tool vet help或者go doc cmd/vet命令查看更多细节信息。vet工具可以对代码进行如下检测(包含但不限于):
· 检查赋值语句。
· 检查代码中对代码包sync/atomic的使用是否正确。
· 检查编译标签的有效性。
· 检查复合结构实例的初始化代码。
· 检查那些拥有标准命名的方法的签名。
· 检查代码中对打印函数的使用是否正确。
· 检查代码中对在range语句块中迭代赋值的变量的使用是否正确。
· 检查结构类型的字段的标签格式是否标准。
· 查找并报告不可到达的代码。
利用这个工具,我们可以对code01目录中的文件hello_vet.go进行静态分析,以发现潜在的bug。在目录code01中打开命令行,执行如下命令:
go vet hello_vet.go
则会输出如下信息:
hello_vet.go:5:2: cannot find package "fmt1" in any of: C:\Go\src\fmt1 (from $GOROOT) C:\GoWork\src\fmt1 (from $GOPATH)
由此可见,vet工具提示在%GOROOT%和%GOPATH%目录下没有找到文件hello_vet.go内第5行语句需要导入的"fmt1"包,正确的包名应该是"fmt"。
注意
vet工具在检测到潜在bug时会停止后续检测,因此当修复某个bug后,需要用vet工具再次进行检测。