本文是學習 A Tour of Go (中文參考 Go 之旅中文 ) 整理的筆記,介紹Go 語言的指針,結構體,數組,切片,映射和閉包的基本概念和使用。
1. 指針
$GOPATH/src/go_note/gotour/advancetype/pointer/pointer.go
源碼如下:
/**
* go 語言指針
*/
package main
import (
"fmt"
)
func main() {
i, j := 42, 36
p := &i
fmt.Println(*p) // 42
*p = 21
fmt.Println(i) // 21
p = &j
*p = *p / 4
fmt.Println(j) // 9
}
Go 具有指針。 指針保存了變量的內存地址。類型 *T
是指向 T
類型值的指針,其零值為 nil 。
var p *int
&
操作符會生成一個指向其操作數的指針。
i := 42
p = &i
*
操作符表示指針指向的底層值。
fmt.Println(*p) // 通過指針 p 讀取 i
*p = 21 // 通過指針 p 設置 i
2. 結構體
$GOPATH/src/go_note/gotour/advancetype/struct/struct.go
源碼如下:
package main
import (
"fmt"
)
type Point struct {
X int
Y int
}
var (
p1 = Point{1, 2}
p2 = Point{X: 1}
p3 = Point{}
pt1 = &Point{1, 2}
)
func main() {
fmt.Println(Point{1, 2})
p := Point{3, 4}
p.X = 4 // 結構體字段使用點號來訪問
fmt.Println(p, p.Y)
pt := &p
pt.X = 5 // 使用隱式間接引用,直接寫 `pt.X`
(*pt).Y = 6 // 通過 `(*pt).Y` 來訪問其字段 `Y`
fmt.Println(p)
}
一個結構體( struct )就是一個字段的集合,結構體字段使用點號來訪問。
2.1 結構體指針
結構體字段可以通過結構體指針來訪問。
pt := &p
pt.X = 5
(*pt).Y = 6
如果我們有一個指向結構體的指針 pt
,那么可以通過 (*pt).X
來訪問其字段 X
,也可以使用隱式間接引用,直接寫 pt.X
。
2.2 結構體語法
結構體文法通過直接列出字段的值來新分配一個結構體。
使用 Name:
語法可以僅列出部分字段。(字段名的順序無關。)
特殊的前綴 &
返回一個指向結構體的指針。
var (
p1 = Point{1, 2}
p2 = Point{X: 1}
p3 = Point{}
pt1 = &Point{1, 2}
)
3. 數組
$GOPATH/src/go_note/gotour/advancetype/array/array.go
源碼如下:
/**
* go 語言數組
*/
package main
import (
"fmt"
)
func main() {
var a [2]string
a[0] = "hello"
a[1] = "world"
fmt.Println(a, a[0])
list := [10]int{1, 2, 3, 4}
fmt.Println(list)
}
類型 [n]T
表示擁有 n
個 T
類型的值的數組。
表達式
var a [10]int // 變量 a 聲明為擁有 10 個整數的數組
數組的長度是其類型的一部分,因此數組不能改變大小
4. 切片
$GOPATH/src/go_note/gotour/advancetype/slice/slice.go
源碼如下:
/**
* go 語言 slice
*/
package main
import (
"fmt"
"strings"
)
func main() {
primes := [6]int{1, 2, 3, 4, 5, 6}
var s []int = primes[1:4]
fmt.Println(s) // [2 3 4]
names := [4]string{"john", "paul", "george", "ringo"}
fmt.Println(names) // [john paul george ringo]
a := names[0:2]
b := names[1:3]
fmt.Println(a, b) // [john paul] [paul george]
b[0] = "xxx" // 更改切片的元素會修改其底層數組中對應的元素,并且與它共享底層數組的切片都會觀測到這些修改
fmt.Println(a, b, names) // [john xxx] [xxx george] [john xxx george ringo]
// 默認分片上下界
i := []int{1, 2, 3, 4}
fmt.Println(i[0:4]) // [1 2 3 4]
fmt.Println(i[:4]) // [1 2 3 4]
fmt.Println(i[0:]) // [1 2 3 4]
fmt.Println(i[:]) // [1 2 3 4]
// 長度與容量
fmt.Printf("len=%d, cap=%d, %v\n", len(i[:4]), cap(i[:4]), i[:4]) // len=4, cap=4, [1 2 3 4]
fmt.Printf("len=%d, cap=%d, %v\n", len(i[2:4]), cap(i[2:4]), i[2:4]) // len=2, cap=2, [3 4]
// nil 切片
var j []int
fmt.Printf("len=%d, cap=%d, %v\n", len(j), cap(j), j) // len=0, cap=0, []
// make 創建切片
a_m := make([]int, 5)
b_m := make([]int, 0, 5)
fmt.Printf("len=%d, cap=%d, %v\n", len(a_m), cap(a_m), a_m) // len=5, cap=5, [0 0 0 0 0]
fmt.Printf("len=%d, cap=%d, %v\n", len(b_m), cap(b_m), b_m) // len=0, cap=5, []
// 包含切片的切片
board := [][]string{
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
}
for i := 0; i < len(board); i++ {
fmt.Printf("%s\n", strings.Join(board[i], " "))
}
// 向切片追加元素
var s_p []int
fmt.Printf("len=%d, cap=%d, %v\n", len(s_p), cap(s_p), s_p) // len=0, cap=5, []
s_p = append(s_p, 1)
s_p = append(s_p, 2)
fmt.Printf("len=%d, cap=%d, %v\n", len(s_p), cap(s_p), s_p) // len=0, cap=5, []
}
每個數組的大小都是固定的,而切片則提供動態數組,類型 []T
表示一個元素類型為 T
的切片。切片類似不限定長度的數組。
4.1 切片是數組的引用
切片并不存儲任何數據,它只是描述了底層數組中的一段。更改切片的元素會修改其底層數組中對應的元素,并且與它共享底層數組的切片都會觀測到這些修改。
如下創建一個切片時,會先創建數組,然后構建一個引用了它的切片
[]bool{true, true, false}
4.2 切片的默認行為
在進行切片時,切片有默認上下界。切片下界的默認值為 0 ,上界則是該切片的長度。
對于數組
var a [10]int
來說,以下切片是等價的:
a[0:10]
a[:10]
a[0:]
a[:]
4.3 切片的長度與容量
切片擁有 長度
和 容量
- 長度就是它所包含的元素個數。
- 容量是從它的第一個元素開始數,到其底層數組元素末尾的個數。
切片 s
的長度和容量可通過表達式 len(s)
和 cap(s)
來獲取。
切片的零值是 nil
, nil
切片的長度和容量為 0 且沒有底層數組。
4.4 make 創建切片
切片可以用內建函數 make
來創建,這也是你創建動態數組的方式。make
函數會分配一個元素為零值的數組并返回一個引用了它的切片:
a := make([]int, 5) // len(a)=5
要指定它的容量,需向 make 傳入第三個參數:
b := make([]int, 0, 5) // len(b)=0, cap(b)=5
4.5 切片的切片
切片可包含任何類型,甚至包括其它的切片
board := [][]string{
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
}
4.6 append 向切片追加元素
為切片追加新的元素是種常用的操作,為此 Go 提供了內建的 append
函數
func append(s []T, vs ...T) []T
append
的第一個參數 s
是一個元素類型為 T
的切片, 其余類型為 T
的值將會追加到該切片的末尾。append
的結果是一個包含原切片所有元素加上新添加元素的切片。當 s
的底層數組太小,不足以容納所有給定的值時,它就會分配一個更大的數組。 返回的切片會指向這個新分配的數組。
5. range
$GOPATH/src/go_note/gotour/advancetype/range/range.go
源碼如下:
/**
* go 語言 range用法
*/
package main
import "fmt"
var pow = []int{1, 2, 4, 8, 16, 32}
func main() {
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
// 2**0 = 1
// 2**1 = 2
// 2**2 = 4
// 2**3 = 8
// 2**4 = 16
// 2**5 = 32
s := make([]int, 5)
for i := range s {
s[i] = 1 << uint(i) // == 2**i
}
for _, v := range s {
fmt.Printf("%d\n", v)
}
}
for
循環的 range
形式可遍歷切片或映射。
當使用 for
循環遍歷切片時,每次迭代都會返回兩個值。 第一個值為當前元素的下標,第二個值為該下標所對應元素的一份副本。
可以將下標或值賦予 _
來忽略它, 若你只需要索引,去掉 , value
的部分即可。
6. 映射
$GOPATH/src/go_note/gotour/advancetype/map/map.go
源碼如下:
/**
* go 語言映射
*/
package main
import (
"fmt"
)
type Vertex struct {
Lit, Log float64
}
var m map[string]Vertex
var m1 = map[string]Vertex{ // 定義并初始化一個映射
"Bell Labs": Vertex{
40.123, -74.123,
},
"Google": Vertex{
37.123, -122.123,
},
}
var m2 = map[string]Vertex{ // 當頂級域名只是一個類型名時,可以在定義語句中省略它
"Bell Labs": {40.123, -74.123},
"Google": {37.123, -74.123},
}
func main() {
m = make(map[string]Vertex) // make 返回給定類型的映射,并將其初始化備用
m["Bell Labs"] = Vertex{40.123, -74.123}
fmt.Println(m["Bell Labs"], m2)
user := make(map[string]int)
user["Age"] = 42
fmt.Println(user["Age"])
delete(user, "Age")
fmt.Println(user["Age"])
v, ok := user["Age"]
fmt.Println("The Age:", v, "Presen?", ok)
}
映射將鍵映射到值, 映射的零值為 nil
, nil
映射既沒有鍵,也不能添加鍵。make
函數會返回給定類型的映射,并將其初始化備用。
6.1 修改映射
在映射 m
中插入或修改元素:
m[key] = elem
獲取元素:
elem = m[key]
刪除元素:
delete(m, key)
通過雙賦值檢測某個鍵是否存在:
elem, ok = m[key]
若 key
在 m
中, ok
為 true
;否則, ok
為 false
若 key
不在映射中,那么 elem
是該映射元素類型的零值。同樣的,當從映射中讀取某個不存在的鍵時,結果是映射的元素類型的零值。
7. 閉包
$GOPATH/src/go_note/gotour/advancetype/closure/closure.go
源碼如下:
/**
* go 語言閉包
*/
package main
import (
"fmt"
"math"
)
func compute(fn func(float64, float64) float64) float64{
return fn(3, 4)
}
// adder 返回一個閉包, 每個閉包都被綁定在其各自的 sum 變量上。
func adder() func (int) int {
sum := 0
return func (x int) int {
sum += x
return sum
}
}
func main() {
hypot := func (x, y float64) float64 {
return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(5, 12)) // 13
fmt.Println(compute(hypot)) // 5
fmt.Println(compute(math.Pow)) // 81
pos, neg := adder(), adder()
fmt.Printf("%v\n", pos)
for i := 0; i < 10; i++ {
fmt.Println(i, pos(i), -2*i, neg(-2*i))
}
}
函數也是值,它們可以像其它值一樣傳遞,函數值可以用作函數的參數或返回值。
Go 函數可以是一個閉包。閉包是一個函數值,它引用了其函數體之外的變量。該函數可以訪問并賦值其引用的變量的值,也即該函數被“綁定”在了這些變量上。