1. 程式人生 > >Go基礎系列(3):構建go程式

Go基礎系列(3):構建go程式

hello world

從一個簡單的程式開始解釋,將下面的內容放進test.go檔案中,路徑隨意:

package main

import (
    "fmt"
)

func main() {
    fmt.Println("Hello World")
}

Go通過包的方式管理程式,每個Go原始碼檔案都必須宣告自己所在的包,正如上面的package main宣告自己所在的包是main包。

每個程式都必須有一個main包,main包作為整個程式的編譯入口包,main包中的main()函式作為程式的執行入口。

import關鍵字用來匯入其它包,匯入某個包之後就能在當前檔案中使用這個包中的函式,例如上面的main包匯入fmt包後,可以使用fmt包中的函式Println()。

然後可以使用go的build工具編譯這個test.go檔案:

$ go build test.go

編譯後,將在當前路徑下生成一個可執行二進位制檔案:Windows下生成的是test.exe檔案,Unix下生成的是test檔案。既然是可執行檔案,當然可以直接執行:

$ ./test

將輸出"Hello World"。

也可以直接通過go的run工具將編譯和執行兩個步驟合二為一:

$ go run test.go
Hello World

go run不會生成可執行的二進位制檔案,它實際上是將編譯得到的檔案放進一個臨時目錄,然後執行,執行完後自動清理臨時目錄。

關於包和go檔案

每個go程式碼檔案只能且必須使用package語句宣告一個包,也就是說一個檔案中不能包含多個包。

Go中有兩種型別的包,或者說有兩種型別的檔案:

  1. 編譯後成為可執行檔案的包,也就是main包編譯後的得到的檔案
  2. 編譯後成為共享庫的包,只要go程式檔案中宣告的不是main包,就是庫檔案

注意: 在go的官方文件中將go的二進位制可執行程式稱為命令,有時候還會將go的原始碼檔案稱為命令的原始檔。可執行程式和包相反,包一般是作為"庫"檔案存在,用於匯入而非用於執行

共享庫中包含一些函式,這些函式比較通用,所以放進共享庫方便函式複用。例如fmt包中的Println函式,到處都在使用這個函式,且因為fmt包是標準庫(Standary library),無論是誰都可以去使用這個包。

有兩種型別的庫檔案:標準庫和第三方的庫。標準庫是隨Go安裝的時候放在go安裝目錄下的($GOROOT/src/

),第三方庫是放在workspace下的。關於workspace後文再說。

共享庫可以被import匯入(例如fmt包)。由於匯入操作是在編譯期間實現的,共享庫中不應該包含任何輸出型語句。

Go中對庫檔案要求比較嚴格,或者說強制性的規範。它要求庫檔案中package宣告的包名必須和目錄名稱相同,且同一個目錄下只允許有一個包,但同一個目錄下可以有多個庫檔案片段,只不過這些庫檔案中必須都使用package宣告它的包名為目錄名。例如:

src/mycode
     |- first.go
     |- second.go
     |- third.go

如果這三個檔案都是庫檔案,則它們都必須且只能使用package mycode宣告自己的包為mycode。go build的時候,會將它們合併起來。如果宣告的包名不是mycode,go build會直接忽略它。

當然,對main包就無所謂了,它不是庫檔案,可以放在任何地方,對目錄名沒有要求。但如果使用go install,則有額外的要求,見後文。

庫檔案中的大小寫命名

Go通過名稱首字母的大小寫決定屬性是否允許匯出:

  • 首字母大寫的屬性是允許匯出的屬性
  • 首字母小寫的屬性不允許被匯出

所以當庫檔案被匯入時,只有這個庫檔案中以大寫字母開頭的常量、變數、函式等才會被匯出,才可以在其他檔案中使用。

例如,庫檔案abc.go中:

func first() {}
func Second() {}

當匯入這個包的時候,由於first()函式首字母小寫,外界無法使用它,它只能在自己的包abc.go中使用,對外界不可見。大寫字母開頭的Second()函式會被匯入,所以可用。

工作空間(workspace)

速覽

  • 通過環境變數GOPATH設定workspace的路徑
  • Go程式設計人員一般將它們的Go程式碼放在一個workspace下,當然,這不是必須的
  • workspace包含一個或多個版本控制系統的倉庫(如git)
  • 每個倉庫包含一個或多個package
  • 每個package由單個目錄下的一個或多個Go原始檔組成,它們都必須宣告目錄名作為它們的包名
  • package的目錄路徑決定匯入包時import的路徑

Go和其它程式語言在組織專案的時候有所不同,其它語言一般每個專案都有一個單獨的workspace,且workspace一般和版本控制倉庫進行繫結。

現在設定GOPATH環境變數,假設設定為/gocode

echo 'export GOPATH=/gocode' >>/etc/profile.d/gopath.sh
chmod +x /etc/profile.d/gopath.sh
source /etc/profile.d/gopath.sh

go env GOPATH確定是否正確:

$ go env GOPATH
/gocode

workspace目錄結構

每個workspace都是一個目錄,這個目錄下至少包含三個目錄:

  • src:該目錄用於存放Go原始碼檔案(也稱為命令的原始檔)
  • bin:該目錄用於存放可執行命令(即構建後可執行的二進位制go程式,也稱為命令檔案)
  • pkg:該目錄用於存放共享庫檔案(即構建後非可執行程式的庫包,也稱為包物件檔案)

括號中給的名稱是go官方文件中常見的別名稱呼。

所以,先建立這3個目錄

mkdir -p /gocode/{src,pkg,bin}

