這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
前言
Go語言起源
Golang發展曆程
Go語言項目
- "軟體的複雜性是乘法級相關的-----Rob Pike"
- 簡潔的設計需要在工作開始的時候捨棄不必要的想法,並且在軟體的生命週期內嚴格區別好的改變和壞的改變。通過足夠的努力,一個好的改變可以在不破壞原有完整概念的前提下保持自適應,正如Fred Brooks所說的“概念完整性”;而一個壞的改變則不能達到這個效果,它們僅僅是通過膚淺的和簡單的妥協來破壞原有設計的一致性。只有通過簡潔的設計,才能讓一個系統保持穩定、安全和持續的進化。
本書的組織
基礎
- 第一章包含了本教程的基本結構,通過十幾個程式介紹了用Go語言如何?類似讀寫檔案、文字格式設定化、建立映像、網路用戶端和伺服器通訊等日常工作。
- 第二章描述了Go語言程式的基本元素結構、變數、新類型定義、包和檔案、以及範圍等概念。
- 第三章討論了數字、布爾值、字串和常量,並示範了如何顯示和處理Unicode字元。
- 第四章描述了複合類型,從簡單的數組、字典、切片到動態列表。
- 第五章涵蓋了函數,並討論了錯誤處理、panic和recover,還有defer語句。
第一章到第五章是基礎部分,主流命令式程式設計語言這部分都類似。個別之處,Go語言有自己特色的文法和風格,但是大多數程式員能很快適應。其餘章節是Go語言特有的:方法、介面、並發、包、測試和反射等語言特性。
進階
- 第八章討論了基於順序通訊進程(CSP)概念的並發編程,使用goroutines和channels處理並發編程。
- 第九章則討論了傳統的基於共用變數的並發編程。
- 第十章描述了包機制和包的組織圖。這一章還展示了如何有效地利用Go內建的工具,使用單個命令完成編譯、測試、基準測試、代碼格式化、文檔以及其他諸多任務。
- 第十一章討論了單元測試,Go語言的工具和標準庫中整合了輕量級的測試功能,避免了強大但複雜的測試架構。測試庫提供了一些基本構件,必要時可以用來構建複雜的測試構件。
- 第十二章討論了反射,一種程式在運行期間審視自己的能力。反射是一個強大的編程工具,不過要謹慎地使用;這一章利用反射機制實現一些重要的Go語言庫函數, 展示了反射的強大用法。第十三章解釋了底層編程的細節,在必要時,可以使用unsafe包繞過Go語言安全的類型系統。
入門
Hello, World
嘗試用100中方法列印出Hello, World
哈哈!
package mainfunc main() { print("Hello, 世界") //Go語言原生支援Unicode,它可以處理全世界任何語言的文本。}
命令列參數
os包以跨平台的方式,提供了一些與作業系統互動的函數和變數。程式的命令列參數可從os包的Args變數擷取;os包外部使用os.Args訪問該變數。
尋找重複的行
bufio
包,它使處理輸入和輸出方便又高效。Scanner類型是該包最有用的特性之一,它讀取輸入並將其拆成行或單詞;通常是處理行形式的輸入最簡單的方法。
- 格式化verb
%d 十進位整數%x, %o, %b 十六進位,八進位,二進位整數。%f, %g, %e 浮點數: 3.141593 3.141592653589793 3.141593e+00%t 布爾:true或false%c 字元(rune) (Unicode碼點)%s 字串%q 帶雙引號的字串"abc"或帶單引號的字元'c'%v 變數的自然形式(natural format)%T 變數的類型%% 字面上的百分比符號標誌(無運算元)
ioutil.ReadFile(filename)
函數返回一個位元組切片(byte slice)
GIF動畫
- 產生的圖形名字叫利薩形(Lissajous figures)
擷取URL
http.Get(url)
ioutil.ReadAll(resp.Body)
並發擷取多個URL
goroutine和channel
Web服務
package mainimport ( "fmt" "log" "net/http")func main() { http.HandleFunc("/", handler) // each request calls handler log.Fatal(http.ListenAndServe("localhost:8000", nil))}// handler echoes the Path component of the requested URL.func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path)}
- 如果你的請求pattern是以/結尾,那麼所有以該url為首碼的url都會被這條規則匹配。
要點
程式結構
命名
- 25個關鍵字
break default func interface selectcase defer go map structchan else goto package switchconst fallthrough if range typecontinue for import return var
- 30多個預定義的名字
內建常量: true false iota nil內建類型: int int8 int16 int32 int64uint uint8 uint16 uint32 uint64 uintptrfloat32 float64 complex128 complex64bool byte rune string error內建函數: make len cap new append copy close deletecomplex real imagpanic recover
- Go語言程式員推薦使用 駝峰式 命名
聲明
變數
- 簡短變數聲明
i := 100
- 指標
x := 1p := &x // p, of type *int, points to xfmt.Println(*p) // "1"*p = 2 // equivalent to x = 2fmt.Println(x) // "2"
- new函數
p := new(int) // p, *int 類型, 指向匿名的 int 變數
- 變數的生命週期
Go語言的自動垃圾收集器對編寫正確的代碼是一個巨大的協助,但也並不是說你完全不用考慮記憶體了。你雖然不需要顯式地分配和釋放記憶體,但是要編寫高效的程式你依然需要瞭解變數的生命週期。例如,如果將指向短生命週期對象的指標儲存到具有長生命週期的對象中,特別是儲存到全域變數時,會阻止對短生命週期對象的記憶體回收(從而可能影響程式的效能)。
賦值
- 元組賦值
x, y = y, xa[i], a[j] = a[j], a[i]
- 可賦值性
可賦值性的規則對於不同類型有著不同要求,對每個新類型特殊的地方我們會專門解釋。對於目前我們已經討論過的類型,它的規則是簡單的:類型必須完全符合,nil可以賦值給任何指標或參考型別的變數。常量(§3.6)則有更靈活的賦值規則,因為這樣可以避免不必要的顯式的類型轉換。
類型
包和檔案
- 匯入包
"fmt". "fmt" //省略包名_ "fmt" //只匯入
- 包的初始化
func init() { /* ... */ }
範圍
- 不要將範圍和生命週期混為一談。聲明語句的範圍對應的是一個原始碼的文本地區;它是一個編譯時間的屬性。一個變數的生命週期是指程式運行時變數存在的有效時間段,在此時間地區內它可以被程式的其他部分引用;是一個運行時的概念。
- 控制流程標號,就是break、continue或goto語句後面跟著的那種標號,則是函數級的範圍。
基礎資料類型
整型
8、16、32、64bit
浮點數
float32和float64
好漂亮
// Copyright 2016 Alan A. A. Donovan & Brian W. Kernighan.// License: https://creativecommons.org/licenses/by-nc-sa/4.0/// See page 58.//!+// Surface computes an SVG rendering of a 3-D surface function.package mainimport ( "fmt" "math")const ( width, height = 600, 320 // canvas size in pixels cells = 100 // number of grid cells xyrange = 30.0 // axis ranges (-xyrange..+xyrange) xyscale = width / 2 / xyrange // pixels per x or y unit zscale = height * 0.4 // pixels per z unit angle = math.Pi / 6 // angle of x, y axes (=30°))var sin30, cos30 = math.Sin(angle), math.Cos(angle) // sin(30°), cos(30°)func main() { fmt.Printf("<svg xmlns='http://www.w3.org/2000/svg' "+ "style='stroke: grey; fill: white; stroke-width: 0.7' "+ "width='%d' height='%d'>", width, height) for i := 0; i < cells; i++ { for j := 0; j < cells; j++ { ax, ay := corner(i+1, j) bx, by := corner(i, j) cx, cy := corner(i, j+1) dx, dy := corner(i+1, j+1) fmt.Printf("<polygon points='%g,%g %g,%g %g,%g %g,%g'/>\n", ax, ay, bx, by, cx, cy, dx, dy) } } fmt.Println("</svg>")}func corner(i, j int) (float64, float64) { // Find point (x,y) at corner of cell (i,j). x := xyrange * (float64(i)/cells - 0.5) y := xyrange * (float64(j)/cells - 0.5) // Compute surface height z. z := f(x, y) // Project (x,y,z) isometrically onto 2-D SVG canvas (sx,sy). sx := width/2 + (x-y)*cos30*xyscale sy := height/2 + (x+y)*sin30*xyscale - z*zscale return sx, sy}func f(x, y float64) float64 { r := math.Hypot(x, y) // distance from (0,0) return math.Sin(r) / r}//!-
複數
Go語言提供了兩種精度的複數類型:complex64和complex128,分別對應float32和float64兩種浮點數精度。內建的complex函數用於構建複數,內建的real和imag函數分別返回複數的實部和虛部:
布爾型
字串
\a 響鈴\b 退格\f 換頁\n 換行\r 斷行符號\t 定位字元\v 垂直定位字元\' 單引號 (只用在 '\'' 形式的rune符號面值中)\" 雙引號 (只用在 "..." 形式的字串面值中)\\ 反斜線
- 得益於UTF8編碼優良的設計,諸多字串操作都不需要解碼操作。我們可以不用解碼直接測試一個字串是否是另一個字串的首碼:
func HasPrefix(s, prefix string) bool { return len(s) >= len(prefix) && s[:len(prefix)] == prefix}
或者是尾碼測試:func HasSuffix(s, suffix string) bool { return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix}
或者是包含子串測試:func Contains(s, substr string) bool { for i := 0; i < len(s); i++ { if HasPrefix(s[i:], substr) { return true } } return false}
package mainimport "fmt"func main() { s := "Hello, 世界" fmt.Printf("%s\t%d\n", s, len(s)) for i := 0; i < len(s); i++ { fmt.Printf("%d\t%v\t%q\n", i, s[i], s[i]) } fmt.Println("...") for i, r := range s { fmt.Printf("%d\t%d\t%q\n", i, r, r) }}
結果
Hello, 世界 130 72 'H'1 101 'e'2 108 'l'3 108 'l'4 111 'o'5 44 ','6 32 ' '7 228 'ä'8 184 '¸'9 150 '\u0096'10 231 'ç'11 149 '\u0095'12 140 '\u008c'...0 72 'H'1 101 'e'2 108 'l'3 108 'l'4 111 'o'5 44 ','6 32 ' '7 19990 '世'10 30028 '界'
- 字串和Byte切片
標準庫中有四個包對字串處理尤為重要:bytes、strings、strconv和unicode包。
- strings包提供了許多如字串的查詢、替換、比較、截斷、拆分和合并等功能。
- bytes包也提供了很多類似功能的函數,但是針對和字串有著相同結構的[]byte類型。因為字串是唯讀,因此逐步構建字串會導致很多分配和複製。在這種情況下,使用bytes.Buffer類型將會更有效,稍後我們將展示。
- strconv包提供了布爾型、整型數、浮點數和對應字串的相互轉換,還提供了雙引號轉義相關的轉換。
- unicode包提供了IsDigit、IsLetter、IsUpper和IsLower等類似功能,它們用於給字元分類。每個函數有一個單一的rune類型的參數,然後返回一個布爾值。而像ToUpper和ToLower之類的轉換函式將用於rune字元的大小寫轉換。所有的這些函數都是遵循Unicode標準定義的字母、數字等分類規範。strings包也有類似的函數,它們是ToUpper和ToLower,將原始字串的每個字元都做相應的轉換,然後返回新的字串。
- 字串和數位轉換
常量
複合資料型別
數組
Slice
Map
在Go語言中,一個map就是一個雜湊表的引用,map類型可以寫為map[K]V,其中K和V分別對應key和value。
內建的make函數可以建立一個map:
ages := make(map[string]int) // mapping from strings to ints
我們也可以用map字面值的文法建立map,同時還可以指定一些最初的key/value:
ages := map[string]int{ "alice": 31, "charlie": 34,}
使用內建的delete函數可以刪除元素:
delete(ages, "alice") // remove element ages["alice"]
結構體
JSON
json.Marshal
和json.MarshalIndent
- json處理struct未匯出成員
golang的結構體裡的成員的名字如果以小寫字母開頭,那麼其他的包是無法訪問的,也就是json無法訪問我們的結構體裡小寫字母開頭的成員。兩種解決方案
- struct的成員用大寫開頭,然後加tag
package mainimport ( "encoding/json" "fmt" "log")type Movie struct { Title string Year int `json:"released"` Color bool `json:"color,omitempty"` Actors []string}func main() { var movies = []Movie{ {Title: "Casablanca", Year: 1942, Color: false, Actors: []string{"Humphrey Bogart", "Ingrid Bergman"}}, {Title: "Cool Hand Luke", Year: 1967, Color: true, Actors: []string{"Paul Newman"}}, {Title: "Bullitt", Year: 1968, Color: true, Actors: []string{"Steve McQueen", "Jacqueline Bisset"}}, // ... } data, err := json.Marshal(movies) // json.MarshalIndent(struct, "", " ") if err != nil { log.Fatalf("JSON marshaling failed: %s", err) } fmt.Printf("%s\n", data)}
[{"Title":"Casablanca","released":1942,"Actors":["Humphrey Bogart","Ingrid Bergman"]},{"Title":"Cool Hand Luke","released":1967,"color":true,"Actors":["Paul Newman"]},{"Title":"Bullitt","released":1968,"color":true,"Actors":["Steve McQueen","Jacqueline Bisset"]}]
- 實現json.Marshaler介面
```
package main
import (
"encoding/json"
"fmt"
)
func main() {
var s S
s.a = 5
s.b[0] = 3.123
s.b[1] = 111.11
s.b[2] = 1234.123
s.c = "hello"
s.d[0] = 0x55
j, _ := json.Marshal(s)fmt.Println(string(j))
}
type S struct {
a int
b [4]float32
c string
d [12]byte
}
func (this S) MarshalJSON() ([]byte, error) {
return json.MarshalIndent(map[string]interface{}{ // json.MarshalIndent(struct, "", " ")
"a": this.a,
"b": this.b,
"c": this.c,
"d": this.d,
}, "", " ")
}
```{"a":5,"b":[3.123,111.11,1234.123,0],"c":"hello","d":[85,0,0,0,0,0,0,0,0,0,0,0]}
文本和HTML模板
函數
函式宣告
遞迴
多傳回值
錯誤
函數值
匿名函數
可變參數
Deferred函數
Panic異常
- 一般而言,當panic異常發生時,程式會中斷運行,並立即執行在該goroutine(可以先理解成線程,在第8章會詳細介紹)中被延遲的函數(defer 機制)
Recover捕獲異常
方法
方法聲明
基於指標對象的方法
通過嵌入結構體來擴充類型
方法值和方法運算式
Bit數組
封裝
介面
當設計一個新的包時,新的Go程式員總是通過建立一個介面的集合開始和後面定義滿足它們的具體類型。這種方式的結果就是有很多的介面,它們中的每一個僅只有一個實現。不要再這麼做了。這種介面是不必要的抽象;它們也有一個運行時損耗。你可以使用匯出機制(§6.6)來限制一個類型的方法或一個結構體的欄位是否在包外可見。介面只有當有兩個或兩個以上的具體類型必須以相同的方式進行處理時才需要。
當一個介面只被一個單一的具體類型實現時有一個例外,就是由於它的依賴,這個具體類型不能和這個介面存在在一個相同的包中。這種情況下,一個介面是解耦這兩個包的一個好好方式。
因為在Go語言中只有當兩個或更多的類型實現一個介面時才使用介面,它們必定會從任意特定的實現細節中抽象出來。結果就是有更少和更簡單方法(經常和io.Writer或 fmt.Stringer一樣只有一個)的更小的介面。當新的類型出現時,小的介面更容易滿足。對於介面設計的一個好的標準就是 ask only for what you need(只考慮你需要的東西)
Goroutines和Channels
基於共用變數的並發
包和工具
工具
測試
go test
反射