Source

github.com/erlang/otp:Erlang tour.go-zh.org/concurrency/11:Go指南
go-zh.org/doc/:Go文档 go-zh.org/doc/code.html:Go Web编程
go-zh.org/pkg/:包手册 go-zh.org/ref/spec:语法规范
youtube.com/watch?v=TYZs:Go并发模型—-幻灯片talks.go-zh.org/2012/concurrency.slide
youtube.com/watch?v=QDD:深入Go并发模型—幻灯片talks.go-zh.org/2013/advconc.slide
go-zh.org/doc/codewalk/sharemem/:通过通信共享内存
vimeo:简单的编程模型—-幻灯片talks.go-zh.org/2012/simple.slide
go-zh.org/doc/wiki/:编写Web应用 go-zh.org/doc/codewalk/functions/:函数—Go一等公民
blog.go-zh.org/:Go博客—-mikespook博客mikespook.com/tag/golang/
github.com/astaxie/build-web-application-with-golang:Go Web编程
github.com/Unknwon/the-way-to-go_ZH_CN:Go 入门指南—The Way To Go

Basic

指针

  • Go拥有指针,指针保留了值的内存地址。与C不同,Go没有指针运算
    • 类型 T 是指向 T 类型值的指针。其零值为 nil。 var p int
    • & 操作符会生成一个指向其操作数的指针。

i := 42 p = &i

    • 操作符表示指针指向的底层值。

fmt.Println(p) // 通过指针 p 读取 i
p = 21 // 通过指针 p 设置 i
即通常所说的“间接引用”或“重定向”

结构体

  • 一个结构体(struct)就是一组字段(field)

    结构体字段通过点号或者指针来访问

  • 结构体文法通过直接列出字段的值来新分配一个结构体。

使用 Name: 语法可以仅列出部分字段。(字段名的顺序无关。)
特殊的前缀 & 返回一个指向结构体的指针

  • 类型 [n]T 表示拥有 n 个 T 类型的值的数组。

表达式var a [10]int 会将变量 a 声明为拥有 10 个整数的数组。
数组的长度是其类型的一部分,so数组不能改变大小。看似是个限制,不过Go 提供了更加便利的方式来使用数组

切片

  • 切片—每个数组的大小都是固定的。而切片则为数组元素提供动态大小的、灵活的视角。在实践中,切片比数组更常用。类型 []T 表示一个元素类型为 T 的切片。

切片通过两个下标来界定,即一个上界和一个下界,二者以冒号分隔:a[low : high]
它会选择一个半开区间,包括第一个元素,但排除最后一个元素。
切片并不存储任何数据,它只是描述了底层数组中的一段

  • 切片文法类似于没有长度的数组文法
  1. package main
  2. import "fmt"
  3. func main() {
  4. s := []int{2, 3, 5, 7, 11, 13}
  5. s = s[1:6]
  6. fmt.Println(s)
  7. s = s[:2]
  8. fmt.Println(s)
  9. s = s[1:1]
  10. fmt.Println(s)
  11. s = s[:]
  12. fmt.Println(s)
  13. }

结果

  1. [3 5 7 11 13]
  2. [3 5]
  3. []
  4. []

s = s[1:2]或者[1: ]
fmt.Println(s)
s = s[:]
fmt.Println(s)结果

  1. [3 5 7 11 13]
  2. [3 5]
  3. [5]
  4. [5]

s = s[1:3]
fmt.Println(s)
s = s[:]
fmt.Println(s)结果

  1. [3 5 7 11 13]
  2. [3 5]
  3. [5 7]
  4. [5 7]

s := []int{2, 3, 5, 7, 11, 13}
s = s[1:6]
fmt.Println(s)
s = s[:2]
fmt.Println(s)
s = s[:]
fmt.Println(s)结果

  1. [3 5 7 11 13]
  2. [3 5]
  3. [3 5]
  • 切片的长度就是它所包含的元素个数。

切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数

  1. package main
  2. import "fmt"
  3. func main() {
  4. s := []int{2, 3, 5, 7, 11, 13}
  5. printSlice(s)
  6. // 截取切片使其长度为 0
  7. s = s[:0]
  8. printSlice(s)
  9. // 拓展其长度
  10. s = s[:4]
  11. printSlice(s)
  12. // 舍弃前两个值
  13. s = s[2:]
  14. printSlice(s)
  15. }
  16. func printSlice(s []int) {
  17. fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
  18. }

