建立一个用于编写Go程序的工作目录go-examples
,其绝对路径为/home/go-examples
.开始编写我们的第一个Go程序。
一、在go-examples
下创建一个文件hello.go
复制代码代码如下:
1 | //hello.go |
我们来分析下这个程序:
- 1、程序中的第2行这个是必须的。所有的Go文件以
package <something>
开头,对于独立运行的执行文件必须是package main
; - 2、第4行说需要将”fmt”包加入main。不是main的其他包都被称为库,其他许多编程语言有着类似的概念。
- 3、第1行和第4行中的
//
和/*—*/
都是注释 - 4、
package main
必须首先出现,紧跟着是import
。在Go中,package
总是首先出现,然后是import
,然后是其他所有内容。当Go 程序在执行的时候,首先调用的函数是main.main()
,这是从C 中继承而来。这里定义了这个函数; - 5、第8行调用了来自于fmt包的函数打印字符串到屏幕
二、编译和运行代码
编译该源文件并执行生成的可执行文件
1 | go build hello.go |
通过go build
加上要编译的Go源文件名,我们即可得到一个可执行文件,默认情况下这个文件的名字为源文件名字去掉.go
后缀。当然我们也可以通过-o
选项来指定其他名字:
1 | go build -o firstgo hello.go |
如果我们在go-examples
目录下直接执行go build
命令,后面不带文件名,我们将得到一个与目录名同名的可执行文件:
1 | go build |
三、程序入口点(entry point)和包(package)
Go保持了与C家族语言一致的风格:即目标为可执行程序的Go源码中务必要有一个名为main的函数,该函数即为可执行程序的入口点。除此之外 Go还增加了一个约束:作为入口点的main函数必须在名为main的package中。正如上面hellogo.go
源文件中的那样,在源码第 一行就声明了该文件所归属的package为main。
Go去除了头文件的概念,而借鉴了很多主流语言都采用的package的源码组织方式。package
是个逻辑概念,与文件没有一一对应的关系。 如果多个源文件都在开头声明自己属于某个名为foo的包,那这些源文件中的代码在逻辑上都归属于包foo(这些文件最好在同一个目录下,至少目前 的Go版本还无法支持不同目录下的源文件归属于同一个包)。
我们看到hellogo.go
中import
一个名为fmt的包,并利用该包内的Printf
函数输出”Hello, Go!”。直觉告诉我们fmt包似乎是一个标准库中的包。没错,fmt
包提供了格式化文本输出以及读取格式化输入的相关函数,与C中的printf
或 scanf
等类似。我们通过import
语句将fmt包导入我们的源文件后就可以使用该fmt包导出(export)的功能函数了(比如 Printf)。
在C中,我们通过static
来标识局部函数还是全局函数。而在Go中,包中的函数是否可以被外部调用,要看该函数名的首母是否为大写。这是一种 Go语言固化的约定:首母大写的函数被认为是导出的函数,可以被包之外的代码调用;而小写字母开头的函数则仅能在包内使用。在例子中你也看到了 fmt
包的Printf
函数其首母就是大写的。
四、GOPATH
把上面的hellogo.go
稍作改造,拆分成两个文件:main.go
和hello.go
1 | //hello.go |
用go build
编译main.go
结果如下
1 | go build main.go |
编译器居然提示无法找到hello
这个package
,而hello.go
中明明定义了package hello
了。这是怎么回事呢?原来go compiler
搜索package
的方式与我们常规理解的有不同,Go在这方面也有一套约定,这里面涉及到一个重要的环境变量:GOPATH
。我们可以使用go help gopath来查看一下有关gopath的manual。
Go compiler
的package
搜索顺序是这样的,以搜索hello
这个package
为例:
- 首先,
Go compiler
会在GO安装目录(GOROOT
,这里是/home/go/
)下查找是否有src/pkg/hello
相关包源码;如果没有则继续; - 如果
export GOPATH=PATH1:PAHT2
,则Go compiler
会依次查找是否存在PATH1/src/hello
、PATH2/src/hello
;配置在GOPATH中的PATH1
和PATH2
被称作workplace
; - 如果在上述几个位置均无法找到
hello
这个package
,则提示出错。
在本例子中,我们尚未设置过GOPATH
环境变量,也没有建立类似PATH1/src/hello
这样的路径,因此Go compiler
显然无法找到hello
这个package
了。我们来设置一下GOPATH变量并建立相关目录:
1 | export GOPATH=/home/go-examples/ |
Go install
将main.go
移到src/main
中,这样这个demo project
显得更加合理,所有源码均在src下:
1 | cd src/ |
Go提供了install
命令,与build
命令相比,install
命令在编译源码后还会将可执行文件或库文件安装到约定的目录下。我们以main目录为例:
1 | cd main/ |
install
命令执行后,我们发现main目录下没有任何变化,原先build时产生的main可执行文件也不见了踪影。别急,Go install
也有一套自己的约定:
go install
(在src/DIR
下)编译出的可执行文件以其所在目录名(DIR)命名go install
将可执行文件安装到与src
同级别的bin
目录下,bin
目录由go install
自动创建go install
将可执行文件依赖的各种package
编译后,放在与src
同级别的pkg
目录下
现在我们来看看bin
目录:
1 | ls |
的确出现一个bin
目录,并且刚刚编译的程序main
在bin
下面。
hello.go
编译后并非可执行程序,在编译main的同时,由于main依赖hello package
,因此hello
也被关联编译了。这与单独在hello
目录下执行install
的结果是一样的,我们试试:
1 | cd hello/ |
在我们的workspace
(go-examples目录)下出现了一个pkg
目录,pkg
目录下是一个名为linux_386
的子目录,其下面有一个文件:hello.a
。这就是我们install
的结果。hello.go
被编译为hello.a
并安装到pkg/linux_386
目录下了。
.a
这个后缀名让我们想起了静态共享库,但这里的.a
却是Go独有的文件格式,与传统的静态共享库并不兼容。但Go语言的设计者使用这个后缀名似乎是希望 这个.a
文件也承担起Go语言中”静态共享库”的角色。我们不妨来试试,看看这个hello.a
是否可以被Go compiler
当作”静态共享库”来对待。我们移除src
中的hello
目录,然后在main
目录下执行go build
:
1 | go build |
Go编译器提示无法找到hello
这个包,可见目前版本的Go编译器似乎不理pkg下的.a
文件。这个issue也印证了这一点,不过后续Go版本很可能会支持链接.a
文件。毕竟我们在使用第三方package的时候,很可能无法得到其源码,并且在每个项目中都保存一份第三方包的源码也十分不利于项目源码的后期维护。