1.1. Hello, World

我们以1978年出版的C语言圣经《The C Programming Language》中经典的“hello world”案例来开始吧(译注:本书作者之一Brian W. Kernighan也是C语言圣经一书的作者)。C语言对Go语言的设计产生了很多影响。用这个例子,我们来讲解一些Go语言的核心特性:

gopl.io/ch1/helloworld
package main

import "fmt"

func main() {
    fmt.Println("Hello, 世界")
}

Go是一门编译型语言,Go语言的工具链将源代码和其依赖一起打包,生成机器的本地指令(译注:静态编译)。Go语言提供的工具可以通过go命令下的一系列子命令来调用。最简单的一个子命令就是run。这个命令会将一个或多个文件名以.go结尾的源文件,和关联库链接到一起,然后运行最终的可执行文件。(本书将用$表示命令行的提示符。)

$ go run helloworld.go

毫无意外,这个命令会输出:

Hello, 世界

Go语言原生支持Unicode标准,所以你可以用Go语言处理世界上的任何自然语言。

如果你希望自己的程序不只是简单的一次性实验,那么你一定会希望能够编译这个程序,并且能够将编译结果保存下来以备将来之用。这个可以用build子命令来实现:

$ go build helloworld.go

这会创建一个名为helloworld的可执行的二进制文件(译注:在Windows系统下生成的可执行文件是helloworld.exe,增加了.exe后缀名),之后你可以在任何时间去运行这个二进制文件,不需要其它的任何处理(译注:因为是静态编译,所以也不用担心在系统库更新的时候冲突,幸福感满满)。

下面是运行我们的编译结果样例(译注:在Windows系统下在命令行直接输入helloworld.exe命令运行):

$ ./helloworld
Hello, 世界

本书中我们所有的例子都做了一个特殊标记,你可以通过这些标记在 http://gopl.io 在线网站上找到这些样例代码,比如这个

gopl.io/ch1/helloworld

如果你执行 go get gopl.io/ch1/helloworld 命令,go命令能够自己从网上获取到这些代码(译注:需要先安装Git或Hg之类的版本管理工具,并将对应的命令添加到PATH环境变量中),并且将这些代码放到对应的目录中(译注:序言已经提及,需要先设置好GOPATH环境变量,下载的代码会放在 $GOPATH/src/gopl.io/ch1/helloworld 目录)。更详细的介绍在2.6和10.7章节中。

我们来讨论一下程序本身。Go语言的代码是通过package来组织的,package的概念和你知道的其它语言里的libraries或者modules概念比较类似。一个package会包含一个或多个.go结束的源代码文件。每一个源文件都是以一个package xxx的声明语句开头的,比如我们的例子里就是package main。这行声明语句表示该文件是属于哪一个package,紧跟着是一系列import的package名,表示这个文件中引入的package。再之后是本文件本身的代码。

Go的标准库已经提供了100多个package,用来完成一门程序语言的一些常见的基本任务,比如输入、输出、排序或者字符串/文本处理。比如fmt这个package,就包括接收输入、格式化输出的各种函数。Println是其中的一个常用的函数,可以用这个函数来打印一个或多个值,该函数会将这些参数用空格隔开进行输出,并在输出完毕之后在行末加上一个换行符。

package main是一个比较特殊的package。这个package里会定义一个独立的程序,这个程序是可以运行的,而不是像其它package一样对应一个library。在main这个package里,main函数也是一个特殊的函数,这是我们整个程序的入口(译注:其实C系语言差不多都是这样)。main函数所做的事情就是我们程序做的事情。当然了,main函数一般是通过是调用其它packge里的函数来完成自己的工作,比如fmt.Println。

我们必须告诉编译器如何要正确地执行这个源文件,需要用到哪些package,这就是import在这个文件里扮演的角色。上述的hello world例子只用到了一个其它的package,就是fmt。一般情况下,需要import的package可能不只一个。

这也正是因为go语言必须引入所有要用到的package的原则,假如你没有在代码里import需要用到的package,程序将无法编译通过,同时当你import了没有用到的package,也会无法编译通过(译注:Go语言编译过程没有警告信息,争议特性之一)。

import声明必须跟在文件的package声明之后。在import语句之后,则是各种方法、变量、常量、类型的声明语句(分别用关键字func, var, const, type来进行定义)。这些内容的声明顺序并没有什么规定,可以随便调整顺序(译注:最好还是定一下规范)。我们例子里的程序比较简单,只包含了一个函数。并且在该函数里也只调用了一个其它函数。为了节省空间,有些时候的例子我们会省略package和import声明,但是读者需要注意这些声明是一定要包含在源文件里的。

一个函数的声明包含func这个关键字、函数名、参数列表、返回结果列表(我们例子里的main函数参数列表和返回值都是空的)以及包含在大括号里的函数体。关于函数的更详细描述在第五章。

Go语言是一门不需要分号作为语句或者声明结束的语言,除非要在一行中将多个语句、声明隔开。然而在编译时,编译器会主动在一些特定的符号(译注:比如行末是,一个标识符、一个整数、浮点数、虚数、字符或字符串文字、关键字break、continue、fallthrough或return中的一个、运算符和分隔符++、--、)、]或}中的一个) 后添加分号,所以在哪里加分号合适是取决于Go语言代码的。例如:在Go语言中的函数声明和 { 大括号必须在同一行,而在x + y这样的表达式中,在+号后换行可以,但是在+号前换行则会有问题(译注:以+结尾的话不会被插入分号分隔符,但是以x结尾的话则会被分号分隔符,从而导致编译错误)。

Go语言在代码格式上采取了很强硬的态度。gofmt工具会将你的代码格式化为标准格式(译注:这个格式化工具没有任何可以调整代码格式的参数,Go语言就是这么任性),并且go工具中的fmt子命令会自动对特定package下的所有.go源文件应用gofmt工具格式化。如果不指定package,则默认对当前目录下的源文件进行格式化。本书中的所有代码已经是执行过gofmt后的标准格式代码。你应该在自己的代码上也执行这种格式化。规定一种标准的代码格式可以规避掉无尽的无意义的撕逼(译注:也导致了Go语言的TIOBE排名较低,因为缺少撕逼的话题)。当然了,这可以避免由于代码格式导致的逻辑上的歧义。

很多文本编辑器都可以设置为保存文件时自动执行gofmt,所以你的源代码应该总是会被格式化。这里还有一个相关的工具,goimports,会自动地添加你代码里需要用到的import声明以及需要移除的import声明。这个工具并没有包含在标准的分发包中,然而你可以自行安装:

$ go get golang.org/x/tools/cmd/goimports

对于大多数用户来说,下载、build package、运行测试用例、显示Go语言的文档等等常用功能都是可以用go的工具来实现的。这些工具的详细介绍我们会在10.7节中提到。