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]
它会选择一个半开区间,包括第一个元素,但排除最后一个元素。
切片并不存储任何数据,它只是描述了底层数组中的一段
- 切片文法类似于没有长度的数组文法
package mainimport "fmt"func main() {s := []int{2, 3, 5, 7, 11, 13}s = s[1:6]fmt.Println(s)s = s[:2]fmt.Println(s)s = s[1:1]fmt.Println(s)s = s[:]fmt.Println(s)}
结果
[3 5 7 11 13][3 5][][]
s = s[1:2]或者[1: ]
fmt.Println(s)
s = s[:]
fmt.Println(s)结果
[3 5 7 11 13][3 5][5][5]
s = s[1:3]
fmt.Println(s)
s = s[:]
fmt.Println(s)结果
[3 5 7 11 13][3 5][5 7][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)结果
[3 5 7 11 13][3 5][3 5]
- 切片的长度就是它所包含的元素个数。
切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数
package mainimport "fmt"func main() {s := []int{2, 3, 5, 7, 11, 13}printSlice(s)// 截取切片使其长度为 0s = s[:0]printSlice(s)// 拓展其长度s = s[:4]printSlice(s)// 舍弃前两个值s = s[2:]printSlice(s)}func printSlice(s []int) {fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)}
结果
len=6 cap=6 [2 3 5 7 11 13]len=0 cap=6 []len=4 cap=6 [2 3 5 7]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 函数会返回给定类型的映射,并将其初始化备用
方法/函数—-指针
方法即函数—-方法只是个带接收者参数的函数
- 指针
结果为50package mainimport ("fmt""math")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 * fv.Y = v.Y * f}func main() {v := Vertex{3, 4}v.Scale(10)fmt.Println(v.Abs())}
12行去掉*
package mainimport ("fmt""math")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 * fv.Y = v.Y * f}func main() {v := Vertex{3, 4}v.Scale(10)fmt.Println(v.Abs())}
结果为5
- v.Scale(5),即便 v 是个值而非指针,带指针接收者的方法也能被直接调用。
即,由于 Scale 方法有一个指针接收者,为方便起见,Go 会将语句 v.Scale(5) 解释为 (&v).Scale(5)
带指针参数的函数必须接受一个指针
var v VertexScaleFunc(v, 5) // 编译错误!ScaleFunc(&v, 5) // OK
相反,而以指针为接收者的方法被调用时,接收者既能为值又能为指针:
var v Vertexv.Scale(5) // OKp := &vp.Scale(10) // OK
接受一个值作为参数的函数必须接受一个指定类型的值:
var v Vertexfmt.Println(AbsFunc(v)) // OKfmt.Println(AbsFunc(&v)) // 编译错误!
而以值为接收者的方法被调用时,接收者既能为值又能为指针:
var v Vertexfmt.Println(v.Abs()) // OKp := &vfmt.Println(p.Abs()) // OK
这种情况下,方法调用 p.Abs() 会被解释为 (*p).Abs()
- 使用指针接收者的原因:
首先,方法能够修改其接收者指向的值。
其次,这样可以避免在每次调用方法时复制该值。若值的类型为大型结构体时,这样做会更加高效
接口
- 接口也是值。它们可以像其它值一样传递。
接口值可以用作函数的参数或返回值。
在内部,接口值可以看做包含值和具体类型的元组:(value, type)
接口值保存了一个具体底层类型的具体值。
接口值调用方法时会执行其底层类型的同名方法
package mainimport ("fmt""math")type I interface {M()}type T struct {S string}func (t *T) M() {fmt.Println(t.S)}type F float64func (f F) M() {fmt.Println(f)}func main() {var i Ii = &T{"Hello"}describe(i)i.M()i = F(math.Pi)describe(i)i.M()}func describe(i I) {fmt.Printf("(%v, %T)\n", i, i)}
(&{Hello}, *main.T)Hello(3.141592653589793, main.F)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
- 类型断言 提供了访问接口值底层具体值的方式。t := i.(T)
该语句断言接口值 i 保存了具体类型 T,并将其底层类型为 T 的值赋予变量 t。
若 i 并未保存 T 类型的值,该语句就会触发一个恐慌。
为了 判断 一个接口值是否保存了一个特定的类型,类型断言可返回两个值:其底层值以及一个报告断言是否成 功的布尔值。t, ok := i.(T)
若 i 保存了一个 T,那么 t 将会是其底层值,而 ok 为 true,否则,ok 将为 false。 而 t 将为 T 类型的零值, 程序并不会产生恐慌
恐慌在哪里?恐慌的是返回的值不确定
stringer
-
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)
仅当信道的缓冲区填满后,向其发送数据时才会阻塞。当缓冲区为空时,接受方会阻塞
未填满时
package mainimport "fmt"func main() {ch := make(chan int, 1)ch <- 1ch <- 2fmt.Println(<-ch)fmt.Println(<-ch)}
互斥锁???
sync.Mutex—-信道非常适合在各个 Go 程间进行通信。
但是如果并不需要通信呢?比如若我们只是想保证每次只有一个 Go 程能够访问一个共享的变量,从而避免冲突?
这里涉及的概念叫做 互斥(mutualexclusion) ,通常使用 互斥锁(Mutex)* 这一数据结构来提供这种机制。
Go 标准库中提供了 sync.Mutex 互斥锁类型及其两个方法:
- Lock
- Unlock
可以通过在代码前调用 Lock 方法,在代码后调用 Unlock 方法来保证一段代码的互斥执行—比如Inc 方法。
也可以用 defer 语句来保证互斥锁一定会被解锁
