Golang語法
變數定義
變數定義語法
- 使用var關鍵字,可放在函式內,也可放在包內
// var + 變數名 + 資料型別(有預設值) var a int var b string = "string" // 通過賦值自動判斷型別 var c = true // 集中定義 var ( x = 1 y = 2 )
- 使用:=定義變數,只能在函式內使用
// := 用於賦初值 a, b := 1, 2
內建變數型別(builtin)
- bool, string
- (u)int, (u)int8, (u)int16, (u)int32, (u)int64, uintptr
- byte, rune
- float32, float64, complex64, complex128
強制型別轉換
Golang要求強制型別轉換,無隱式轉換
a, b := 3, 4 var c int c = int(math.Sqrt(float64(a * a + b * b)))
常量
使用const關鍵字定義常量,const數值可作為各種型別使用。
// 不確定型別 const a = 3 var b float64 b = a // 合併定義 const ( c = 1 d = 2 )
列舉
const ( spring = 0 summer = 1 autumn = 2 winter = 3 ) // iota用在取值為0的const變數,後續變數依次加1 const ( spring = iota summer autumn winter )
變數定義要點
- 變數型別寫在變數名之後
- 編譯器可以推測變數型別
- Golang沒有char,使用rune
- 原生支援複數型別
分支
if
const filename = "abc.txt" contents, err := ioutil.ReadFile(filename) if err != nil { fmt.Println(err) } else { fmt.Println(contents) } // 可以在if條件中賦值,賦值變數的作用域在if語句中 if contents, err := ioutil.ReadFile(filename); err != nil { fmt.Println(err) } else { fmt.Println(contents) }
switch
switch後可以沒有表示式,case結束後自動break,通過fallthrough不使用自動break。
func grade(score int) { grade := "" switch { case score < 0: panic(fmt.Sprintf("Wrong score: %d\n", score)) case score < 60: grade = "C" case score < 80: grade = "B" case score < 90: grade = "A" case score < 100: // 自動break,除非出現fallthrough fallthrough case score >= 100: grade = "S" } fmt.Println(grade) }
迴圈
for條件沒有括號,沒有while。
func convertToBin(n int) string { result := "" for ; n > 0; n /= 2 { lsb := n % 2 result = strconv.Itoa(lsb) + result } return result } func printFile(filename string) { file, err := os.Open(filename) if err != nil { panic(err) } scanner := bufio.NewScanner(file) for scanner.Scan() { fmt.Println(scanner.Text()) } } func forever() { // while true for{ fmt.Println("forever") } }
函式
- 函式宣告的順序為:關鍵字,函式名,引數,返回型別,函式體。
- 函式允許有多個返回值,並能在函式體內拿到返回值。
- 函式可以作為另一個函式的引數
- 函式支援可變引數列表
func div(a, b int) (q, r int) { // q = a / b // r = a % b // return return a / b, a % b } // 使用_拋棄返回值 a, _ := div(12, 7) // 使用另一個函式作為引數 func apply(operation func(a, b int), a, b int) { operation(a, b) } // 使用匿名函式 apply(func(a, b int) { fmt.Println(a, b) }, 1, 2) // 可變引數列表 func sum(numbers ...int) int { s := 0 for i := range numbers { s += numbers[i] } return s }
指標
Golang只有值傳遞一種方式。
// 使用指標操作 func change(pa *int) { *pa++ } a := 5 change(&a) func swap(x, y *int) { *x, *y = *y, *x }
陣列
陣列長度寫在型別之前,[10]int和[20]int是不同的型別。陣列是值複製。
var arr1 [5]int arr2 := [3]int{1, 3, 5} arr3 := [...]int{2, 4, 6} var arr4 [4][5]int
遍歷陣列
for i := 0; i < len(arr3); i++ { fmt.Println(arr3[i]) } for i := range arr3 { fmt.Println(arr3[i]) } // 按下標和值遍歷 for i, v := range arr3 { fmt.Println(i, v) }
切片
陣列切片相當於資料的一個檢視,可以通過切片改變陣列的值。
func updateSlice(s []int) { s[0] = 100 } func main() { array := [...]int{0, 1, 2, 3, 4, 5, 6, 7} s1 := array[2:6] s2 := array[2:] s3 := array[:6] s4 := array[:] fmt.Println(s1, s2, s3, s4) // [2 3 4 5] [2 3 4 5 6 7] [0 1 2 3 4 5] [0 1 2 3 4 5 6 7] updateSlice(s2) fmt.Println(s2) // [100 3 4 5 6 7] updateSlice(s3) fmt.Println(s3) // [100 1 100 3 4 5] // 切片擴充套件 s5 := s3[6:8] fmt.Println(s5) // 新增元素 s6 := append(s5, 8) fmt.Println(s6, array) fmt.Println(array[:8]) // 通過make建立切片 // 長度為10的切片 x := make([]int, 10) // 長度為10,容量為16的切片 y := make([]int, 10, 16) // 拷貝陣列元素 copy(x, y) // 擷取陣列元素 z := append(x[:2], x[3:]...) }
切片底層維護了一個數組,ptr指向切片的首個元素,len表示切片的長度,cap表示底層陣列的從ptr到最末的元素數量。切片可以向後擴充套件,不可向前擴充套件。切片新增元素如果會超過底層陣列的cap,Golang會分配更大的底層陣列(原容量*2進行擴充套件),由於值傳遞的關係,必須接收append方法的返回值。
map
通過map[K]v的形式定義map。除了slice、map、function的內建型別和struct型別都可以作為map的key。
m := map[string]string{ "name": "wch", "age":"23", } m2 := make(map[string]int) // 無序遍歷map for k, v := range m { fmt.Println(k, v) } // map讀值 name := m["name"] // 判斷key是否存在 grade, exist := m["grade"] // 刪除 delete(m, "name")
rune
Golang的rune相當於java的char,中文字元在Golang中佔3個位元組,使用utf8.RuneCountInString
獲取字元數,用len
獲取位元組長度,使用[]byte
獲取位元組。
s := "學習Golang!" fmt.Println(s) // ch是rune型別,中文字元佔3個位元組 for i, ch := range s { fmt.Printf("(%d %X) ", i, ch) } fmt.Println() fmt.Println("rune count in string:", utf8.RuneCountInString(s)) bytes := []byte(s) for len(bytes) > 0 { ch, size := utf8.DecodeRune(bytes) bytes = bytes[size:] fmt.Printf("size= %d, rune=%c\n", size, ch) } for i, ch := range []rune(s) { fmt.Printf("(%d %c)", i, ch) } fmt.Println()
面向物件
Golang僅支援封裝,不支援繼承和多型,沒有class,只有struct,通過struct來定義物件。
type treeNode struct { valueint left, right *treeNode } func createNode(value int) *treeNode { return &treeNode{value: value} } func main() { // 通過多種方式宣告物件 var root treeNode root = treeNode{value: 0} root.left = &treeNode{} root.right = &treeNode{1, nil, nil} root.right.left = new(treeNode) root.right.right = createNode(3) }
Golang支援顯式定義方法接收者,值/指標接收者均可接收值/指標。當需要改變內容或結構過大時需要使用指標接收者,值接收者是Golang特有的
// 指定方法接收者 func (node treeNode) print() { fmt.Println(node.value) } func (node *treeNode) setValue(value int) { node.value = value } func main() { node := treeNode{} node.setValue(100) node.print() pNode := &node pNode.setValue(101) pNode.print() }
封裝
Golang的可見性(針對包)使用首字母來控制,首字母大寫表示public,首字母小寫表示private。每個目錄都是一個包,main包包含可執行入口。為結構定義的方法必須放在同一個包內,可以是不同的檔案。Golang提供了兩種擴充套件系統型別和已封裝型別的方法:
- 定義別名:通過type為已有型別定義一個別名,針對新的結構體進行擴充套件。
type Queue []int func (q *Queue) Push(v int) { *q = append(*q, v) } func (q *Queue) Pop() int { head := (*q)[0] *q = (*q)[1:] return head } func (q *Queue) IsEmpty() bool { return len(*q) == 0 }
- 使用組合:定義新的結構體,其中一個成員是需要擴充套件的型別的指標。
type EnhanceNode struct { node *Node } func (enhanceNode *EnhanceNode) PostOrder() { if nil == enhanceNode || nil == enhanceNode.node { return } left := EnhanceNode{enhanceNode.node.Left} right := EnhanceNode{enhanceNode.node.Right} left.PostOrder() right.PostOrder() enhanceNode.PostOrder() }
GOROOT、GOPATH、go get
GOROOT一般為下載的sdk路徑,GOPATH為使用者可定義的路徑,使用者原始碼和通過go get命令下載的第三方庫在GOPATH的src目錄下。
gopm是go get的映象工具,通過go get -g -v github.com/gpmgo/gopm
下載gopm。
// 下載 gopm get -g -v Golang.org/x/tools/cmd/goimports // 更新 gopm get -g -v -u Golang.org/x/tools/cmd/goimports // 安裝到GOPATH的bin目錄 go install Golang.org/x/tools/cmd/goimports
介面
介面的實現是隱式的,只要實現介面的方法。
duck typing
“像鴨子走路,像鴨子叫(長得像鴨子),那麼就是鴨子”,意為描述事物的外部行為而非內部結構。嚴格說go屬於結構化型別系統,類似duck typing。
介面變數裡有什麼
- 介面自帶指標
- 介面變數同樣採用值傳遞,幾乎不需要使用介面的指標
- 指標接收者只能以指標的方式使用,值接收者都可以
- interface{}可以代表任何型別
- 定義介面
type Retriever interface { Get(url string) string } func download(r Retriever) string { return r.Get("http://www.baidu.com") }
- 實現介面
type Retriever struct { UserAgent string Timeouttime.Duration } func (r *Retriever) Get(url string) string { resp, err := http.Get(url) if nil != err { panic(err) } result, err := httputil.DumpResponse(resp, true) resp.Body.Close() if nil != err { panic(err) } return string(result) }
- 呼叫介面
var r Retriever r = &real.Retriever{ UserAgent: "Mozilla/5.0", Timeout:time.Minute, } fmt.Println(download(r)) // Type Assertion if retriever, ok := r.(*real.Retriever); ok { fmt.Printf("%T %v\n", retriever, retriever) }
- 介面組合
type HttpExecute interface { Retriever Poster }
函數語言程式設計
- 函式是一等公民:引數,變數,返回值都可以是函式。
- 高階函式:函式的引數可以是函式
- 函式->閉包
func adder() func(int) int { sum := 0 return func(i int) int { sum += i return sum } } func main() { // f中不僅是一個函式,還有對變數sum的引用 f := adder() for i := 0; i < 10; i++ { fmt.Println(f(i)) } }
錯誤處理和資源管理
defer
用於指定在函式結束之前執行,可以用於關閉檔案、釋放鎖等。
func tryDefer() { // 在函數借結束之前執行 defer fmt.Println("execute last...") defer fmt.Println("execute before last...") fmt.Println("execute") }
panic
- 停止當前函式執行
- 一直向上返回,執行每一層的defer
- 如果沒有遇見recover,程式退出
recover
- 僅在defer呼叫中使用
- 獲取panic的值
- 如果無法處理,重新panic
func tryRecover() { defer func() { r := recover() if err, ok := r.(error); ok { fmt.Println("error:", err) } else { panic(fmt.Sprintf("unknown error: %v", r)) } }() panic(errors.New("this is an error")) }
測試
測試格式
- 測試檔案與待測試程式碼放在同一目錄下
-
測試檔名以
_test.go
結尾 -
測試函式名為
TestXxx
或Test_xxx
的格式,使用其它型別測試,如效能測試則函式名為BenchmarkXxx
或 ```Benchmark_xxx`` 的格式
傳統測試
- 傳統測試資料與邏輯混在一起
- 傳統測試的出錯資訊不明確
- 傳統測試一旦出錯測試即全部結束
表格驅動測試
- 測試程式碼
func calcTriangle(a, b int) int { return int(math.Sqrt(float64(a*a + b*b))) }
-
表格驅動測試
分離了測試資料和測試邏輯,明確了出錯資訊,可以部分失敗。
func TestCalcTriangle(t *testing.T) { tests := []struct{ a, b, c int }{ {3, 4, 5}, {5, 12, 13}, {8, 15, 18}, } for _, tt := range tests { if actual := calcTriangle(tt.a, tt.b); actual != tt.c { t.Errorf("calcTriangle(%d %d); got %d; expected %d", tt.a, tt.b, actual, tt.c) } } }
效能測試
執行b.N次,由Golang決定具體次數,測試完成後在控制檯列印執行次數和平均執行時間。
func BenchmarkTriangle(b *testing.B) { x, y, z := 8, 15, 17 for i := 0; i < b.N; i++ { actual := calcTriangle(x, y) if actual != z { b.Errorf("calcTriangle(%d %d); got %d; expected %d", x, y, actual, z) } } }
測試命令列工具
# 測試命令 go test # 程式碼覆蓋率測試 go test -cover # 生成程式碼測試覆蓋率檔案 go test -coverprofile c.out # 程式碼覆蓋率測試html go tool cover -html=c.out # 效能測試 go test -bench . # 生成效能報告 go test -bench . -cpuprofile cpu.out
文件
Golang提供go doc
命令檢視由註釋組成的文件,通過godoc -http :6060
命令在本地6060埠生成html文件。在test檔案中建立名為ExampleXxx_xxx
的函式,通過特定格式生成程式碼例項文件。
func ExampleQueue_Pop() { queue := Queue{} queue.Push(1) fmt.Println(queue.Pop()) // Output: // 1 }
go routine
協程Coroutine
- 輕量級“執行緒”
- 非搶佔式多工處理,由協程主動交出控制權
- 編譯器/直譯器/虛擬機器層面的多工
- 多個協程可能在一個或多個執行緒上執行
goroutine定義
go runtime.Gosched() go run -race xxx.go
channel
相對於通過共享記憶體來通訊,channel通過通訊來共享記憶體。
range
使用select進行排程
select可以用來監聽IO操作,可以同時監聽多個channel的訊息狀態。