Go指南:https://tour.go-zh.org/list
基础
包、变量、函数
-
包
- 按照约定,包名与导入路径的最后一个元素一致。例如,
"math/rand"
包中的源码均以package rand
语句开始
- 按照约定,包名与导入路径的最后一个元素一致。例如,
-
导出名
- 大写开头 -> 已导出,类似public
- 小写开头 -> 未导出,类似protected
-
函数
-
变量类型在声明之后,why
- 更直观,在复杂的函数声明中尤为明显
-
连续多个参数类型相同时,除最后一个外,其它可以不写
func add(x, y int) int ...
-
可以对返回值命名,需要在函数体内写入返回的变量名,然后返回空值
func split(sum int) (x, y int) { x = sum * 4 / 9 y = sum - x return }
-
函数值:函数也是值。它们可以像其它值一样传递。
-
支持闭包
package main import "fmt" // 返回一个“返回int的函数” func fibonacci() func() int { a := 0 b := 1 return func() int { ret := a a = b b = ret + b return ret } } func main() { f := fibonacci() for i := 0; i < 10; i++ { fmt.Println(f()) } }
-
-
变量
- 声明:
var name type
- 初始化:
var x int = 3
orvar x = 1
对于赋值的初始化,类型能省略不写,因为能够推测出来 - 短变量声明:在函数中,简洁赋值语句
:=
可在类型明确的地方代替var
声明
- 声明:
-
基本类型
bool string // int、uint、uintptr追随系统,32位系统为32位,64位系统为64位 int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr byte // uint8 的别名 rune // int32 的别名 // 表示一个 Unicode 码点 float32 float64 complex64 complex128
- 零值:没有明确初始值的变量声明会被赋予它们的 零值(0,false,"")。
var i int
- 类型转换:表达式
T(v)
将值v
转换为类型T
- 类型推导:未指明类型的数值常量时,数值由常量的精度推断
- 常量:使用
const
引导,不能使用短变量声明
- 零值:没有明确初始值的变量声明会被赋予它们的 零值(0,false,"")。
流程控制语句
-
for
-
只有for循环=。=
for i := 0; i < 10; i++ { fmt.Println(i) } for { // 无限循环 }
-
后置语句(上例的i++)是可选的
-
-
if
-
无需小括号,大括号必须
-
也拥有初始化语句
func pow(x, n, lim float64) float64 { if v := math.Pow(x, n); v < lim { fmt.Println(v) return v } return lim }
-
switch:只运行选定的case,而非之后所有的。
func main() { fmt.Println("When's Saturday?") today := time.Now().Weekday() // switch {} 不加变量时,默认是True switch time.Saturday { case today + 0: fmt.Println("Today.") case today + 1: fmt.Println("Tomorrow.") case today + 2: fmt.Println("In two days.") default: fmt.Println("Too far away.") } }
-
-
defer
-
外围函数执行完毕才执行该函数。推迟的函数调用会被压入一个栈中
defer fmt.Println("world") fmt.Println("hello")
-
more: Defer, Panic, and Recover
-
Defer可以确保程序结束之后的工作正常进行,让coder在开始操作之后立即编写结束代码。配合
panic
和recover
可以对异常进行人为的处理。(感觉怎么像是对于大面积try catch的优雅改造)package main import "fmt" func main() { f() fmt.Println("Returned normally from f.") } func f() { defer func() { if r := recover(); r != nil { fmt.Println("Recovered in f", r) } }() fmt.Println("Calling g.") g(0) fmt.Println("Returned normally from g.") } func g(i int) { if i > 3 { fmt.Println("Panicking!") panic(fmt.Sprintf("%v", i)) // 返回异常信息,4 } defer fmt.Println("Defer in g", i) fmt.Println("Printing in g", i) g(i + 1) }
-
-
更多类型:struct、slice和映射
-
指针
- 和C类似,但是不能对指针进行运算
-
结构体
type Vertex struct { X int Y int } v := Vertex{1, 2} v.X = 20 var ( v1 = Vertex{1, 2} // 创建一个 Vertex 类型的结构体 v2 = Vertex{X: 1} // Y:0 被隐式地赋予 v3 = Vertex{} // X:0 Y:0 p = &Vertex{1, 2} // 创建一个 *Vertex 类型的结构体(指针) )
-
数组
- 类型
[n]T
表示拥有n
个T
类型的值的数组。 - 赋值声明:
primes := [6]int{2, 3, 5, 7, 11, 13}
orprimes := [...]int{2, 3, 5, 7, 11, 13}
- 当一个数组变量被赋值或者被传递的时候,实际上会复制整个数组。
- 类型
-
切片
-
array[low: high],数组的引用。
-
切片文法:
primes := []int{2, 3, 5, 7, 11, 13}
,先创建了上方的数组,然后primes是上方数组的引用 -
长度和容量:
len(array)
andcap(array)
。容量是从它的第一个元素开始数,到其底层数组元素末尾的个数,长度就是它所包含的元素个数。 -
nil切片:长度和容量都为0的切片
-
make创建切片(动态数组):
make(array type, initial length, initial cap)
-
切片的切片(多维数组)
board := [][]string{ []string{"_", "_", "_"}, []string{"_", "_", "_"}, []string{"_", "_", "_"}, }
-
追加元素:
func append(s []T, vs ...T) []T
- e.g
var s []int; s = append(s, 2, 3, 4)
- 使用
...
语法进行参数展开:var x []int{1, 2}; var y []int{3}; var z = append(y, x...)
- more: Go 切片:用法和本质
- 一个切片是一个数组片段的描述。它包含了指向数组的指针,片段的长度,和容量(片段的最大长度)。
- 切片增长不能超出其容量。
- 避免陷阱:返回切片由于是数组的引用,即使只用一小部分,GC仍然不能回收未使用的部分。
- e.g
-
-
range
for
循环的range
形式可遍历切片或映射。当使用for
循环遍历切片时,每次迭代都会返回两个值。第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。for index, value := range slice {}
- 只需要索引的话,可以去掉value部分
-
映射
-
映射将键映射到值。
type Vertex struct { Lat, Long float64 } var m map[string]Vertex var m = map[string]Vertex{ "Bell Labs": Vertex{ 40.68433, -74.39967, }, "Google": Vertex{ 37.42202, -122.08408, }, } // 若顶级类型只是一个类型名,你可以在文法的元素中省略它。 var m = map[string]Vertex{ "Bell Labs": {40.68433, -74.39967}, "Google": {37.42202, -122.08408}, } m = make(map[string]Vertex) // 返回给定类型的映射,并将其初始化备用
-
删除元素
delete(map, key)
-
双赋值检测元素是否存在
elem, ok = m[key]
-
方法和接口
-
方法
-
方法就是一类带特殊的 接收者 参数的函数,方法接收者在它自己的参数列表内,位于
func
关键字和方法名之间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} fmt.Println(v.Abs()) }
-
方法只是个带接收者参数的函数
-
接收者的类型定义和方法声明必须在同一包内;不能为内建类型声明方法
-
指针重定向:由于
Scale
方法有一个指针接收者,为方便起见,Go 会将语句v.Scale(5)
解释为(&v).Scale(5)
;方法调用p := &v; p.Abs()
会被解释为(*p).Abs()
-
尽量使用指针接收者,然后避免混用指针和值接收者的情况。
- 一是可以在方法内改变接收者值,二是不必要每一次调用复制一次
-
-
接口
-
接口类型 是由一组方法签名定义的集合
type Abser interface { Abs() float64 } type MyFloat float64 // 接口隐式实现:此方法表示类型 MyFloat 实现了接口Abser,但我们无需显式声明此事。 func (f MyFloat) Abs() float64 { if f < 0 { return float64(-f) } return float64(f) } type Vertex struct { X, Y float64 } func (v *Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func main() { var a Abser f := MyFloat(-math.Sqrt2) v := Vertex{3, 4} a = f // a MyFloat 实现了 Abser a = &v // a *Vertex 实现了 Abser // 下面一行,v 是一个 Vertex(而不是 *Vertex) // 所以没有实现 Abser,会报错 a = v fmt.Println(a.Abs()) }
-
接口值:接口可以像值一样传递,作为函数的参数或返回值。在内部,接口值可以看做包含值和具体类型的元组
(value, type)
。type I interface {..}; var i I; fmt.Printf("(%v, %T)\n", i, i)
-
空接口:零个方法的接口值被称作空接口。可以保存任何类型的值,因为每个类型都至少实现了零个方法,?。可以用来接收未知类型的参数
func print(i interface{}) {..}
-
-
类型断言
- 类型断言 提供了访问接口值底层具体值的方式。有两种断言形式。
t := i.(T)
该语句断言接口值i
保存了具体类型T
,并将其底层类型为T
的值赋予变量t
。如果失败,会触发恐慌t, ok := i.(T)
如果失败,t为类型T的零值,ok为false,不会触发恐慌
-
类型选择
-
类型选择 是一种按顺序从几个类型断言中选择分支的结构。类似于switch
switch v := i.(type) { case T: // v 的类型为 T case S: // v 的类型为 S default: // 没有匹配,v 与 i 的类型相同 }
-
-
Stringer
-
/* type Stringer interface { String() string } */ 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) }
-
-
错误
-
Go 程序使用
error
值来表示错误状态。和fmt.Stringer类似,是一个内建接口type error interface { Error() string } i, err := strconv.Atoi("42") if err != nil { fmt.Printf("couldn't convert number: %v\n", err) return }
-
调用的它的代码应当判断这个错误是否等于
nil
来进行错误处理
-
-
Reader
io
包指定了io.Reader
接口,它表示从数据流的末尾进行读取。有一个Read
方法:func (T) Read(b []byte) (n int, err error)
-
图像
-
image
包定义了Image
接口package image type Image interface { ColorModel() color.Model Bounds() Rectangle At(x, y int) color.Color }
-
并发
-
Go程
- goroutine是由 Go 运行时管理的轻量级线程
go func
即可将func的执行放入新的Go程中
-
信道
-
信道是带有类型的管道,你可以通过它用信道操作符
<-
来发送或者接收值ch := make(chan int[, int]) // 创建信道,后面为信道的缓冲长度,当长度满后,向信道发送回阻塞 ch <- v // 将 v 发送至信道 ch。 v := <-ch // 从 ch 接收值并赋予 v。 v, ok := <-ch // 通过close关键字关闭信道,可以给接收表达式分配第二个参数来看信道是否关闭 for i := range(ch) {} // 会不断读取信道,直到信道关闭
-
-
select
-
select
语句使一个 Go 程可以等待多个通信操作,select
会阻塞到某个分支可以继续执行为止,这时就会执行该分支。当多个分支都准备好时会随机选择一个执行package main import "fmt" func fibonacci(c, quit chan int) { x, y := 0, 1 for { select { case c <- x: x, y = y, x+y case <-quit: fmt.Println("quit") return default: fmt.Println("default") // 所有select分支都没准备好时 } } } func main() { c := make(chan int) quit := make(chan int) go func() { for i := 0; i < 10; i++ { fmt.Println(<-c) } quit <- 0 }() fibonacci(c, quit)
-
-
sync.Mutex
-
互斥锁
package main import ( "fmt" "sync" "time" ) // SafeCounter 的并发使用是安全的。 type SafeCounter struct { v map[string]int mux sync.Mutex } // Inc 增加给定 key 的计数器的值。 func (c *SafeCounter) Inc(key string) { c.mux.Lock() // Lock 之后同一时刻只有一个 goroutine 能访问 c.v c.v[key]++ c.mux.Unlock() } // Value 返回给定 key 的计数器的当前值。 func (c *SafeCounter) Value(key string) int { c.mux.Lock() // Lock 之后同一时刻只有一个 goroutine 能访问 c.v defer c.mux.Unlock() return c.v[key] } func main() { c := SafeCounter{v: make(map[string]int)} for i := 0; i < 1000; i++ { go c.Inc("somekey") } time.Sleep(time.Second) fmt.Println(c.Value("somekey")) }
-
备忘
- 大括号不能在同一列(竟然被强行规定下来了,?)
- 进阶:接下来去哪?
练习
1.循环与函数
package main
import (
"fmt"
)
func Sqrt(x float64) float64 {
z := 1.0
for i := 0; i < 10; i++ {
z -= (z * z - x) / (2 * z)
}
return z
}
func main() {
fmt.Println(Sqrt(2))
}
2.切片
package main
import "golang.org/x/tour/pic"
import "math"
func Pic(dx, dy int) [][]uint8 {
ret := make([][]uint8, dy)
for y := range(ret) {
ret[y] = make([]uint8, dx)
for x := range(ret[y]) {
//ret[y][x] = uint8((x + y) / 2)
//ret[y][x] = uint8(x * y)
//ret[y][x] = uint8(x^y)
//ret[y][x] = uint8(math.Pow(float64(x), float64(y)))
ret[y][x] = uint8(float64(x) * math.Log(float64(y)))
//ret[y][x] = uint8(x % (y + 1))
}
}
return ret
}
func main() {
pic.Show(Pic)
}
3.映射
package main
import (
"golang.org/x/tour/wc"
"strings"
"fmt"
)
func WordCount(s string) map[string]int {
wc := make(map[string]int)
for _, word := range(strings.Fields(s)) {
fmt.Println(word, wc[word])
wc[word] += 1
}
return wc
}
func main() {
wc.Test(WordCount)
}
4.斐波纳契闭包
package main
import "fmt"
// 返回一个“返回int的函数”
func fibonacci() func() int {
a := 0
b := 1
return func() int {
ret := a
a, b = b, a + b
return ret
}
}
func main() {
f := fibonacci()
for i := 0; i < 10; i++ {
fmt.Println(f())
}
}
5.Stringer
package main
import "fmt"
type IPAddr [4]byte
// TODO: 给 IPAddr 添加一个 "String() string" 方法
func (i IPAddr) String() string {
return fmt.Sprintf("%v.%v.%v.%v", i[0], i[1], i[2], i[3])
}
func main() {
hosts := map[string]IPAddr{
"loopback": {127, 0, 0, 1},
"googleDNS": {8, 8, 8, 8},
}
for name, ip := range hosts {
fmt.Printf("%v: %v\n", name, ip)
}
}
6.错误
package main
import (
"fmt"
)
type ErrNegativeSqrt float64
func (e ErrNegativeSqrt) Error() string {
// 不把e转换为float64,那么这条句子其实是
// fmt.Sprint("cannot Sqrt negative number: %v", e.Error())
// 造成死循环
return fmt.Sprintf("cannot Sqrt negative number: %v", float64(e))
}
func Sqrt(x float64) (float64, error) {
if x < 0 {
return -1, ErrNegativeSqrt(x)
}
z := 1.0
for i := 0; i < 10; i++ {
z -= (z * z - x) / (2 * z)
}
return z, nil
}
func main() {
fmt.Println(Sqrt(2))
fmt.Println(Sqrt(-2))
}
7.Reader
package main
import "golang.org/x/tour/reader"
type MyReader struct{}
// TODO: 给 MyReader 添加一个 Read([]byte) (int, error) 方法
func (r MyReader) Read(data []byte) (int, error) {
for i := range(data) {
data[i] = 'A'
}
return len(data), nil
}
func main() {
reader.Validate(MyReader{})
}
8.rot13Reader
package main
import (
"io"
"os"
"strings"
)
type rot13Reader struct {
r io.Reader
}
func (rot *rot13Reader) Read(b []byte) (int, error) {
_, err := rot.r.Read(b)
if err != nil {
return 0, err
}
for i, v := range(b) {
if v >= 'A' && v <= 'Z' {
b[i] = byte((int(v) + 13 - int('A')) % 26 + int('A'))
} else if v >= 'a' && v <= 'z' {
b[i] = byte((int(v) + 13 - int('a')) % 26 + int('a'))
}
}
return len(b), nil
}
func main() {
s := strings.NewReader("Lbh penpxrq gur pbqr!")
r := rot13Reader{s}
io.Copy(os.Stdout, &r)
}
9.图像
package main
import "golang.org/x/tour/pic"
import "image"
import "image/color"
type Image struct{
w, h int
v uint8
}
func (i *Image) ColorModel() color.Model {
return color.RGBAModel
}
func (i *Image) Bounds() image.Rectangle {
return image.Rect(0, 0, i.w, i.h)
}
func (i *Image) At(x, y int) color.Color {
return color.RGBA{uint8(x) + i.v, uint8(y) + i.v, 255, 255}
}
func main() {
m := Image{70, 140, 200}
pic.ShowImage(&m)
}
10.等价二叉查找树
package main
import (
"golang.org/x/tour/tree"
"fmt"
)
/*
type Tree struct {
Left *Tree
Value int
Right *Tree
}
*/
// Walk 步进 tree t 将所有的值从 tree 发送到 channel ch。
func Walk(t *tree.Tree, ch chan int) {
walk(t, ch)
close(ch)
}
func walk(t *tree.Tree, ch chan int) {
if t != nil {
walk(t.Left, ch)
ch <- t.Value
walk(t.Right, ch)
}
}
// Same 检测树 t1 和 t2 是否含有相同的值。
func Same(t1, t2 *tree.Tree) bool {
tc1 := make(chan int)
tc2 := make(chan int)
go Walk(t1, tc1)
go Walk(t2, tc2)
for v1 := range(tc1) {
if v1 != <- tc2 {
return false
}
}
return true
}
func main() {
fmt.Println(Same(tree.New(1), tree.New(2)))
fmt.Println(Same(tree.New(3), tree.New(3)))
}
11.Web 爬虫
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
type SafeUrlCache struct {
v map[string]bool
mux sync.Mutex
}
type Fetcher interface {
// Fetch 返回 URL 的 body 内容,并且将在这个页面上找到的 URL 放到一个 slice 中。
Fetch(url string) (body string, urls []string, err error)
}
// Crawl 使用 fetcher 从某个 URL 开始递归的爬取页面,直到达到最大深度。
func Crawl(url string, depth int, fetcher Fetcher, c *SafeUrlCache) {
wg.Add(1)
go crawl(url, depth, fetcher, c)
wg.Wait()
}
func crawl(url string, depth int, fetcher Fetcher, c *SafeUrlCache) {
defer wg.Done()
if depth <= 0 {
return
}
c.mux.Lock()
if c.v[url] {
c.mux.Unlock()
return
}
c.v[url] = true
c.mux.Unlock()
body, urls, err := fetcher.Fetch(url)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("found: %s %q\n", url, body)
for _, u := range urls {
wg.Add(1)
go crawl(u, depth-1, fetcher, c)
}
return
}
// 等待进程结束,参考:https://www.jianshu.com/p/d55483e90329
func main() {
c := SafeUrlCache{v: make(map[string]bool)}
Crawl("https://golang.org/", 4, fetcher, &c)
}
// fakeFetcher 是返回若干结果的 Fetcher。
type fakeFetcher map[string]*fakeResult
type fakeResult struct {
body string
urls []string
}
func (f fakeFetcher) Fetch(url string) (string, []string, error) {
if res, ok := f[url]; ok {
return res.body, res.urls, nil
}
return "", nil, fmt.Errorf("not found: %s", url)
}
// fetcher 是填充后的 fakeFetcher。
var fetcher = fakeFetcher{
"https://golang.org/": &fakeResult{
"The Go Programming Language",
[]string{
"https://golang.org/pkg/",
"https://golang.org/cmd/",
},
},
"https://golang.org/pkg/": &fakeResult{
"Packages",
[]string{
"https://golang.org/",
"https://golang.org/cmd/",
"https://golang.org/pkg/fmt/",
"https://golang.org/pkg/os/",
},
},
"https://golang.org/pkg/fmt/": &fakeResult{
"Package fmt",
[]string{
"https://golang.org/",
"https://golang.org/pkg/",
},
},
"https://golang.org/pkg/os/": &fakeResult{
"Package os",
[]string{
"https://golang.org/",
"https://golang.org/pkg/",
},
},
}