6.1 方法声明
func (p Point) Distance (q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
// p 为方法的接收器
x := Point{1, 2}
y := Point{3, 4}
fmt.Println(Distance(x, y)) // 函数
fmt.Println(x.Distance(y)) // 方法
6.2 基于指针对象的方法
- 声明
func (p *Point) ScaleBy (factor float64) {
p.X *= factor
p.Y *= factor
}
- 使用
// 1
r := &Point{1, 2}
r.ScaleBy(2)
// 2
p := Point{1, 2}
p.ScaleBy(2)
- 不管方法的接收器是指针还是非指针, 都可以直接通过指针或非指针进行调用, 会自动类型转换
- 如果对象本身不是很大, 当接收器是非指针时, 会调用一次拷贝
- 如果使用指针类型接收器, 始终指向内存地址
6.3 通过嵌入结构体来扩展类型
type Point struct {
X, Y float64
}
type ColoredPoint struct {
Point
Color color.RGBA
}
- 调用 Point 的方法
p := ColoredPoint{Point{1, 2}, red}
q := ColoredPoint{Point{3, 4}, blue}
p.Distance(q.Point) // 不能直接写q, 因为q不是Point类型
p.ScaleBy(2)
// 实现方式
func (p ColoredPoint) Distance (q Point) float64 {
return p.Point.Distance(q)
}
6.4 方法值和方法表达式
- 方法值
p := Point{1, 2}
q := Point{3, 4}
distanceFromP := p.Distance // 方法值
distanceFromP(q) // 相当于p.Distance(q)
time.AfterFunc(time.Second, func(){ r.Launch()})
time.AfterFunc (time. Second, r.Launch)
- 方法表达式
p := Point{1, 2}
q := Point{3, 4}
distance := Point.Distance // 方法表达式
distance(p, q)
// 用处
// 当根据一个变量决定调用同一个类型的哪个函数时
func (path Path) TranslateBy(offset Point, add bool) {
var op func(p, q Point) Point
if add {
op = Point.Add
} else {
op = Point.Sub
}
for i := range path {
// Call either path[i].Add(offset) or path[i].Sub(offset).
path[i] = op(path[i], offset)
}
}
7.1 接口约定
- 定义
type Writer interface {
Write(p []byte) (n int, err error)
}
- 实现
type ByteCounter int
func (c *ByteCounter) Write (p []byte) (int, error) {
*c += ByteCounter(len(p))
return len(p), nil
}
7.2 接口类型
- 接口可以像结构体一样嵌套
type ReadWriter interface {
Reader
Writer
}
7.3 实现接口的条件
- 如果一个类型实现了接口中的所有方法, 那这个类型就实现了这个接口
- 对于接口, 变量和指针要区分. 对于方法, 只是编译器隐式转换了
- 接口类型对具体类型进行了封装和隐藏。即使具体类型有其他的方法,在使用该实例时,只有实现了接口类型暴露出来的方法才会被调用到,其他方法都会被隐藏。
os.Stdout.Write([]byte("hello")) // OK, *os.File has Write method
os.Stdout.Close() // OK, *os.File has Close method
var w io.Writer
w = os.Stdout
w.Write([]byte("hello"))
w.Close() // error
- 空接口
var any interface{}
any = true
any = 1.2
any = "hello"
-
断言
- 类型断言
var v interface{} // ... str, ok := v.(string) // v是接口值, ()是类型断言操作符, string是期望的类型- 接口断言
var _ io.Writer = (*bytes.Buffer)(nil) // 把*bytes.Buffer类型的nil赋值给io.Writer类型的变量, 如果*bytes.BUffer没有实现io.Writer, 则编译时会报错
7.5 接口值
- 由两部分组成, type 和 value
var w io.Writer
w = os.Stdout // type: *os.File, value: os.Stdout
- 接口的比较
- 动态类型相同且动态值可以比较, 那就可以比较
- 动态类型相同但值不可以比较 (比如切片), 那比较就 panic
7.6 sort. Interface 接口
- 接口定义
package sort
type Interface interface {
Len() int
Less (i, j int) bool // indices
Swap(i, j int)
}
- 使用
sort.Sort(x)
sort.Sort(sort.Reverse(x))
7.8 error 接口
- error 类型是接口类型
type error interface {
Error() string
}
- errors 包
package errors
func New(text string) error { return &errorString{text} }
// 使用结构体, 防止修改错误信息
type errorString struct { text string }
// 使用指针类型, 保证每个New函数都分配了一个独一无二的实例, errors.New("EOF") == errors.New("EOF") false
func (e *errorString) Error() string { return e.text }
7.10 类型断言
- 第一种, 判断 x 的动态类型是否与具体类型 T 一致
var w io.Writer
w = os.Stdout
f := w.(*os.File) // success: f == os.Stdout
c := w.(*bytes.Buffer) // panic
- 第二种, 判断 x 的动态类型是否满足接口 T
var w io.Writer
w = os.Stdout // w的接口类型是io.Writer
rw := w.(io.ReadWriter) // rw 也是 os.Stdout, 但是接口类型是 io.ReadWriter
w = new(ByteCounter)
rw = w.(io.ReadWriter) // panic, 因为*ByteCounter没有Read方法
- 不 panic, 而是检验类型
var w io.Writer = os.Stdout
f, ok := w.(*os.File)
b, ok := w.(*bytes.Buffer)
7.13 类型分支
- 原理
func sqlQuote(x interface{}) string {
if x == nil {
return "NULL"
} else if _, ok := x.(int); ok {
return fmt.Sprintf("%d", x)
} else if _, ok := x.(uint); ok {
return fmt.Sprintf("%d", x)
} else if b, ok := x.(bool); ok {
if b {
return "TRUE"
}
return "FALSE"
} else if s, ok := x.(string); ok {
return sqlQuoteString(s) // (not shown)
} else {
panic(fmt.Sprintf("unexpected type %T: %v", x, x))
}
}
- 简化版本
switch x.(type) {
case nil: // ...
case int, uint: // ...
case bool: // ...
case string: // ...
default: // ...
}
8.1 Goroutines
- go f ()
8.4 Channels
- 创建 channel
ch := make(chan int)
- 发送和接收
ch <- x
x = <-ch
<-ch
- 关闭
close(ch)
- 关闭 channel 后, 再向 ch 中发送会导致 panic, 但仍可以接收到之前成功发送的数据, 如果没有数据的话会产生一个零值数据
- 判断 channel 是否被关闭
// 1
x, ok := <-ch
if !ok{
break // closed
}
// 2 当ch被关闭并且没有值时会跳出循环
for x := range ch {
fmt.Println(x)
}
- 单方向 channel
ch1 := chan<- int // 只能发送
ch2 := <-chan int // 只能接收
- 带缓存的 channel
ch := make(chan string, 3)
fmt.Println(cap(ch))
fmt.Println(len(ch))
8.5 并发的循环
- 使用计数器来等待 goroutine 完成
func makeThumbnails3(filenames []string) {
ch := make(chan struct{})
for _, f := range filenames {
go func(f string) {
thumbnail.ImageFile(f) // NOTE: ignoring errors
ch <- struct{}{}
}(f)
}
// Wait for goroutines to complete.
for range filenames {
<-ch
}
}
- 并发中匿名函数要显式传参
// 错误方式, 因为传入goroutine中的是f的引用, 等goroutine开始执行并读取f时, 这个for循环可能已经结束了, 结果f就只是最后一个值
for _, f := range filenames {
go func () {
thumbnail.ImageFile(f)
}()
}
- 使用计数器 sync. WaitGroup 为 goroutines 计数
func makeThumbnails6(filenames <-chan string) int64 {
sizes := make(chan int64)
var wg sync.WaitGroup // number of working goroutines
for f := range filenames {
wg.Add(1)
// worker
go func(f string) {
defer wg.Done()
thumb, err := thumbnail.ImageFile(f)
if err != nil {
log.Println(err)
return
}
info, _ := os.Stat(thumb) // OK to ignore error
sizes <- info.Size()
}(f)
}
// closer
go func() {
wg.Wait()
close(sizes)
}()
var total int64
for size := range sizes {
total += size
}
return total
- Add 和 Done 不对称, Add 在外部是为了确保 Add 在 closer goroutine 调用 wg. Wait ()之前调用
- 为什么 closer 要创建新 routine:
- 如果放在 main routine 当前位置, 将一直处于 wg. Wait ()状态, 因为无法到达 sizes 的循环, 无法接收通道, 形成阻塞
- 如果放在 for 循环后面, 会一直在循环内部, 因为关闭循环的动作在后面了
8.7 基于 select 的多路复用
- 语法
select {
case <-ch1:
// ...
case x := <-ch2:
// ...use x...
case ch3 <- y:
// ...
default:
// ...
}
- 作用
- 当需要从多个 channel 接收信息时, 如果直接接收的话, 当第一个 channel 中没有事件发过来时会形成阻塞
- 多路复用, 每个 case 代表一个通信操作 (接收或发送)
- 示例, 打印偶数
ch := make(chan int, 1)
for i := 0; i < 10; i++ {
select {
case x := <-ch:
fmt.Println(x) // "0" "2" "4" "6" "8"
case ch <- i:
}
}
- 当多个 case 同时就绪时, 随机选择一个执行.
- 利用 select 避免发送或接收导致的阻塞
select {
cast <- abort:
fmt.Printf("Launch aborted\n")
return
default:
// do nothing
}
8.9 并发的退出
- 当关闭了一个 channel 并浪费掉了所有已发送的值, 操作 channel 之后的代码可以立即被执行, 并且会产生零值
- 广播机制, 利用关闭一个 channel 进行广播
var done = make(chan struct{})
func cancelled() bool {
select {
case <-done:
return true
default:
return false
}
}
func walkDir(dir string, n *sync.WaitGroup, fileSizes chan<- int64) {
defer n.Done()
if cancelled() {
return
}
for _, entry := range dirents(dir) {
// ...
}
}
10.2 导入路径
import (
"fmt"
"math/rand"
"encoding/json"
"golang.org/x/net/html"
"github.com/go-sql-driver/mysql"
)
10.3 包声明
package rand- 每个 Go 语言源文件必须有包声明语句,用于确定当前包被其他包导入时默认的标识符。默认包名通常为导入路径名的最后一段,但有三种例外情况:main 包、测试的外部扩展包和依赖版本号的包。包名相同的包可以同时导入,但需要用不同的别名来区分。
10.4 导入声明
- 导入包的重命名
import (
"crypto/rand"
mrand "math/rand" // alternative name mrand avoids conflict
)
10.5 包的匿名导入
- 匿名导入 image/png
import _ "image/png"
- 优点:
- 侧效应导入: 不使用任何导出的函数变量和类型, 只使用 init 函数. 例如注册驱动, 添加插件
- 模块化: 通过匿名导入来扩展 img 包的功能, 而不需要修改 image. Decode 的调用代码
10.7 工具
-
工作区结构
- GOPATH 工作区目录
$ export GOPATH=$HOME/gobook $ go get gopl.io/...- GOPATH 子目录
- $GOPATH/src 存储源代码
- $GOPATH/pkg 保存编译后的包的目标文件
- $GOPATH/bin 保存编译后的可执行程序
- GOROOT 指定 Go 的安装目录以及标准包的位置
- $GOROOT/src
- $GOROOT/pkg
- $GOROOT/bin
go env查看环境变量的值
-
下载包
$ go get github.com/golang/lint/golintgo get获得的是本地存储仓库, 可以使用 git 比较- 默认只会获得本地不存在的包
-u会保证每个包都是最新的
-
构建包
go build如果包是一个库, 则忽略输出结果, 可以检测包是否可以被正确编译. 如果包的名字是 main 则会在当前目录创建可执行程序.- 默认指定
$cd $GOPATH/src/gopl.io/ch1/helloworld $go build - 绝对路径
$cd anywhere $go build gopl.io/ch1/helloworld - 相对路径
$cd $GOPATH go build ./src/gopl.io/ch1/helloworld # 必须以.或者..开头
- 默认指定
-
包文档
go doc- 包
- 函数
- 方法
-
内部包
- 包含 internal 名字的路径段的包就是内部包
- 一个内部包只可以被和 internal 的父目录下的包导入
net/http net/http/internal/chunked net/http/httputil net/url -
查询包
go list- 对于一次性的交互查询或自动化构建或测试脚本有帮助