Go語言學習 三 實例詳解

本文最初發表在我的個人博客,歡迎查看原文:
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 testtesting包組成的輕量級測試框架。

首先在$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.Errort.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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章