本章主要分成三個(gè)部分:第一部分包括基本語(yǔ)法和數(shù)據(jù)結(jié)構(gòu);第二部分討論方法和接口;第三部分介紹并發(fā)機(jī)制。 包、變量和函數(shù) 先看一個(gè)例子Packages.go:
package main import ( "fmt" "math/rand" ) func add(x int, y int) int { return x + y } func main() { fmt.Println("My favorite number is", rand.Intn(10)) fmt.Println(add(42, 13)) }
包:每個(gè) Go 程序都是由包(package)組成的,程序運(yùn)行的入口是包 main
。
導(dǎo)入:這個(gè)代碼用圓括號(hào)組合了導(dǎo)入,這是“打包”導(dǎo)入語(yǔ)句。同樣可以編寫(xiě)多個(gè)導(dǎo)入語(yǔ)句,例如:
import "fmt" import "math"
這個(gè)程序使用并導(dǎo)入了包 "fmt" 和 "math/rand"
。包名與導(dǎo)入路徑的最后一個(gè)目錄一致。例如,如果你導(dǎo)入了"math/rand"
,那么你就可以在程序里面直接寫(xiě)rand.Intn(10),但如果你導(dǎo)入的是"math"
,那么你就得寫(xiě)math.rand.Intn(10)。
函數(shù):函數(shù)可以沒(méi)有參數(shù)或接受多個(gè)參數(shù)。在這個(gè)例子中,add
接受兩個(gè) int 類型的參數(shù),注意類型在變量名之后。有沒(méi)有覺(jué)得很奇怪呢,下面就詳細(xì)解釋一下go這樣做的原因。 看一個(gè)c的例子:
int (*fp)(int (*ff)(int x, int y), int b)
我相信學(xué)過(guò)c的同學(xué)為了看懂這個(gè)都下了不少功夫吧,有沒(méi)有感覺(jué)到痛苦或者說(shuō)痛苦之后的那一點(diǎn)點(diǎn)優(yōu)越感。 什么?你還能看懂?那么再來(lái)一個(gè):
int (*(*fp)(int (*)(int, int), int))(int, int);// 10s內(nèi)看懂的同學(xué)給我回復(fù)下這個(gè)定義是啥,我請(qǐng)你吃飯。
用go語(yǔ)言的話這兩個(gè)例子的定義如下:
f func(func(int,int) int, int) int f func(func(int,int) int, int) func(int, int) int
是不是清晰多了,原來(lái)f返回的是一個(gè)函數(shù)啊。。。
函數(shù)參數(shù): 當(dāng)兩個(gè)或多個(gè)連續(xù)的函數(shù)命名參數(shù)是同一類型,則除了最后一個(gè)類型之外,其他都可以省略,比如func add(x int, y int) int可以寫(xiě)為func add(x, y int) int 函數(shù)返回值:
函數(shù)可以返回任意數(shù)量的返回值,比如func swap(x, y string) (string, string)。 并且,函數(shù)的返回值還可以被命名,并且像變量那樣使用,比如
func split(sum int) (x, y int) { x = sum * 4 / 9 y = sum - x return }
沒(méi)有參數(shù)的 return 語(yǔ)句返回結(jié)果的當(dāng)前值。 變量: var 語(yǔ)句定義了一個(gè)變量的列表;跟函數(shù)的參數(shù)列表一樣,類型在后面,可以定義在包或函數(shù)級(jí)別。比如:
package main import "fmt" var c, python, java bool func main() { var i int fmt.Println(i, c, python, java) }
變量的初始化: 變量定義可以包含初始值,每個(gè)變量對(duì)應(yīng)一個(gè)。如果初始化是使用表達(dá)式,則可以省略類型;變量從初始值中獲得類型。比如:var i, j int = 1, 2或者var c, python, java = true, false, "no!" 短聲明變量:在函數(shù)中,:=
簡(jiǎn)潔賦值語(yǔ)句在明確類型的地方,可以用于替代 var 定義。 函數(shù)外的每個(gè)語(yǔ)句都必須以關(guān)鍵字開(kāi)始(var
、func
、等等),:=
結(jié)構(gòu)不能使用在函數(shù)外。比如:
package main import "fmt" func main() { var i, j int = 1, 2 k := 3 c, python, java := true, false, "no!" fmt.Println(i, j, k, c, python, java) }
Go的基本類型:這里先簡(jiǎn)單列一下,后面再具體講
bool string int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr byte // uint8 的別名 rune // int32 的別名// 代表一個(gè)Unicode碼 float32 float64 complex64 complex128
零值:變量在定義時(shí)沒(méi)有明確的初始化時(shí)會(huì)賦值為零值。數(shù)值類型為 0
,布爾類型為 false
,字符串為 ""
。 類型轉(zhuǎn)換:Go 的在不同類型之間的項(xiàng)目賦值時(shí)需要顯式轉(zhuǎn)換。表達(dá)式 T(v) 將值 v 轉(zhuǎn)換為類型 T
。比如:
var i int = 42 var f float64 = float64(i) var u uint = uint(f)
或者,更加簡(jiǎn)單的形式:
i := 42 f := float64(i) u := uint(f)
類型推導(dǎo):在定義一個(gè)變量但不指定其類型時(shí)(使用沒(méi)有類型的 var 或 := 語(yǔ)句), 變量的類型由右值推導(dǎo)得出。 var i int j := i // j 也是一個(gè) int 但是當(dāng)右邊包含了未指名類型的數(shù)字常量時(shí),新的變量就可能是 int 、 float64 或complex128
。 這取決于常量的精度:
i := 42 // int f := 3.142 // float64 g := 0.867 + 0.5i // complex128
常量:常量的定義與變量類似,只不過(guò)使用 const 關(guān)鍵字,常量可以是字符、字符串、布爾或數(shù)字類型的值,常量不能使用 := 語(yǔ)法定義。 流程控制語(yǔ)句 for循環(huán):Go 只有一種循環(huán)結(jié)構(gòu)for循環(huán)。基本的 for 循環(huán)除了沒(méi)有了 ( )
之外(,看起來(lái)跟 C 或者 Java 中做的一樣,而 { }
是必須的。比如:
package main import "fmt" func main() { sum := 0 for i := 0; i < 10; i++ { sum += i } fmt.Println(sum) }
死循環(huán):如果省略了循環(huán)條件,循環(huán)就不會(huì)結(jié)束,因此可以用更簡(jiǎn)潔地形式表達(dá)死循環(huán)。
package main func main() { for { } }
If語(yǔ)句:if 語(yǔ)句除了沒(méi)有了 ( )
之外,看起來(lái)跟 C 或者 Java 中的一樣,而 { }
是必須的。
func sqrt(x float64) string { if x < 0 { return sqrt(-x) + "i" } return fmt.Sprint(math.Sqrt(x)) }
可以在if條件之前執(zhí)行一個(gè)簡(jiǎn)單的語(yǔ)句,由這個(gè)語(yǔ)句定義的變量的作用域僅在 if 范圍之內(nèi),比如:
func pow(x, n, lim float64) float64 { if v := math.Pow(x, n); v < lim { return v } return lim }
在 if 的便捷語(yǔ)句定義的變量同樣可以在任何對(duì)應(yīng)的 else 塊中使用,比如:
func pow(x, n, lim float64) float64 { if v := math.Pow(x, n); v < lim { return v } else { fmt.Printf("%g >= %g\n", v, lim) } // 這里開(kāi)始就不能使用 v 了 return lim }
Switch語(yǔ)句:switch 的條件從上到下的執(zhí)行,當(dāng)匹配成功的時(shí)候停止,除非以 fallthrough 語(yǔ)句結(jié)束,否則分支會(huì)自動(dòng)終止。比如:
switch i { case 0: case f(): }
當(dāng) i==0 時(shí)不會(huì)調(diào)用 f
。沒(méi)有條件的 switch 同 switch true
一樣,這一構(gòu)造使得可以用更清晰的形式來(lái)編寫(xiě)長(zhǎng)的 if-then-else 鏈。
func main() { t := time.Now() switch { case t.Hour() < 12: fmt.Println("Good morning!") case t.Hour() < 17: fmt.Println("Good afternoon.") default: fmt.Println("Good evening.") } }
Defer語(yǔ)句:defer 語(yǔ)句會(huì)延遲函數(shù)的執(zhí)行直到上層函數(shù)返回,延遲調(diào)用的參數(shù)會(huì)立刻生成,但是在上層函數(shù)返回前函數(shù)都不會(huì)被調(diào)用。比如打印hello world:
func main() { defer fmt.Println("world") fmt.Println("hello") }
延遲的函數(shù)調(diào)用被壓入一個(gè)棧中。當(dāng)函數(shù)返回時(shí), 會(huì)按照后進(jìn)先出的順序調(diào)用被延遲的函數(shù)調(diào)用。
func main() { fmt.Println("counting") for i := 0; i < 10; i++ { defer fmt.Println(i) } fmt.Println("done") }
輸出
counting done 9 8 7 6 5 4 3 2 1 0
這個(gè)功能用來(lái)做清理工作非常清晰且強(qiáng)大,可以用結(jié)構(gòu)化編程的思路處理面向?qū)ο缶幊讨蓄愃莆鰳?gòu)函數(shù)的功能。舉一個(gè)實(shí)際點(diǎn)的例子:
func CopyFile(dstName, srcName string) (written int64, err error) { src, err := os.Open(srcName) if err != nil { return } defer src.Close() dst, err := os.Create(dstName) if err != nil { return } defer dst.Close() return io.Copy(dst, src) }
復(fù)雜類型 指針:Go 具有指針。 指針保存了變量的內(nèi)存地址,類型 T 是指向類型 T 的值的指針。其零值是 nil
,& 符號(hào)會(huì)生成一個(gè)指向其作用對(duì)象的指針, 符號(hào)表示指針指向的底層的值。
var p *int i := 42 p = &i fmt.Println(*p) // 通過(guò)指針 p 讀取 i *p = 21 // 通過(guò)指針 p 設(shè)置 i
與 C 不同,Go 沒(méi)有指針運(yùn)算。 結(jié)構(gòu)體:一個(gè)結(jié)構(gòu)體(struct
)就是一個(gè)字段的集合,跟C類似,結(jié)構(gòu)體字段使用點(diǎn)號(hào)來(lái)訪問(wèn),結(jié)構(gòu)體字段可以通過(guò)結(jié)構(gòu)體指針來(lái)訪問(wèn)。
type Vertex struct { X int Y int } func main() { fmt.Println(Vertex{1, 2}) v.X = 4 fmt.Println(v.X) p := &v p.X = 1e9 }
結(jié)構(gòu)體文法:結(jié)構(gòu)體文法表示通過(guò)結(jié)構(gòu)體字段的值作為列表來(lái)新分配一個(gè)結(jié)構(gòu)體。使用 Name: 語(yǔ)法可以僅列出部分字段。(字段名的順序無(wú)關(guān)。)特殊的前綴 & 返回一個(gè)指向結(jié)構(gòu)體的指針。
type Vertex struct { X, Y int } var ( v1 = Vertex{1, 2} // 類型為 Vertex v2 = Vertex{X: 1} // Y:0 被省略 v3 = Vertex{} // X:0 和 Y:0 p = &Vertex{1, 2} // 類型為 *Vertex )
數(shù)組:類型 [n]T 是一個(gè)有 n 個(gè)類型為 T 的值的數(shù)組,數(shù)組的長(zhǎng)度是其類型的一部分,因此數(shù)組不能改變大小。 Slice:一個(gè) slice 會(huì)指向一個(gè)序列的值,并且包含了長(zhǎng)度信息,[]T 是一個(gè)元素類型為 T 的 slice。slice 可以重新切片,創(chuàng)建一個(gè)新的 slice 值指向相同的數(shù)組。s[lo:hi] 表示從 lo 到 hi-1 的 slice 元素,含兩端,因此s[lo:lo] 是空的。下標(biāo)從0開(kāi)始。
package main import "fmt" func main() { p := []int{2, 3, 5, 7, 11, 13} fmt.Println("p ==", p) fmt.Println("p[1:4] ==", p[1:4]) // 省略下標(biāo)代表從 0 開(kāi)始 fmt.Println("p[:3] ==", p[:3]) // 省略上標(biāo)代表到 len(s) 結(jié)束 fmt.Println("p[4:] ==", p[4:]) }
構(gòu)造slice:slice 由函數(shù) make 創(chuàng)建。這會(huì)分配一個(gè)零長(zhǎng)度的數(shù)組并且返回一個(gè) slice 指向這個(gè)數(shù)組,比如:
a := make([]int, 5) // len(a)=5
為了指定容量,可傳遞第三個(gè)參數(shù)到 make
:
b := make([]int, 0, 5) // len(b)=0, cap(b)=5 b = b[:cap(b)] // len(b)=5, cap(b)=5 b = b[1:] // len(b)=4, cap(b)=4
slice 的零值是 nil
,一個(gè) nil 的 slice 的長(zhǎng)度和容量是 0。 向slice添加元素: func append(s []T, vs ...T) []T append 的第一個(gè)參數(shù) s 是一個(gè)類型為 T 的數(shù)組,append 的結(jié)果是一個(gè)包含原 slice 所有元素加上新添加的元素的 slice,如果 s 的底層數(shù)組太小,而不能容納所有值時(shí),會(huì)分配一個(gè)更大的數(shù)組,返回的 slice 會(huì)指向這個(gè)新分配的數(shù)組。 range: for 循環(huán)的 range 格式可以對(duì) slice 或者 map 進(jìn)行迭代循環(huán)。
package main import "fmt" var pow = []int{1, 2, 4, 8, 16, 32, 64, 128} func main() { for i, v := range pow { fmt.Printf("2**%d = %d\n", i, v) } }
可以通過(guò)賦值給 _ 來(lái)忽略序號(hào)和值,如果只需要索引值,去掉“, value”的部分即可。
func main() { pow := make([]int, 10) for i := range pow { pow[i] = 1 << uint(i) } for _, value := range pow { fmt.Printf("%d\n", value) } }
map:map 在使用之前必須用 make 而不是 new 來(lái)創(chuàng)建;值為 nil 的 map 是空的,并且不能賦值。
type Vertex struct { Lat, Long float64 } var m map[string]Vertex func main() { m = make(map[string]Vertex) m["Bell Labs"] = Vertex{ 40.68433, -74.39967, } fmt.Println(m["Bell Labs"]) }
map 的文法跟結(jié)構(gòu)體文法相似,不過(guò)必須有鍵名。
var m = map[string]Vertex{ "Bell Labs": Vertex{ 40.68433, -74.39967, }, "Google": Vertex{ 37.42202, -122.08408, }, }
如果頂級(jí)的類型只有類型名的話,可以在文法的元素中省略鍵名。
var m = map[string]Vertex{ "Bell Labs": {40.68433, -74.39967}, "Google": {37.42202, -122.08408}, }
修改 map: 在 map m 中插入或修改一個(gè)元素:m[key] = elem 獲得元素:elem = m[key] 刪除元素:delete(m, key) 通過(guò)雙賦值檢測(cè)某個(gè)鍵存在:elem, ok = m[key] 如果 key 在 m 中,ok
為 true 。否則, ok 為 false
,并且 elem 是 map 的元素類型的零值,同樣的,當(dāng)從 map 中讀取某個(gè)不存在的鍵時(shí),結(jié)果是 map 的元素類型的零值。
函數(shù)值:函數(shù)也是值。
package main import ( "fmt" "math" ) func main() { hypot := func(x, y float64) float64 { return math.Sqrt(x*x + y*y) } fmt.Println(hypot(3, 4)) }
函數(shù)的閉包:這個(gè)意思簡(jiǎn)單點(diǎn)就是以函數(shù)作為返回值。 Go 函數(shù)可以是閉包的。閉包是一個(gè)函數(shù)值,它來(lái)自函數(shù)體的外部的變量引用。 函數(shù)可以對(duì)這個(gè)引用值進(jìn)行訪問(wèn)和賦值;換句話說(shuō)這個(gè)函數(shù)被“綁定”在這個(gè)變量上。
package main import "fmt" func adder() func(int) int { sum := 0 return func(x int) int { sum += x return sum } } func main() { pos, neg := adder(), adder() for i := 0; i < 10; i++ { fmt.Println( pos(i), neg(-2*i), ) } }