GOPATH和GOROOT環境變數

GOPATH環境變數指定workspace的位置,用來指示go從哪裡搜尋go原始檔/包,例如import時從哪個路徑搜尋包並匯入。GOROOT環境變數用於指定go的安裝位置。go需要匯入包時,會從GOPATH和GOROOT所設定的位置處搜尋包。

預設位置為$HOME/go(Unix)或%USERPROFILE\go%(Windows)。可以手動設定GOPATH環境變數的路徑從而指定workspace的位置,可以指定為多個目錄,多個目錄時使用冒號分隔目錄(Unix系統)或使用分號分隔目錄(Windows系統)。注意,絕對不能將其設定為go的安裝目錄,即不能和GOROOT環境變數重複。

例如,windows下設定d:\gocode目錄為GOPATH的路徑:

setx GOPATH d:\gocode

Unix下設定$HOME/gocode目錄為GOPATH的路徑:

mkdir ~/gocode
export GOPATH=~/gocode
echo 'GOPATH=~/gocode' >>~/.bashrc

go envgo env GOPATH命令可以輸出當前有效的GOPATH路徑。

$ go env | grep GOPATH
GOPATH="/root/gocode"

$ go env GOPATH
/root/gocode

go build

先寫兩個go檔案,一個是可執行go檔案test.go,一個是共享庫strutils.go,將它們放在workspace的src下。

$ mkdir -p $GOPATH/src/{hello,strutils}
$ tree -C
.
├── bin
├── pkg
├── src
│   ├── hello
│   │   └── test.go
│   └── strutils
│       └── strutils.go

注意,上面故意將test.go放在名為hello的目錄下,可以將其放在src下的任何非庫檔案目錄下(例如不能放進strutils目錄下),名稱不要求。

hello/test.go的內容如下:

package main

import (
    "fmt"
    "strutils"
)

func main() {
    fmt.Println("Hello World")
    fmt.Println(strutils.ToUpperCase("hello world"))
}

strutils/strutils.go的內容如下:

package strutils

import (
    "strings"
)

func ToUpperCase(s string) string{
    return strings.ToUpper(s)
}

func ToLowerCase(s string) string{
    return strings.ToLower(s)
}

go build可以用於編譯,編譯時會對import匯入的包進行搜尋,搜尋的路徑為標準庫所在路徑$GOROOT/src、workspace下的src目錄。它只會生成額外的可執行檔案放在當前目錄下,不會生成額外的庫檔案。但需要注意,生成的可執行檔名稱可能會出乎意料:

例如進入到目錄src/hello下,對test.go的檔案進行編譯,以下三種build路徑都可用成功編譯:

cd src/hello
go build             # 生成的可執行檔名為hello
go build .           # 生成的可執行檔名為hello
go build test.go     # 生成的可執行檔名為test

前兩者是等價的,當go build以目錄的形式進行編譯,則生成的可執行檔名為目錄名。當go build以go程式碼檔名的方式進行編譯,則生成的可執行程式名為go原始碼檔名(去掉字尾.go或增加字尾.exe)。

go install

go還有一個工具install,go install的操作稱為安裝,將檔案安裝到合適的位置。go install時會先進行編譯,然後將編譯後的二進位制檔案儲存到workspace的bin目錄下,將編譯後的庫檔案(稱為包物件檔案,以".a"為字尾)放在pkg目錄下。

注意,go install必須先進入到$GOPATH/src,且只能對目錄進行操作,不能對具體的go檔案操作,因為go認為包和目錄名相同。給go install指定一個目錄名,就表示編譯這個包名。

例如,對src/hello下的test.go進行安裝,由於它匯入了strutils包,所以會自動將strutils也安裝好:

$ cd $GOPATH/src
$ go install hello
$ tree $GOPATH
/gocode
├── bin
│   └── hello           # 二進位制程式檔名為hello,而非test
├── pkg
│   └── linux_amd64     
│       └── strutils.a  # 庫檔案
└── src
    ├── hello
    │   └── test.go
    └── strutils
        └── strutils.go

還可以單獨對庫檔案進行安裝:

$ rm -rf $GOPATH/bin/* $GOPATH/pkg/*
$ cd $GOPATH/src
$ go install strutils
/gocode
├── bin
├── pkg
│   └── linux_amd64
│       └── strutils.a
└── src
    ├── hello
    │   └── test.go
    └── strutils
        └── strutils.go

如果省略目錄名,則表示對當前目錄下的包進行安裝:

$ cd $GOPATH/src/hello
$ go install

再次提醒,go install前先進入到$GOPATH/src目錄下。

由於go install可以直接安裝二進位制檔案到$GOPATH/bin,所以出於方便執行這些二進位制程式,可以將這個目錄放進PATH環境變數。

$ export PATH=$PATH:`go env GOPATH`/bin

構建go程式的規範建議

1.由於可以將所有go專案放在同一個$GOPATH目錄下,為了區分src下的專案目錄和庫檔案目錄,建議將每個專案目錄設定深一點

例如:

bin
pkg
src
 |- /first/project
            |- main.go
            |- myliba
                |- a.go
                |- b.go
            |- mylibb
                |- c.go
                |- d.go
 |- /second/project
            |- main.go
            |- lib
                |- a.go
                |- b.go

2.go install時,先進入到專案目錄下

3.庫檔案的名稱(也是目錄名)要選取合理,儘量短,但卻儘量見名知意,也儘量減少名稱重複的機率

例如util這種名稱到處都是,可以修改為numutil、nameutil等。