運行方式()編程
Golang提供了go run「解釋」執行和go build編譯執行兩種運行方式,所謂的「解釋」執行其實也是編譯出了可執行文件後才執行的。數組
Package管理()緩存
Golang約定:咱們能夠用./或../相對路徑來引本身的package;若是不是相對路徑,那麼go會去$GOPATH/src下查找。安全
格式化輸出()網絡
相似C、Java等語言,Golang的fmt包提供了格式化輸出功能,並且像%d、%s等佔位符和\t、\r、\n轉義也幾乎徹底一致。但Golang的Println不支持格式化,只有Printf支持,因此咱們常常會在後面加入\n換行。此外,Golang加入了%T打印值的類型,%v打印數組等集合的全部元素。數據結構
Go語言基本類型閉包
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // uint8 的別名
rune // int32 的別名
// 表示一個 Unicode 碼點
float32 float64
complex64 complex128併發
變量和常量()app
1.雖然Golang是靜態類型語言,卻用相似JavaScript中的var關鍵字聲明變量。並且像一樣是靜態語言的Scala同樣,支持類型自動推斷。有一點很重要的不一樣是:若是明確指明變量類型的話,類型要放在變量名後面。這有點彆扭吧?!後面會看到函數的入參和返回值的類型也要這樣聲明。
2.短變量聲明
在函數中,簡潔賦值語句 := 可在類型明確的地方代替 var 聲明。
注意: 函數外的每一個語句都必須以關鍵字開始( var 、 func 等等), 所以 := 結構不能在函數外使用。函數
類型轉換
表達式 T(v) 將值 v 轉換爲類型 T.
var i int = 42 var f float64 = float64(i) var u uint = uint(f) 或者這樣寫 i := 42 f := float64(i) u := uint(f)
常量
常量的聲明與變量相似,只不過是使用 const 關鍵字。
常量能夠是字符、字符串、布爾值或數值。
常量不能用 := 語法聲明。
const ( f = 12i ) func main(){ const ( a = 2 b = 3.12 c = true d = "sssss" ) fmt.Println(a, b, c, d, f) }
控制語句()
做爲最基本的語法要素,Golang的各類控制語句也是特色鮮明。在對C繼承發揚的同時,也有本身的想法融入其中:
5.1 if/switch/for 的條件部分都沒有圓括號(能夠寫),但必須有花括號。
5.2 switch的case中不須要break(默認break);
5.3 若是case語句後,想繼續下一個case語句執行,需加入fallthrogh
5.4 沒有條件的 switch 同 switch true 同樣。
5.5 switch的case條件能夠是多個值。
5.6 Golang中沒有while。
分號和花括號()
注意: "分號和花括號 "
分號由詞法分析器在掃描源代碼過程自動插入的,分析器使用簡單的規則:若是在一個新行前方的最後一個標記是一個標識符(包括
像int和float64這樣的單詞)、一個基本的如數值這樣的文字、或break continue fallthrough return ++ – ) }中的一個時,它就
會自動插入分號。 分號的自動插入規則產生了「蝴蝶效應」:全部控制結構的左花括號不都能放在下一行。由於按照上面的規則,這樣
作會致使分析器在左花括號的前方插入一個分號,從而引發難以預料的結果。因此Golang中是不能隨便換行的。
函數()
7.1 func關鍵字。
7.2 最大的不一樣就是「倒序」的類型聲明。
7.3 不須要函數原型,引用的函數能夠後定義。這一點很好,不相似C語言裏要麼將「最底層抽象」的函數放在最前面定義,要麼寫一堆函數原型聲明在最前面。
7.4 函數的定義:
func 關鍵字 函數名(參數1..)(返回值1, 返回值2 ){
函數體
}
如:
func add(a int, b int)(ret int, err int){ return a+b, b }
7.5 函數也是值,它們能夠像其它值同樣傳遞。函數值能夠用做函數的參數或返回值。
func main() { toSqrt := func(x, y float64) float64 { return math.Sqrt(x*x + y*y) } fmt.Println(toSqrt(12, 5)) fmt.Println(autoSqrt(toSqrt)) fmt.Println(autoSqrt(math.Pow)) } func autoSqrt(fn func(x, y float64) float64) float64 { return fn(4, 3) }
7.6 函數的閉包
Go 函數能夠是一個閉包。閉包是一個函數值,它引用了其函數體以外的變量。 該函數能夠訪問並賦予其引用的變量的值,換句話說,該函數被「綁定」在了這些變量上。
集合()基本數據結構 slice, struct
Golang提供了數組和Map做爲基本數據結構:
func test02() { source := []int{1, 2, 3, 4, 5, 6} var sliceTmp []int = source[2:5] //注意 [2:n] 它爲左閉右開, 例子即便: 從 下標2 開始至 下標4 fmt.Printf("%v\n", sliceTmp) }
func main() { source := [6]int{1, 2, 3, 5, 4} var s []int = source[2:6] fmt.Println(s) source[5] = 7 fmt.Println(s) s[0] = 88 fmt.Println(source) } 輸出: [3 5 4 0] [3 5 4 7] [1 2 88 5 4 7]
a := []int{1, 2, 3 } s := []struct{ age int name string }{ {1, "xiaoming"}, {21. "xiaohua"}, {23, "nhao"}, ** 注意, 最後一個逗號不能省 ** }
func main() { // a 是切片 a := []int{12, 5, 3, 6, 8, 6} // 讓切片的長度爲 0 a = a[:0] printSlice(a) // 擴充切片的長度 a = a[:3] printSlice(a) // 丟掉開始的兩個元素 a = a[2:] printSlice(a) } func printSlice(s []int) { fmt.Printf("len = %d, cap = %d, value = %v\n", len(s), cap(s), s) } 輸出: len = 0, cap = 6, value = [] len = 3, cap = 6, value = [12 5 3] len = 1, cap = 4, value = [3]
func main() { var s []int fmt.Println(s, len(s), cap(s)) if s == nil { fmt.Println("nil!") } } 輸出: [] 0 0 nil!
func main() { a := make([]int, 6) printSlice("a", a) b := make([]int, 0, 5) printSlice("b", b) c := make([]int, 3, 5) printSlice("c", c) d := b[:2] printSlice("d", d) e := d[2:5] printSlice("e", e) } func printSlice(flag string, s []int) { fmt.Printf("%s, len = %d, cap = %d, value = %v\n", flag, len(s), cap(s), s) } 輸出: a, len = 6, cap = 6, value = [0 0 0 0 0 0] b, len = 0, cap = 5, value = [] c, len = 3, cap = 5, value = [0 0 0] d, len = 2, cap = 5, value = [0 0] e, len = 3, cap = 3, value = [0 0 0]
8.8 Go的數組 與 C語言數組的區別
Go的數組是值語義。一個數組變量表示整個數組,它不是指向第一個元素的指針(不像 C 語言的數組)。 當一個數組變量被賦值或者被傳遞的時候,實際上會複製整個數組。 (爲了不復制數組,你能夠傳遞一個指向數組的指針,可是數組指針並非數組。) 能夠將數組看做一個特殊的struct,結構的字段名對應數組的索引,同時成員的數目固定。
8.9 map結構的使用
例如:
type People struct { age int name string } var m map[string]People func test04() { m = make(map[string]People) fmt.Println(m)= m["Afra55"] = People{ 22, "Victor", } m["xiaohuo"] = People{ 24, "nihao", //注意:這種寫法,最後一個','必定不能少,不然爲語法錯誤 } fmt.Println(m) fmt.Println(m["Afra55"]) } 輸出: map[] map[Afra55:{22 Victor} xiaohuo:{24 nihao}] {22 Victor}
修改 map 映射
在映射 m 中插入或修改元素: m[key] = elem
獲取元素: elem = m[key]
刪除元素: delete(m, key)
經過雙賦值檢測某個鍵是否存在: elem, ok = m[key]
若 key 在 m 中, ok 爲 true ;不然, ok 爲 false 。
若 key 不在映射中,那麼 elem 是該映射元素類型的零值。
一樣的,當從 映射 中讀取某個不存在的鍵時,結果是 映射 的元素類型的零值。
指針和內存分配()
Golang中可使用指針,並提供了兩種內存分配機制:
9.1 new:分配長度爲0的空白內存,返回類型T*。new 返回的是一個函數指針。
9.2 make:僅用於 切片、map、chan消息管道,返回類型T而不是指針
面向對象編程("不太懂")
Golang的結構體跟C有幾點不一樣:
10.1 結構體能夠有方法,其實也就至關於OOP中的類了。
10.1.1 支持帶名稱的初始化。
10.1.2 用指針訪問結構中的屬性也用」.」而不是」->」,指針就像Java中的引用同樣。
10.1.3 沒有public,protected,private等訪問權限控制。C也沒有protected,C中默認是public的,private須要加static關鍵字限定。Golang中方法名大寫就是public的,小寫就是private的。
10.2 同時,Golang支持接口和多態,並且接口有別於Java中繼承和實現的方式,而是採起了相似Ruby中更爲新潮的DuckType。只要struct與interface有相同的方法,就認爲struct實現了這個接口。就比如只要能像鴨子那樣叫,咱們就認爲它是一隻鴨子同樣。
10.3 Go 沒有類。然而,能夠在結構體類型上定義方法。
10.4 能夠對包中的任意類型(以type定義的類)定義任意方法,而不只僅是針對結構體。可是,不能對來自其餘包的類型或基礎類型定義方法。
10.5 有時候咱們須要將接受方法的對象定義爲指針,這樣能夠有兩個效果:
1) 能夠提升參數傳遞的效率,不用拷貝。
2) 修改接收者指向的值。
異常處理 (不太懂)
11.1 Golang中異常的使用比較簡單,能夠用errors.New建立,也能夠實現Error接口的方法來自定義異常類型,同時利用函數的多返回值特性能夠返回異常類。比較複雜的是defer和recover關鍵字的使用。Golang沒有采起try-catch「包住」可能出錯代碼的這種方式,而是用 延遲處理的方式.
11.2 用defer調用的函數會之後進先出(LIFO)的方式,在當前函數結束後依次順行執行。defer的這一特色正好能夠用來處理panic。當panic被調用時,它將當即中止當前函數的執行並開始逐級解開函數堆棧,同時運行全部被defer的函數。若是這種解開達到堆棧的頂端,程序就死亡了。
11.3 可是,也可使用內建的recover函數來從新得到Go程的控制權並恢復正常的執行。因爲僅在解開期間運行的代碼處在被defer的函數以內,recover僅在被延期的函數內部纔是有用的。
11.4 defer語句會將函數推遲到外層函數返回以後執行。
推遲調用的函數其參數會當即求值,但直到外層函數返回前該函數都不會被調用。
func main() { defer sqrt(9) fmt.Println("9 * 9: ") } func sqrt(x float64) { fmt.Println(x * x) } 打印的值: 9 * 9; 81
goroutine(協程)
goroutine使用Go關鍵字來調用函數,也可使用匿名函數。能夠簡單的把go關鍵字調用的函數想像成pthread_create。若是一個goroutine沒有被
阻塞,那麼別的goroutine就不會獲得執行。也就是說goroutine阻塞時,Golang會切換到其餘goroutine執行,這是很是好的特性!Java對相似
goroutine這種的協程沒有原生支持,像Akka最懼怕的就是阻塞。由於協程不等同於線程,操做系統不會幫咱們完成「現場」保存和恢復,因此要實現
goroutine這種特性,就要模擬操做系統的行爲,保存方法或函數在協程「上下文切換」時的Context,當阻塞結束時才能正確地切換回來。像Kilim等
協程庫利用字節碼生成,可以勝任,而Akka徹底是運行時的。
注意:"若是你要真正的併發,須要調用runtime.GOMAXPROCS(CPU_NUM)設置".
"本身的觀察: Go程相似線程,執行完後, 從本身的函數中就直接退出, 不會回到主進程空間,同時不須要回收資源"
原子操做()
像Java同樣,Golang支持不少CAS操做。運行結果是unsaftCnt可能小於200,由於unsafeCnt++在機器指令層面上不是一條指令,而多是從內存加載
數據到寄存器、執行自增運算、保存寄存器中計算結果到內存這三部分,因此不進行保護的話有些更新是會丟失的。
Channel管道()
14.1 經過前面能夠看到,儘管goroutine很方便很高效,但若是濫用的話極可能會致使併發安全問題。而Channel就是用來解決這個問題的,它是goroutine
之間通訊的橋樑,相似Actor模型中每一個Actor的mailbox。多個goroutine要修改一個狀態時,能夠將請求都發送到一個Channel裏,而後由一個
goroutine負責順序地修改狀態。
14.2 Channel默認是阻塞的,也就是說select時若是沒有事件,那麼當前goroutine會發生讀阻塞。同理,Channel是有大小的,當Channel滿了時,
發送方會發生寫阻塞。Channel這種阻塞的特性加上goroutine能夠很容易就能實現生產者-消費者模式。
14.3 用case能夠給Channel設置阻塞的超時時間,避免一直阻塞。而default則使select進入無阻塞模式。
14.4 有緩存管道與無緩存管道的區別
14.4.1 對於無緩衝的channel,放入操做和取出操做不能再同一個routine中,並且應該是先確保有某個routine對它執行取出操做,而後才能在另外一個routine中執行放入操做
14.4.2 在使用帶緩衝的channel時必定要注意放入與取出的速率問題
14.4.3 使用channel控制goroutine數量
緩衝流()
15.1 Golang的bufio包提供了方便的緩衝流操做,經過strings或網絡IO獲得流後,用bufio.NewReader/Writer()包裝:
15.2 緩衝區:Peek()或Read時,數據會從底層進入到緩衝區。緩衝區默認大小爲4096字節。
15.3 切片和拷貝:Peek()和ReadSlice()獲得的都是切片(緩衝區數據的引用)而不是拷貝,因此更加節約空間。可是當緩衝區數據變化時,切片也會隨之變化。
而ReadBytes/String()獲得的都是數據的拷貝,能夠放心使用。
15.4 Unicode支持:ReadRune()能夠直接讀取Unicode字符。有意思的是Golang中Unicode字符也要用單引號,這點與Java不一樣。
15.5 分隔符:ReadSlice/Bytes/String()獲得的包含分隔符,bufio不會自動去掉。
15.6 Writer:對應地,Writer提供了WriteBytes/String/Rune。
15.7 undo方法:能夠將讀出的字節再放回到緩衝區,就像什麼都沒發生同樣。
由於:當原始切片的容量不夠,增長後,新的切片指向了一個新的地址,開發者此時極容易使用 slice 特性,致使返回的結果不是所指望的。
//1.錯誤寫法 func test(s []int){ s.append(s, 3); //此時slice將與傳入的slice指向不一樣內存地址,因此想獲得理想的結果,就須要將新的slice地址傳出。 } func main(){ s := make([]int, 0) //建立一個容量爲 0 的slice; fmt.Println(s) test(s) //對這個建立的slice進行擴容 fmt.Println(s) } 打印結果爲: [0] [0] //2.正確寫法 func test(s []int) []int { s.append(s, 3) return s } func main(){ s := make([]int, 0) fmt.Println(s) s = test(s) fmt.Println(s) } 打印結果爲: [0] [3]
因此若是在操做slice時, 可能會使容量增大, 此時就必定要把新的slice返回出來。
由於:Go語言的設計時,提供了一組常量layout,來格式化它的時間輸出。可是,可是:要麼使用layout中提供的常量,要麼直接拷貝它的常量字符串,千萬不要對它的字符串進行修改,不然或形成輸出時間不正確。(語言設計時的硬坑)
//1.錯誤寫法 func closures() { s := make([]int, 3) for i := 0; i < 3; i++ { s[i] = i + 1 } for _, v := range s { //輪詢切片s,將取出的值,從地址中取出該值進行打印 ,由於主線程先運行完,等打印時全部的v都已變爲最後一個元素的地址,因此打印全是 3 go func() { fmt.Println(v) }() } } 打印結果爲: 3, 3, 3 //2.正確的寫法 func closures() { s := make([]int, 3) for i := 0; i < 3; i++ { s[i] = i + 1 } for _, v := range s { //輪詢切片s,將取出的值以值傳遞的方式傳入閉包函數中; go func(v int) { fmt.Println(v) }(v) } } 打印結果爲: 1, 2, 3