1.1. Hello, World

我们以1978年,c语言历史上经典的hello world案例来开始吧。C语言对Go语言的设计产生了很多影响。用这个例子,我们来讲解一些Go语言的核心特性:

//gopl.io/ch1/helloworld
package main

import "fmt"

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

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

$ go run helloworld.go

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

Hello, BF

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

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

$ go build helloworld.go

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

下面是运行我们的编译结果样例:

$ ./helloworld
Hello, BF

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

如果你执行go get gopl.io/ch1/helloworld,go能够自己从网上获取到这些代码,并且将这些代码放到对应的目录中。更详细的介绍在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一样的library。在main这个package里,main函数也是一个特殊的函数,这是我们整个程序的入口(译注:其实c系语言差不多都是这样)。main函数所做的事情就是我们程序做的事情。当然了,main函数一般完成的工作是调用其它packge里的函数来完成自己的工作,比如fmt.Println。

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

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

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

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

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

Go语言在代码格式上采取了很强硬的态度。gofmt工具会将你的代码格式化为标准格式,并且go工具中的fmt子命令会自动对特定package下的所有.go源文件应用gofmt。如果不指定package,则默认对当前目录下的源文件进行格式化。本书中的所有代码已经是执行过gofmt后的标准格式代码。你应该在自己的代码上也执行这种格式化。规定一种标准的代码格式可以规避掉无尽的无意义的撕逼。当然了,也可以避免由于代码格式导致的逻辑上的歧义。

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

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

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