本文最初發表在我的個人博客,歡迎查看原文:
blog.favorstack.io/golang
上一篇文章介紹了Go代碼的組織結構及一些基本概念,下面將以github.com/favorstack
作爲基路徑,在工作空間中創建一個目錄來保存源代碼,介紹一下基本的語法。
一 第一個程序
Hello world程序
1). 創建基路徑:
$ mkdir -p $GOPATH/src/github.com/favorstack
或者:
$ mkdir -p ~/go/src/github.com/favorstack
2). 新建一個工程目錄go-example
:
該目錄可以作爲git倉庫的根目錄:
$ cd $GOPATH/src/github.com/favorstack && mkdir go-example
3). 在上述目錄下創建hello
目錄:
$ cd go-example && mkdir hello
4). 在hello
目錄下新建一個hello.go
文件:
$ cd hello && vi hello.go
然後輸入以下代碼:
package main
import "fmt"
func main() {
fmt.Println("Hello World!")
}
5). 保存退出。
6). 編譯執行
在第一篇文章中我們已經使用過go build
命令了,這次我們直接安裝到$GOPATH
目錄的bin
下:
執行go install
命令編譯並安裝:
$ go install
沒有任何信息輸出則表示編譯沒問題。
直接執行~/go/bin
下的命令,需要將該目錄添加到$PATH
環境變量中,如果你還沒有加上,現在是個好機會,可以參考上一篇文章。
沒問題的話,我們可以看到可執行程序已經編譯好並安裝在~/go/bin
目錄下了,直接執行hello
命令即可(Windows下是hello.exe
):
$ hello
Hello World!
上面省略了包路徑,只能在當前目錄下執行。如果加上包路徑,則可以在任意位置執行:
$ go install github.com/favorstack/go-example/hello
在不指定包路徑時,go install
會從當前目錄查找源代碼,而指定包路徑後,會從$GOPATH
指定的位置查找github.com/favorstack/go-example/hello
包的源代碼,所以在指定了包路徑後,無需在當前目錄下執行安裝命令。
另外,源代碼文件未改變(MD5值未改變)的情況下,多次執行
go isntall
所編譯安裝的可執行程序並不會發生改變,實際上,go甚至並未進行新的編譯而是直接複製的第一次編譯後的結果,你可以對目標文件執行MD5值校驗多次編譯的結果來驗證這一點。
代碼解釋
1.go源文件後綴必須以.go
結尾:
例如hello.go
2.go代碼的第一行有效(非註釋)代碼總是以包的聲明開始:
例如package main
3.包的聲明語法:
package 包名
包名
即爲在其他模塊導入時使用的名稱,同一個包下的所有源文件必須使用相同的包名。包名應該儘量簡短,簡潔,好記,按照約定,所有包名使用小寫字母,且使用單個單詞表示,不需要使用下劃線或大小寫混合的方式。
在Go語言中,有用的文檔註釋通常比超長名稱更有價值
4.導入包語法:
import "包導入路徑"
多個導入包需要放到括號中:
import (
"包路徑1"
"包路徑2"
...
)
5.重命名導入:
如果導入的包具有相同的包名時,需要重命名其中一個包名:
import (
"path1/package"
mypackage "path2/package"
)
其中,左側爲新的包名mypackage
,右側爲原始包名"path2/package"
命令
go doc [command]
可以查詢指定包的用法, 例如go doc net/http
6.Go語句的結尾
Go語句無需以分號;
結尾,但是如果多條語句放在一行,則需要用分號隔開,我們並不推薦這種多條語句放在一行的用法。
二 第一個庫
字符串反轉工具
接下來是一個字符串反轉的小工具示例。
1). 首先創建包路徑:
$ mkdir $GOPATH/src/github.com/favorstack/go-example/stringutil
2). 接下來在上面的目錄下創建reverse.go
文件:
$ cd stringutil && vi reverse.go
3). 輸入以下內容:
// stringutil包包含字符串處理相關的函數
package stringutil
// Reverse函數返回其參數字符串從左到右的反轉形式
func Reverse(s string) string {
r := []rune(s)
length := len(r)
for i, j := 0, length - 1; i < length/2; i, j = i + 1, j - 1 {
r[i], r[j] = r[j], r[i]
}
return string(r)
}
4). 然後用go build
編譯一下:
$ go build github.com/favorstack/go-example/stringutil
或者如果你當前已經在stringutil
目錄下了的話,直接執行go build
即可:
# if currently in github.com/favorstack/go-example/stringutil
$ go build
需要注意的是,本次編譯並不會像hello.go
一樣產生編譯後的文件,實際上,它已經把編譯好的包放到了本地緩存中。
5). 接下來修改我們的hello.go
程序使用剛纔編寫的Reverse
函數:
package main
import (
"fmt"
// 導入字符串工具包
"github.com/favorstack/go-example/stringutil"
)
func main() {
fmt.Println("Hello World!")
s := "悠雲白雁過南樓"
fmt.Println(s)
// 使用反轉函數
fmt.Println(stringutil.Reverse(s))
}
6). 然後保存安裝:
$ go install github.com/favorstack/go-example/hello
7). 運行:
$ hello
Hello World!
悠雲白雁過南樓
樓南過雁白雲悠
現在,工作空間的目錄結構應該是這樣:
~/go/ # GOPATH
bin/
hello # 可執行文件
src/
github.com/favorstack/go-example/
hello/
hello.go # 可執行文件源文件
stringutil/
reverse.go # 工具包源文件
代碼解釋
1.包名
需要與導入路徑
的最後一個元素
保持一致;
雖然語法上源文件的包名可以和導入路徑的最後一個元素不一樣,但是按照Go的約定,我們一般習慣將包名
與導入路徑
的最後一個元素
保持一致
例如,示例中的導入路徑github.com/favorstack/go-example/stringutil
(在hello.go
中),包名對應的爲stringutil
(在reverse.go
中);
另外,可執行文件的包總是聲明爲package main
,Go會查找main
包下的main()
函數作爲程序入口。
假如我們將reverse.go
放到github.com/favorstack/go-example/util
下,但是聲明依然是package stringutil
,那麼,相應的導入路徑會變爲github.com/favorstack/go-example/util
,但是如果你試圖使用util
來調用其中的函數:util.Reverse(...)
,則會報錯:
$ go build
# github.com/favorstack/go-example/hello
./hello.go:5:2: imported and not used: "github.com/favorstack/go-example/util" as stringutil
./hello.go:12:14: undefined: util
上述錯誤包含兩點:
1) imported and not used: "github.com/favorstack/go-example/util" as stringutil
提示stringutil
包導入後未曾使用(實際用的是util
)。在Go中,不允許導入的包不使用,即不允許多餘的包導入,這樣會造成資源浪費,所以Go直接從語言層面上就禁止了這種做法。當然,這一點還有另一種折中的辦法來導入不使用的包,參見下面的空白標識符
;
2)undefined: util
提示未定義的包util
(實際定義的是stringutil
)。
因爲包的名字實際上還是叫stringutil
,因此,依然需要使用stringutil.Reverse(...)
。所以爲了不必要的麻煩和誤解,請一定要將包名
與導入路徑
的最後一個元素
保持一致
2.變量的聲明
在reverse.go
中,有如下兩行代碼:
r := []rune(s)
length := len(r)
符號:=
在Go中稱作簡化變量聲明運算符
。這個運算符用於聲明一個變量,同時給這個變量賦予初始值。編譯器使用函數返回值的類型來確定每個變量的類型。簡化變量聲明運算符只是一種簡化記法,讓代碼可讀性更高。
另外,Go還有一種聲明變量的方式:
var 變量名 變量類型
例如:
// 聲明一個類型爲string的變量s
var s string
使用關鍵字var
聲明的變量和簡化變量聲明運算符:=
聲明的變量沒有任何區別。一般情況下,要聲明初始值爲零值的變量,應該使用var關鍵字聲明變量;如果提供確切的非零初始化變量或者使用函數返回值創建變量,應該使用簡化變量聲明運算符。
3.註釋
與大多數語言一樣,在Go中,以//
開頭的行表示註釋
三 測試
第一個測試代碼
Go自身帶有一個由go test
和testing
包組成的輕量級測試框架。
首先在$GOPATH/src/github.com/favorstack/go-example/stringutil
目錄下創建一個reverse_test.go
測試文件,內容如下:
package stringutil
import "testing"
func TestReverse(t *testing.T) {
cases := []struct {
in, want string
}{
{"Hello, world!", "!dlrow ,olleH"},
{"悠雲白雁過南樓", "樓南過雁白雲悠"},
{"", ""},
}
for _, c := range cases {
got := Reverse(c.in)
if got != c.want {
t.Errorf("Reverse(%q) == %q, want %q", c.in, got, c.want)
}
}
}
保存退出,然後在當前目錄運行測試:
$ go test
PASS
ok github.com/favorstack/go-example/stringutil 0.007s
或者在任意位置運行測試:
$ go test github.com/favorstack/go-example/stringutil
ok github.com/favorstack/go-example/stringutil 0.007s
代碼解釋
1.Go測試文件格式
Go測試文件必須以_test.go
結尾,測試函數必須以Test
開頭,形如TestXxx
格式,Xxx
必須以大寫字母開頭,且函數參數簽名必須是func (t *testing.T)
形式。測試框架會執行每一個這樣的函數,如果某個這樣的函數調用了像t.Error
或 t.Fail
這樣的失敗函數,則該測試沒有通過。
2.struct關鍵字
Go使用struct
關鍵字聲明自定義結構類型。
現在只需要知道,這段代碼聲明瞭一個元素爲自定義結構類型的切片(cases
變量),該自定義結構類型有兩個字符串類型的字段(或者叫屬性)in
,want
,並且該切片默認初始化了3個元素。切片也是一種數據結構,有點像動態的數組。切片是可以進行迭代的。
cases := []struct {
in, want string
}{
{"Hello, world!", "!dlrow ,olleH"},
{"悠雲白雁過南樓", "樓南過雁白雲悠"},
{"", ""},
}
3.for range 循環
語法:
for ... := range ... {
statement
...
}
關鍵字range
可以用於迭代數組、字符串、切片、映射和通道等數據類型。
與java不同,Go函數允許返回多個值,由於切片數據類型的每個元素都有對應的索引位置,所以上述測試用例中的for range
循環中會返回兩個值:_
, c
,其中第一個爲當前元素的索引,第二個爲當前元素本身。符號_
在Go語言中有特殊含義,稱爲空白標識符
。
4.空白標識符
下劃線字符_
在Go語言裏稱爲空白標識符,這個標識符用來拋棄不想繼續使用的值,如給導入的包賦予一個空名字,或者忽略函數返回的不感興趣的值(本例)。
主要有以下兩個作用:
-
1)讓Go語言對包做初始化操作,但是並不使用包裏的標識符。爲了讓程序的 可讀性更強,Go編譯器不允許聲明導入某個包卻不使用。下劃線可以讓編譯器接受這類導入,並且調用對應包內的所有代碼文件裏定義的
init
初始化函數。 -
2)佔位符,如果要調用的函數返回多個值,而其中的某個值又不需要,就可以使用下劃線標識符將其忽略。上述測試用例中,迭代過程中我們並不關心元素的索引,所以使用
_
將其忽略掉了。
四 go get命令
導入路徑中如果包含你的遠程倉庫地址的話,go get
命令可以根據這個導入路徑自動從遠程倉庫獲取這個包的源代碼。例如,本文示例的代碼已託管在GitHub上:github.com/favorstack/go-example,那麼你可以直接使用go get
獲取helloworld的源代碼,並自動構建編譯安裝:
$ go get github.com/favorstack/go-example/hello
$ hello
Hello World!
悠雲白雁過南樓
樓南過雁白雲悠
如果你的工作空間中沒有這個包,go get
會將這個包拉取下來並放到$GOPATH
指定的目錄列表的第一個路徑下;如果已經存在,則只是執行一下安裝,不會再重新拉取(如果遠程代碼更新了,go get默認並不會更新最新的代碼,如果需要更新,可以指定-u
參數,前提是該包在本地未被修改)。這也是Go代碼與他人共享的一種方式。
參考
https://golang.org/doc/code.html
https://golang.org/doc/effective_go.html
https://golang.org/pkg/testing