Go指南笔记

官方doc:English 中文

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 or var 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引导,不能使用短变量声明

流程控制语句

  • 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在开始操作之后立即编写结束代码。配合panicrecover可以对异常进行人为的处理。(感觉怎么像是对于大面积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 表示拥有 nT 类型的值的数组。
    • 赋值声明:primes := [6]int{2, 3, 5, 7, 11, 13} or primes := [...]int{2, 3, 5, 7, 11, 13}
    • 当一个数组变量被赋值或者被传递的时候,实际上会复制整个数组。
  • 切片

    • array[low: high],数组的引用。

    • 切片文法:primes := []int{2, 3, 5, 7, 11, 13},先创建了上方的数组,然后primes是上方数组的引用

    • 长度和容量:len(array) and cap(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仍然不能回收未使用的部分。
  • 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

    • fmt 包中定义的 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. 大括号不能在同一列(竟然被强行规定下来了,?)
  2. 进阶:接下来去哪?

练习

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/",
		},
	},
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值