来个 Hello, World


我在这边使用的是 Windows 中的 Go 1.13 版本,你可以至Go 的官方网站下载安装 Go。

如果想来点不同的安装方式,可以参考〈门外汉的 Go 轻量开发环境〉,在 Raspberry Pi 上的 Docker 容器中创建相关环境,就目前为止。

你至少得设定GOROOT环境变量,这会是你的 Go 安装目录。

go run

要编写第一个 Hello, World 程序,你可以创建一个 main.go,在当中编写以下的内容:

package main

import "fmt"

func main() {
    fmt.Println("Hello, World")
    fmt.Println("哈啰!世界!")
}

每个 .go 源码,都必须从package定义开始,而对于包括程序进入点main函数的 .go 源码,必须是在package main之中,为了要能输出消息,这边使用了fmt包(package)之中的Println函数,开头的大写 P 表示这是个公开的函数,可以在包之外进行调用。

Go 的创建者之一也是 UTF-8 的创建者,因此,Go 可以直接处理多国语言,只要你确定编辑器编码为 UTF-8 就可以了,如果你使用 vim,可以在 vim 的命令模式下输入:set encoding=utf-8,或者是在 .vimrc 之中增加一行set encoding=utf-8

Go 可以用直译的方式来执行程序,第一个 Hello, World 程序就是这么做的,执行go run指定你的源码文件名就可以了:

$ go run main.go
Hello, World
哈啰!世界!

package 与 GOPATH

那么,一开始的package是怎么回事?试着先来创建一个 hello.go:

package hello

import "fmt"

func HelloWorld() {
    fmt.Println("Hello, World")
}

记得,package中定义的函数,名称必须是以大写开头,其他包外的程序,才能进行调用,若函数名称是小写,那么会是包中才可以使用的函数。

接着,原本的 main.go 修改为:

package main

import "hello"

func main() {
    hello.HelloWorld()
}

现在显然地,main.go 中要用到方才创建的hello包中的HelloWorld函数,这时package的设定就会发挥一下效用,你得将 hello.go 移到 src/hello 目录之中,也就是目录名称必须符合package设定之名称。

同样地,你可以将 main.go 移到 src/main 目录之中,以符合package的设定。

而 src 的位置,必须是在GOROOT或者是GOPATH的路径中可以找到,当 Go 需要某包中的元素时,会分别到这两个环境变量的目录之中,查看 src 中是否有相应于包的源码存在。

为了方便,通常会设定GOPATH,例如,指向目前的工作目录:

set GOPATH=c:\workspace\go-exercise

如果没有设定GOPATH的话,Go 默认会是使用者目录的 go 目录,虽然目前GOPATH中只一个目录,不过GOPATH中可以设定数个目录,现在我的 go-exercise 目录底下会有这些东西:

go-exercise
          └─src
              ├─hello
              │      hello.go
              │
              └─main
                      main.go

接着在 go 目录中执行指令go run src/main/main.go的话,你就会看到 Hello, World 了。

go build

如果想编译源码为可执行文件,那么可以使用go build,例如,直接在 go 目录中执行go build src/main/main.go,就会在执行指令的目录下,产生一个名称为 main.exe 的可执行文件,可执行文件的名称是来自己指定的源码文件文件名,执行产生出来的可执行文件就会显示 Hello, World。

你也可以创建一个 bin 目录,然后执行go build -o bin/main.exe src/main/main.go,这样产生出来的可执行文件,就会被放在 bin 底下。

go install

每次使用go build,都是从源码编译为可执行文件,这比较没有效率,如果想要编译时更有效率一些,可以使用go install,例如,在目前既有的目录与源码架构之下,于 go 目录中执行go install hello的话,你就会发现有以下的内容:

go-exercise
        ├─bin
        │      main.exe
        │
        ├─pkg
        │  └─windows_amd64
        │          hello.a
        │
        └─src
            ├─hello
            │      hello.go
            │
            └─main
                    main.go

go install packageName表示要安装指定名称的包,如果是main包,那么会在 bin 中产生可执行文件,如果是公用包,那么会在 pkg 目录的$GOOS_$GOARCH目录中产生 .a 文件,你可以使用go env来查看 Go 使用到的环境变量,例如:

set GO111MODULE=
set GOARCH=amd64
set GOBIN=
set GOCACHE=C:\Users\Justin\AppData\Local\go-build
set GOENV=C:\Users\Justin\AppData\Roaming\go\env
set GOEXE=.exe
set GOFLAGS=
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=C:\Users\Justin\go
set GOPRIVATE=
set GOPROXY=https://proxy.golang.org,direct
set GOROOT=C:\Winware\Go
set GOSUMDB=sum.golang.org
set GOTMPDIR=
set GOTOOLDIR=C:\Winware\Go\pkg\tool\windows_amd64
set GCCGO=gccgo
set AR=ar
set CC=gcc
set CXX=g++
set CGO_ENABLED=1
set GOMOD=
set CGO_CFLAGS=-g -O2
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-g -O2
set CGO_FFLAGS=-g -O2
set CGO_LDFLAGS=-g -O2
set PKG_CONFIG=pkg-config
set GOGCCFLAGS=-m64 -mthreads -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=C:\Users\Justin\AppData\Local\Temp\go-build282125542=/tmp/go-build -gno-record-gcc-switches

.a 文件是编译过后的包,因此,你看到的 hello.a,就是 hello.go 编译之后的结果,如果编译时需要某个包,而对应的 .a 文件存在,且源码自上次编译后未曾经过修改,那么就会直接使用 .a 文件,而不是从源码开始编译起。

os.Args

那么,如果想在执行 Go 程序时使用命令行实参呢?可以使用os包的Args,例如,写一个 main.go:

package main

import "os"
import "fmt"

func main() {
    fmt.Printf("Command: %s\n", os.Args[0])
    fmt.Printf("Hello, %s\n", os.Args[1])
}

os.Args是个数组,索引从 0 开始,索引 0 会是编译后的可执行文件名称,索引 1 开始会是你提供的实参,例如,在执行过 go build 或 go install 之后,如下直接执行编译出来的执行文件,会产生的消息是…

$ ./bin/main Justin
Command: ./bin/main
Hello, Justin

go doc

fmt的 Printf,就像是 C 的printf,可用的格式控制可参考Package fmt的说明。实际上,Go 本身附带了说明文件,可以执行go doc <pkg> <sym>[.<method>]来查询说明。例如:

$ go doc fmt.Printf
func Printf(format string, a ...interface{}) (n int, err error)

    Printf formats according to a format specifier and writes to standard
    output. It returns the number of bytes written and any write error
    encountered.




展开阅读全文