「Go是一個開源的編程語言,它很容易用於構建簡單、可靠和高效的軟件。」(摘自Go語言官 方網站:http://golang.org )
Go語言不須要在語句或者聲明的末尾添加分號,除非一行上有多條語句。實際上,編譯器會主動把特定符號後的換行符轉換爲分號, 所以換行符添加的位置會影響Go代碼的正確解析html
c類語言中i++爲表達式,而go中,i++是語句而非表達式,且++i非法c++
*p++ //增長p指針指向的變量的值,而不改變p自身,與c/c++不一樣
不一樣類型間不能進行直接賦值操做程序員
數組長度是數組類型的一部分,[3]int
和[4]int
屬於不一樣類型golang
p := [3]int{1,2,3} p = [4]int{1,2,3,4} //error
函數的全部形參包括數組在內都是採用副本的形式傳入express
func test(a [4]int)[4]int{ for i,j := range a{ a[i] = j+1 } return a } func main() { var a = [4]int{1,2,3,4} fmt.Println(a) //1,2,3,4 var b = test(a) fmt.Println(a) //1,2,3,4 fmt.Println(b) //2,3,4,5 }
切片聲明時不須要指定大小,系統自動生成編程
s := []int{1,2,3,4} //與數組不一樣,不須要指定大小
值爲nil
的slice沒有底層數組,與nil相等的slice長度爲0,當長度爲0的slice不必定是nil數組
var s []int // len(s) == 0, s == nil s = nil // len(s) == 0, s == nil s = []int(nil) // len(s) == 0, s == nil s = []int{} // len(s) == 0, s != nil
內置的make函數建立一個指定元素類型、長度和容量的slice。容量部分能夠省略,在這種狀況下,容量將等於長度。緩存
make([]T, len) make([]T, len, cap) // same as make([]T, cap)[:len]
內置的append函數用於向slice追加元素:服務器
var runes []rune for _, r := range "Hello, 世界" { runes = append(runes, r) } fmt.Printf("%q\n", runes) // "['H' 'e' 'l' 'l' 'o' ',' ' ' '世' '界']"
一個結構體可能同時含有導出成員和未導出成員多線程
點操做符也能夠和指向結構體的指針一塊兒工做:
var employeeOfTheMonth *Employee = &dilbert employeeOfTheMonth.Position += " (proactive team player)"
至關於下面語句
(*employeeOfTheMonth).Position += " (proactive team player)"
若結構的全部成員都是可比較的,則結構體是可比較的當結構體全部成員都相等時,結構體變量相等
結構體能夠嵌入到另外一個結構體中
匿名成員的數據類型必須是命名的類型或指向一個命名的類型的指針
對於匿名嵌入,能夠直接訪問葉子屬性而不須要給出完整的路徑
type Point struct { X, Y int } type Circle struct { Point Radius int } type Wheel struct { Circle Spokes int } var w Wheel w.X = 8 // equivalent to w.Circle.Point.X = 8 w.Y = 8 // equivalent to w.Circle.Point.Y = 8 w.Radius = 5 // equivalent to w.Circle.Radius = 5 w.Spokes = 20
如下4中聲明所表明的含義相同
func add(x int, y int) int {return x + y} func sub(x, y int) (z int) { z = x - y; return} func first(x int, _ int) int { return x } func zero(int, int) int { return 0 } fmt.Printf("%T\n", add) // "func(int, int) int" fmt.Printf("%T\n", sub) // "func(int, int) int" fmt.Printf("%T\n", first) // "func(int, int) int" fmt.Printf("%T\n", zero) // "func(int, int) int"
go支持多個返回值
若是一個函數將全部的返回值都顯示的變量名,那麼該函數的return語句能夠省略操做數。這
稱之爲bare return。
// CountWordsAndImages does an HTTP GET request for the HTML // document url and returns the number of words and images in it. func CountWordsAndImages(url string) (words, images int, err error) { resp, err := http.Get(url) if err != nil { return } doc, err := html.Parse(resp.Body) resp.Body.Close() if err != nil { err = fmt.Errorf("parsing HTML: %s", err) return } words, images = countWordsAndImages(doc) return } func countWordsAndImages(n *html.Node) (words, images int) { /* ... */ }
在GO中,函數被看做第一類值:函數擁有類型,能夠賦值給其餘變量,傳遞給函數,從函數返回
可是函數值之間是不可比較的,也不能用函數值做爲map的key。
擁有函數名的函數只能在包級語法塊中被聲明,經過函數字面量(function literal),咱們可繞過這一限制,在任何表達式中表示一個函數值。函數字面量的語法和函數聲明類似,區別在於func關鍵字後沒有函數名。函數值字面量是一種表達式,它的值被稱爲匿名函數(anonymous function),也稱閉包。
,經過這種方式定義的函數能夠訪問完整的詞法環境(lexical environment),
這意味着在函數中定義的內部函數能夠引用該函數的變量,以下例所示:
// squares返回一個匿名函數。 // 該匿名函數每次被調用時都會返回下一個數的平方。 func squares() func() int { var x int return func() int { x++ return x * x } } func main() { f := squares() fmt.Println(f()) // "1" fmt.Println(f()) // "4" fmt.Println(f()) // "9" fmt.Println(f()) // "16" }
函數聲明包括函數名、形式參數列表、返回值列表(可省略)以及函數體。
func name(parameter-list)(result-list){ body }
擁有函數名的函數只能在包級語法塊中被聲明,經過函數字面量(function literal),咱們可繞過這一限制,在任何表達式中表示一個函數值。函數字面量的語法和函數聲明類似,區別 在於func關鍵字後沒有函數名。函數值字面量是一種表達式,它的值被稱爲匿名函數(anonymous function)。
經過這種方式定義的函數能夠訪問完整的詞法環境(lexical environment), 這意味着在函數中定義的內部函數能夠引用該函數的變量,以下例所示:
// squares返回一個匿名函數。 // 該匿名函數每次被調用時都會返回下一個數的平方。 func squares() func() int { var x int return func() int { x++ return x * x } } func main() { f := squares() fmt.Println(f()) // "1" fmt.Println(f()) // "4" fmt.Println(f()) // "9" fmt.Println(f()) // "16" }
函數squares返回另外一個類型爲 func() int 的函數。對squares的一次調用會生成一個局部變量x並返回一個匿名函數。每次調用時匿名函數時,該函數都會先使x的值加1,再返回x的平方。第二次調用squares時,會生成第二個x變量,並返回一個新的匿名函數。新匿名函數操做的是第二個x變量。
注意捕獲迭代變量
var rmdirs []func() for _, d := range tempDirs() { dir := d // NOTE: necessary! os.MkdirAll(dir, 0755) // creates parent directories too rmdirs = append(rmdirs, func() { os.RemoveAll(dir) }) } // ...do some work… for _, rmdir := range rmdirs { rmdir() // clean up }
在上面的程序中,for循環語句引入了新的詞法塊,循環 變量dir在這個詞法塊中被聲明。在該循環中生成的全部函數值都共享相同的循環變量。須要注意,函數值中記錄的是循環變量的內存地址,而不是循環變量某一時刻的值。以dir爲例, 後續的迭代會不斷更新dir的值,當刪除操做執行時,for循環已完成,dir中存儲的值等於最後一次迭代的值。這意味着,每次對os.RemoveAll的調用刪除的都是相同的目錄。這不是go或defer自己致使的,而是由於它們都會等待循環結束後,再執行函數值。
一般,爲了解決這個問題,咱們會引入一個與循環變量同名的局部變量,做爲循環變量的副 本。好比下面的變量dir,雖然這看起來很奇怪,但卻頗有用。
for _, dir := range tempDirs() { dir := dir // declares inner dir, initialized to outer dir // ... }
參數數量可變的函數稱爲爲可變參數函數。典型的例子就是fmt.Printf和相似函數。Printf首先接收一個的必備參數,以後接收任意個數的後續參數。在聲明可變參數函數時,須要在參數列表的最後一個參數類型以前加上省略符號「...」,這表示
該函數會接收任意數量的該類型參數。
func sum(vals...int) int { total := 0 for _, val := range vals { total += val } return total }
sum函數返回任意個int型參數的和。在函數體中,vals被看做是類型爲[] int的切片。sum能夠接收任意數量的int型參數:
fmt.Println(sum()) // "0" fmt.Println(sum(3)) // "3" fmt.Println(sum(1, 2, 3, 4)) // "10"
在上面的代碼中,調用者隱式的建立一個數組,並將原始參數複製到數組中,再把數組的一個切片做爲參數傳給被調函數。若是原始參數已是切片類型,咱們該如何傳遞給sum?只需在最後一個參數後加上省略符。下面的代碼功能與上個例子中最後一條語句相同。
values := []int{1, 2, 3, 4} fmt.Println(sum(values...)) // "10"
雖然在可變參數函數內部,...int 型參數的行爲看起來很像切片類型,但實際上,可變參數函數和以切片做爲參數的函數是不一樣的。
func f(...int) {} func g([]int) {} fmt.Printf("%T\n", f) // "func(...int)" fmt.Printf("%T\n", g) // "func([]int)"
當defer語句被執行時,跟在defer後面的函數會被延遲執行。直到包含該defer語句的函數執行完畢時,defer後的函數纔會被執行,不論包含defer語句的函數是經過return正常結束,仍是因爲panic致使的異常結束。你能夠在一個函數中執行多條defer語句,它們的執行順序與聲明順序相反。
defer語句常常被用於處理成對的操做,如打開、關閉、鏈接、斷開鏈接、加鎖、釋放鎖。經過defer機制,不論函數邏輯多複雜,都能保證在任何執行路徑下,資源被釋放。釋放資源的defer應該直接跟在請求資源的語句後。
Go的類型系統會在編譯時捕獲不少錯誤,但有些錯誤只能在運行時檢查,如數組訪問越界、空指針引用等。這些運行時錯誤會引發painc異常。
通常而言,當panic異常發生時,程序會中斷運行,並當即執行在該goroutine中被延遲的函數(defer 機制)。隨後,程序崩潰並輸出日誌信息。日誌信息包括panic value和函數調用的堆棧跟蹤信息。panic value一般是某種錯誤信息。對於每一個goroutine,日誌信息中都會有與之相對的,發生panic時的函數調用堆棧跟蹤信息。一般,咱們不須要再次運行程序去定位問題,日誌信息已經提供了足夠的診斷依據。所以,在咱們填寫問題報告時,通常會將panic異常和日誌信息一併記錄。
雖然Go的panic機制相似於其餘語言的異常,但panic的適用場景有一些不一樣。因爲panic會引發程序的崩潰,所以panic通常用於嚴重錯誤,如程序內部的邏輯不一致。
若是在deferred函數中調用了內置函數recover,而且定義該defer語句的函數發生了panic異常,recover會使程序從panic中恢復,並返回panic value。致使panic異常的函數不會繼續運行,但能正常返回。在未發生panic時調用recoverrecover會返回nil。
func Parse(input string) (s *Syntax, err error) { defer func() { if p := recover(); p != nil { err = fmt.Errorf("internal error: %v" , p) } }() // ...parser... }
deferred函數幫助Parse從panic中恢復。在deferred函數內部,panic value被附加到錯誤信息中;並用err變量接收錯誤信息,返回給調用者。咱們也能夠經過調用runtime.Stack往錯誤信息中添加完整的堆棧調用信息。
GO不支持類,但支持方法,能夠爲結構體或其餘類型定義方法,方法就是一類帶特殊的 接收者 參數的函數。方法接收者在它本身的參數列表內,位於 func
關鍵字和方法名之間。
type Vertex struct { X, Y float64 } func (v Vertex) Abs() float64 { //定義結構體方法 return math.Sqrt(v.X*v.X + v.Y*v.Y) } func main() { v := Vertex{3, 4} fmt.Println(v.Abs()) //v.Abs() 調用方法 }
其形式相似於將函數聲明中的形參放到函數名以前
只能爲同一個包的類型接收者聲明方法,不能爲其餘包內定義的類型聲明方法
能夠爲其餘類型定義方法,但不能爲內置類型(如:int
)定義方法
type MyFloat float64 func (f MyFloat) Abs() float64 { //ture if f < 0 { return float64(-f) } return float64(f) } //cannot define new methods on non-local type float64 //func (f float64) Abs() float64 { // if f < 0 { // return float64(-f) // } // return float64(f) //} func main() { f := MyFloat(-math.Sqrt2) fmt.Println(f.Abs()) }
使用指針接收者能夠改變接收者自身的值
type Vertex struct { X, Y float64 } func (v Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func (v *Vertex) Scale(f float64) { //指針接收者 v.X = v.X * f v.Y = v.Y * f } func main() { v := Vertex{3, 4} v.Scale(10) //v = {30,40} fmt.Println(v.Abs()) }
調用函數時,指針類型的形參必須接受一個指針;調用方法時接收者爲變量時能夠是指針也能夠爲值,編譯器會自動解引用或取地址。
var v Vertex ScaleFunc(v, 5) // 編譯錯誤! ScaleFunc(&v, 5) // OK var v Vertex v.Scale(5) // OK p := &v p.Scale(10) // OK
在現實的程序裏,通常會約定若是Point這個類有一個指針做爲接收器的方法,那麼全部Point 的方法都必須有一個指針接收器,即便是那些並不須要這個指針接收器的函數。
只有類型(Point)和指向他們的指針(*Point),纔是可能會出如今接收器聲明裏的兩種接收器。 此外,爲了不歧義,在聲明方法時,若是一個類型名自己是一個指針的話,是不容許其出如今接收器中的,好比下面這個例子:
type P *int func (P) f() { /* ... */ } // compile error: invalid receiver type
使用嵌入結構體時,被嵌入結構體能夠直接調用嵌入結構體的方法,
import "image/color" type Point struct{ X, Y float64 } type ColoredPoint struct { Point Color color.RGBA } red := color.RGBA{255, 0, 0, 255} blue := color.RGBA{0, 0, 255, 255} var p = ColoredPoint{Point{1, 1}, red} var q = ColoredPoint{Point{5, 4}, blue} fmt.Println(p.Distance(q.Point)) // "5" Distance是point類型的方法,p的類型爲ColoredPoint,但能夠直接調用Distance p.ScaleBy(2) q.ScaleBy(2) fmt.Println(p.Distance(q.Point)) // "10" 但參數類型爲Point時,必須顯示調用point字段
能夠將特定變量的方法調用賦值給變量,經過變量調用方法,其形式相似於函數變量的賦值:
p := Point{1, 2} q := Point{4, 6} distanceFromP := p.Distance // method value,選擇器返回一個方法值 fmt.Println(distanceFromP(q)) // "5" scaleP := p.ScaleBy // method value,選擇器返回一個方法值 scaleP(2) // p becomes (2, 4)
p.distance
,p.ScaleBy稱爲
選擇器,選擇器返回一個方法值。
在一個包的API須要一個函數值、且調用方但願操做的是某一個綁定了對象的方法的話,方
法"值"會很是實用。舉例來講,下面例子中的time.AfterFunc
這個函數的功能是在指定的延遲時間以後來執行一個(譯註:另外的)函數。且這個函數操做的是一個Rocket對象r
type Rocket struct { /* ... */ } func (r Rocket) Launch() { / ... */ } r := new(Rocket) time.AfterFunc(10 * time.Second, func() { r.Launch() }) //這裏至關於將r.Launch封裝爲一個函數傳入
直接用方法"值"傳入AfterFunc
的話能夠更爲簡短:
time.AfterFunc(10 * time.Second, r.Launch)
和方法"值"相關的還有方法表達式。當調用一個方法時,與調用一個普通的函數相比,咱們必需要用選擇器(p.Distance)語法來指定方法的接收器。當T是一個類型時,方法表達式可能會寫做T.f或者(*T).f,會返回一個函數"值",這種函數會將
其第一個參數用做接收器,因此能夠用一般(譯註:不寫選擇器)的方式來對其進行調用:
p := Point{1, 2} q := Point{4, 6} //這裏Point是類型名,其擁有一個方法func (p Point) Distance(), distance := Point.Distance // method expression fmt.Println(distance(p, q)) // "5" fmt.Printf("%T\n", distance) // "func(Point, Point) float64" scale := (*Point).ScaleBy scale(&p, 2) fmt.Println(p) // "{2 4}" fmt.Printf("%T\n" , scale) // "func(*Point, float64)"
以上的內容至關於將類型的一個方法轉化爲一個函數,該函數相較於方法多了第一個參數,該參數代表接收器,如上述將方法func (p Point) Distance()float64
轉化爲func(Point, Point) float64
在Go語言中還存在着另一種類型:接口類型。接口類型是一種抽象的類型。它不會暴露出它所表明的對象的內部值的結構和這個對象支持的基礎操做的集合;它們只會展現出它們本身的方法。也就是說當你有看到一個接口類型的值時,你不知道它是什麼,惟一知道的就是能夠經過它的方法來作什麼。
nil 接口值既不保存值也不保存具體類型。
也就是說,若是一個類型聲明瞭某個接口給出的全部方法,則認爲該類型繼承了該接口,而無需顯式說明。
Stringer
是一個能夠用字符串描述本身的類型。fmt
包(還有不少包)都經過此接口來打印值
type Person struct { Name string Age int } func (p Person) String() string { return fmt.Sprintf("%v (%v years)", p.Name, p.Age) } func main() { a := Person{"Arthur Dent", 42} z := Person{"Zaphod Beeblebrox", 9001} fmt.Println(a, z) //Arthur Dent (42 years) Zaphod Beeblebrox (9001 years) }
Go 程序使用 error
值來表示錯誤狀態。與 fmt.Stringer
相似,error
類型是一個內建接口:
type error interface { Error() string }
(與 fmt.Stringer
相似,fmt
包在打印值時也會知足 error
。)
package http type Handler interface { ServeHTTP(w ResponseWriter, r *Request) } func ListenAndServe(address string, h Handler) error
ListenAndServe函數須要一個例如「localhost:8000」的服務器地址,和一個全部請求均可以分派的Handler接口實例。它會一直運行,直到這個服務由於一個錯誤而失敗(或者啓動失敗),它的返回值必定是一個非空的錯誤。
類型斷言 提供了訪問接口值底層具體值的方式。
t := i.(T)
該語句斷言接口值 i
保存了具體類型 T
,並將其底層類型爲 T
的值賦予變量 t
。若 i
並未保存 T
類型的值,該語句就會觸發一個panic。
這裏有兩種可能。第一種,若是斷言的類型T是一個具體類型,而後類型斷言檢查x的動態類型是否和T相同。若是這個檢查成功了,類型斷言的結果是x的動態值,固然它的類型是T。換句話說,具體類型的類型斷言從它的操做對象中得到具體的值。若是檢查失敗,接下來這個操做會拋出panic。例如:
var w io.Writer w = os.Stdout f := w.(*os.File) // success: f == os.Stdout c := w.(*bytes.Buffer) // panic: interface holds *os.File, not *bytes.Buffer
上述中,w爲os.Stdout
,其類型爲*os.File
,因此w.(*os.File)
斷言成功,返回w的動態值,即os.Stdout
。由於w的類型與*bytes.Buffer
不符,因此c :=w.(*bytes.Buffer)
運行時返回panic
若是斷言類型T是接口類型,則類型斷言檢查x的動態類型是否知足T.若是此檢查成功,在下面代碼中,w.(io.ReadWriter)
檢查的是w的動態類型(即os.Stdout
的動態類型io.ReadWriter
),與w的接口io.Writer
無關。
var w io.Writer w = os.Stdout rw := w.(io.ReadWriter) // success: *os.File has both Read and Write w.Write([]byte("w write ")) // w.Read([]byte("w read")) //w.Read undefined (type io.Writer has no field or method Read) rw.Read([]byte("rw read")) rw.Write([]byte("rw write"))
在上面的第一個類型斷言後,w和rw都持有os.Stdout
所以它們每一個有一個動態類型*os.File
,可是變量w是一個io.Write
, r類型只對外公開出文件的Write方法,然而rw變量同時公開它的Read和write方法。
若是斷言操做的對象是一個nil接口值,那麼不論被斷言的類型是什麼這個類型斷言都會失敗。
爲了 判斷 一個接口值是否保存了一個特定的類型,類型斷言可返回兩個值:其底層值以及一個報告斷言是否成功的布爾值。
t, ok := i.(T)
func main() { var i interface{} = "hello" s := i.(string) //i保存的值的具體類型爲string fmt.Println(s) s, ok := i.(string) fmt.Println(s, ok) f, ok := i.(float64) fmt.Println(f, ok) f = i.(float64) // 報錯(panic) fmt.Println(f) }
類型選擇 是一種按順序從幾個類型斷言中選擇分支的結構。
類型選擇與通常的 switch 語句類似,不過類型選擇中的 case 爲類型(而非值), 它們針對給定接口值所存儲的值的類型進行比較。
func do(i interface{}) { switch v := i.(type) { case int: fmt.Printf("Twice %v is %v\n", v, v*2) case string: fmt.Printf("%q is %v bytes long\n", v, len(v)) default: fmt.Printf("I don't know about type %T!\n", v) } } func main() { do(21) do("hello") do(true) }
Go 程(goroutine)是由 Go 運行時管理的輕量級線程。
go f(x, y, z)
會啓動一個新的 Go 程並執行
信道是帶有類型的管道,你能夠經過它用信道操做符 <-
來發送或者接收值。
ch <- v // 將 v 發送至信道 ch。 v := <-ch // 從 ch 接收值並賦予 v。
(「箭頭」就是數據流的方向。)
和映射與切片同樣,信道在使用前必須建立:
ch := make(chan int) ch := make(chan int, 100) //帶緩衝信道
默認狀況下,發送和接收操做在另外一端準備好以前都會阻塞。這使得 Go 程能夠在沒有顯式的鎖或競態變量的狀況下進行同步。
發送者可經過 close(ch)
關閉一個信道來表示沒有須要發送的值了。接收者能夠經過爲接收表達式分配第二個參數來測試信道是否被關閉:若沒有值能夠接收且信道已被關閉,那麼在執行完
v, ok := <-ch
以後 ok
會被設置爲 false
值。循環 for i := range c
會不斷從信道接收值,直到它被關閉。
注意: 只有發送者才能關閉信道,而接收者不能。向一個已經關閉的信道發送數據會引起程序恐慌(panic)。
當一個被關閉的channel中已經發送的數據都被成功接收後,後續的接收操做將再也不阻塞,它們會當即返回一個零
還要注意: 信道與文件不一樣,一般狀況下無需關閉它們。只有在必須告訴接收者再也不有須要發送的值時纔有必要關閉,例如終止一個 range
循環。
Go語言的類型系統提供了單方向的channel類型,分別用於只發送或只接收的channel
。類型 chan<- int
表示一個只發送int的channel,只能發送不能接收。相反,類型<-chan int
表示一個只接收int的channel,只能接收不能發送。(箭頭 <- 和關鍵字chan的相對位置代表了channel的方向。)這種限制將在編譯期檢測。
帶緩存的Channel內部持有一個元素隊列。隊列的最大容量是在調用make函數建立channel時經過第二個參數指定的。下面的語句建立了一個能夠持有三個字符串元素的帶緩存Channel。
ch = make(chan string, 3)
使用內置函數cap能夠獲取channel緩存大小, 函數len能夠獲取channel有效數據個數
fmt.Println(cap(ch)) //緩衝區大小 fmt.Println(len(ch)) //channel中的有效數據個數
make(chan int)
和make(chan int,1)
的區別select
語句使一個 Go 程能夠等待多個通訊操做。
select
會阻塞到某個分支能夠繼續執行爲止,這時就會執行該分支。當多個分支都準備好時會隨機選擇一個執行。
當 select
中的其它分支都沒有準備好時,default
分支就會執行。爲了在嘗試發送或者接收時不發生阻塞,可以使用 default
分支:
select { case i := <-c: // 使用 i default: // 從 c 中接收會阻塞時執行 }
每個OS線程都有一個固定大小的內存塊(通常會是2MB)來作棧,這個棧會用來存儲當前正在被調用或掛起(指在調用其它函數時)的函數的內部變量。
相反,一個goroutine會以一個很小的棧開始其生命週期,通常只須要2KB。一個goroutine的棧,和操做系統線程同樣,會保存其活躍或掛起的函數調用的本地變量,可是和OS線程不太同樣的是一個goroutine的棧大小並非固定的;棧的大小會根據須要動態地伸縮。
OS線程由操做系統內核調用調用時會產生上下文切換,消耗資源較多
GO自身包含調度器,其調度消耗小於線程切換
在大多數支持多線程的操做系統和程序語言中,當前的線程都有一個獨特的身份(id),而且這個身份信息能夠以一個普通值的形式被被很容易地獲取到,典型的能夠是一個integer或者指針值。goroutine沒有能夠被程序員獲取到的身份(id)的概念。