结果

  1. len=6 cap=6 [2 3 5 7 11 13]
  2. len=0 cap=6 []
  3. len=4 cap=6 [2 3 5 7]
  4. len=2 cap=4 [5 7]
  • 内建函数 make 来创建切片,也是你创建动态数组的方式。

make 函数会分配一个元素为零值的数组并返回一个引用了它的切片:
a := make([]int, 5) // len(a)=5
要指定它的容量,需向 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

func append(s []T, vs …T) []T。append 的结果是一个包含原切片所有元素加上新添加元素的切片。
append 的第一个参数 s 是一个元素类型为 T 的切片,其余类型为 T 的值将会追加到该切片的末尾。
当 s 的底层数组太小,不足以容纳所有给定值时,它就会分配一个更大的数组。返回的切片会指向这个新分配的数组

映射/nil

  • 映射的零值为 nil 。nil 映射既没有键,也不能添加键。make 函数会返回给定类型的映射,并将其初始化备用

    映射必须有键名

    方法/函数—-指针

  • 方法即函数—-方法只是个带接收者参数的函数

  • 指针
    1. package main
    2. import (
    3. "fmt"
    4. "math"
    5. )
    6. type Vertex struct {
    7. X, Y float64
    8. }
    9. func (v Vertex) Abs() float64 {
    10. return math.Sqrt(v.X*v.X + v.Y*v.Y)
    11. }
    12. func (v *Vertex) Scale(f float64) {
    13. v.X = v.X * f
    14. v.Y = v.Y * f
    15. }
    16. func main() {
    17. v := Vertex{3, 4}
    18. v.Scale(10)
    19. fmt.Println(v.Abs())
    20. }
    结果为50

12行去掉*

  1. package main
  2. import (
  3. "fmt"
  4. "math"
  5. )
  6. type Vertex struct {
  7. X, Y float64
  8. }
  9. func (v Vertex) Abs() float64 {
  10. return math.Sqrt(v.X*v.X + v.Y*v.Y)
  11. }
  12. func (v Vertex) Scale(f float64) {
  13. v.X = v.X * f
  14. v.Y = v.Y * f
  15. }
  16. func main() {
  17. v := Vertex{3, 4}
  18. v.Scale(10)
  19. fmt.Println(v.Abs())
  20. }

结果为5

  • v.Scale(5),即便 v 是个值而非指针,带指针接收者的方法也能被直接调用。

即,由于 Scale 方法有一个指针接收者,为方便起见,Go 会将语句 v.Scale(5) 解释为 (&v).Scale(5)
带指针参数的函数必须接受一个指针

  1. var v Vertex
  2. ScaleFunc(v, 5) // 编译错误!
  3. ScaleFunc(&v, 5) // OK

相反,而以指针为接收者的方法被调用时,接收者既能为值又能为指针:

  1. var v Vertex
  2. v.Scale(5) // OK
  3. p := &v
  4. p.Scale(10) // OK

接受一个值作为参数的函数必须接受一个指定类型的值:

  1. var v Vertex
  2. fmt.Println(AbsFunc(v)) // OK
  3. fmt.Println(AbsFunc(&v)) // 编译错误!

而以值为接收者的方法被调用时,接收者既能为值又能为指针:

  1. var v Vertex
  2. fmt.Println(v.Abs()) // OK
  3. p := &v
  4. fmt.Println(p.Abs()) // OK

这种情况下,方法调用 p.Abs() 会被解释为 (*p).Abs()

  • 使用指针接收者的原因:

首先,方法能够修改其接收者指向的值。
其次,这样可以避免在每次调用方法时复制该值。若值的类型为大型结构体时,这样做会更加高效

接口

  • 接口也是值。它们可以像其它值一样传递。

接口值可以用作函数的参数或返回值。
在内部,接口值可以看做包含值和具体类型的元组:(value, type)
接口值保存了一个具体底层类型的具体值。
接口值调用方法时会执行其底层类型的同名方法

  1. package main
  2. import (
  3. "fmt"
  4. "math"
  5. )
  6. type I interface {
  7. M()
  8. }
  9. type T struct {
  10. S string
  11. }
  12. func (t *T) M() {
  13. fmt.Println(t.S)
  14. }
  15. type F float64
  16. func (f F) M() {
  17. fmt.Println(f)
  18. }
  19. func main() {
  20. var i I
  21. i = &T{"Hello"}
  22. describe(i)
  23. i.M()
  24. i = F(math.Pi)
  25. describe(i)
  26. i.M()
  27. }
  28. func describe(i I) {
  29. fmt.Printf("(%v, %T)\n", i, i)
  30. }
  1. (&{Hello}, *main.T)
  2. Hello
  3. (3.141592653589793, main.F)
  4. 3.141592653589793

1.main

  • i = &T{“Hello”} describe(i) 返回(&{Hello}, *main.T)
  • i.M()返回 Hello
  • i = F(math.Pi) describe(i) 调用17行的函数,返回(3.141592653589793, main.F)
  • i.M()返回 3.141592653589793
    • nil 接口值既不保存值也不保存具体类型
    • 指定了零个方法的接口值被称为 空接口:,空接口可保存任何类型的值,可以用来处理未知类型的值

      类型判断-恐慌

  • 类型断言 提供了访问接口值底层具体值的方式。t := i.(T)

该语句断言接口值 i 保存了具体类型 T,并将其底层类型为 T 的值赋予变量 t。
若 i 并未保存 T 类型的值,该语句就会触发一个恐慌。
为了 判断 一个接口值是否保存了一个特定的类型,类型断言可返回两个值:其底层值以及一个报告断言是否成 功的布尔值。t, ok := i.(T)
若 i 保存了一个 T,那么 t 将会是其底层值,而 ok 为 true,否则,ok 将为 false。 而 t 将为 T 类型的零值, 程序并不会产生恐慌
恐慌在哪里?恐慌的是返回的值不确定

stringer

  • Stringer可以用string描述自己的类型

    Reader

    io 包指定了 io.Reader 接口,它表示从数据流的末尾进行读取。
    Go 标准库包含了该接口的许多实现,包括文件、网络连接、压缩和加密等。
    io.Reader 接口有一个 Read 方法:
    func (T) Read(b []byte) (n int, err error)
    Read 用数据填充给定的字节切片并返回填充的字节数和错误值。在遇到数据流的结尾时它会返回一个 io.EOF 错误

    Import

    import包中有import接口
    Bounds 方法的返回值 Rectangle 实际上是一个 image.Rectangle,它在 image 包中声明。
    color.Color 和 color.Model 类型也是接口,但是通常因为直接使用预定义的实现 image.RGBA 和 image.RGBAModel 而被忽视了。这些接口和类型由 image/color 包定义

    Go程

    Go 程(goroutine)是由 Go 运行时管理的轻量级线程。go f(x, y, z)
    会启动一个新的 Go 程并执行:f(x, y, z)
    f, x, y 和 z 的求值发生在当前的 Go 程中,而 f 的执行发生在新的 Go 程中。
    Go 程在相同的地址空间中运行,因此在访问共享的内存时必须进行同步。sync 包提供了这种能力,不过在 Go 中并不经常用到,因为还有其它的办法

    信道<-

    有类型的管道(“箭头”就是数据流的方向)和映射与切片一样,信道在使用前必须创建:ch := make(chan int)
    默认情况下,发送和接收操作在另一端准备好之前都会阻塞。这使得 Go 程可以在没有显式的锁或竞态变量的情况下进行同步

  • 带缓冲的信道

ch := make(chan int, 100)
仅当信道的缓冲区填满后,向其发送数据时才会阻塞。当缓冲区为空时,接受方会阻塞
未填满时

  1. package main
  2. import "fmt"
  3. func main() {
  4. ch := make(chan int, 1)
  5. ch <- 1
  6. ch <- 2
  7. fmt.Println(<-ch)
  8. fmt.Println(<-ch)
  9. }

错误

互斥锁???

sync.Mutex—-信道非常适合在各个 Go 程间进行通信。
但是如果并不需要通信呢?比如若我们只是想保证每次只有一个 Go 程能够访问一个共享的变量,从而避免冲突?
这里涉及的概念叫做 互斥(mutualexclusion) ,通常使用 互斥锁(Mutex)* 这一数据结构来提供这种机制。
Go 标准库中提供了 sync.Mutex 互斥锁类型及其两个方法:

  • Lock
  • Unlock

可以通过在代码前调用 Lock 方法,在代码后调用 Unlock 方法来保证一段代码的互斥执行—比如Inc 方法。
也可以用 defer 语句来保证互斥锁一定会被解锁