跳到主要内容

《go语言趣学指南》学习笔记

· 阅读需 9 分钟

命令式编程

格式化输出

  • %v :默认格式打印
  • %b :以二进制格式打印整数
  • %f :浮点数打印。%05.2f 表示宽度为5,精度为2,前面补0。
  • %c :打印Unicode字符
  • %s :以字符串打印
  • %[1]v :以默认格式打印第1个参数。参数索引以1开始。

循环和分支

// if语句
if 条件1 {
} else if 条件2 {
} else {
}

// switch语句
switch 变量 {
case1: 表达式1
case2: 表达式2
default: 表达式3
// switch语句不需要加break,默认不执行下一分支的代码
// 如果要执行下一分支的代码,使用 fallthrough 关键字

// 循环
for 条件 {
}
for i := 0; i < 10; i++ {
}

类型

big 包:大数

rune:支持Unicode的字符类型

转换布尔值:布尔值没有相等的数字值或字符串值,不能与数字或字符串直接转换。

构建块

函数声明:

func Intn(n int) int {
// ...
}

方法声明:

func (k kelvin) celsius() celsius {
// ...
}

变量可以指向函数,函数也可以作为参数传递给其他函数。

匿名函数可以赋值给变量,然后像使用其他函数那样使用那个变量。匿名函数是闭包的。

收集器

数组

// 数组的定义
var planets [8]string // 未被赋值的元素初始化为零值。string的零值:空字符串
dwarfs := [5]string{"Ceres", "Pluto", "Haumea", "Makemake", "Eris"}
dwarfs := [...]string{"Ceres", "Pluto", "Haumea", "Makemake", "Eris"}

// 数组的遍历
for i := 0; i < len(dwarfs); i++ {
dwarf := dwarfs[i]
}
for i, dwarf := range dwarfs {
fmt.Println(i, dwarf)
}

数组赋值给新变量或传递给函数,都会产生一个完整的副本,因此函数一般使用切片而不是数组作为形参。

切片

切片是左闭右开,如planets[0:4]包括索引0不包括索引4。

// 创建切片
dwarfs := []string{"Ceres", "Pluto", "Haumea", "Makemake", "Eris"}
// 字符串切片排序
sort.StringSlice(planets).Sort()
sort.Strings(planets)
// 添加元素
dwarfs = append(dwarfs, "Orcus", "Sedna")
// 切片长度
len(slice)
// 切片容量
cap(slice)

如果底层数组没有足够的空间用于切片的append,会创建原数组2倍容量的新数组。使用三索引切片可以指定容量。

使用make创建切片:

s1 := make([]string, 5, 10) // 创建长度为5,容量为10的切片
s2 := make([]string, 5) // 创建长度为5,容量为5的切片

可变函数参数:

func terraform(prefix string, worlds ..string) []string {  // worlds是字符串切片

映射

// 声明map并赋初值
temperature := map[string]int{
"Earth": 15,
"Mars": -65,
}

// map取不存在的key时,返回零值
// 为了区分不存在该key与存在该key但值为0的情况,使用第2个参数
// ok为true时表示key存在,为false时表示key不存在
moon, ok := temperature["Moon"]

// 使用make对映射实行预分配
make(map[float64]int, 8)

映射在赋值或传参时传递的是引用而不是副本。

状态与行为

结构

// 结构定义
type location struct {
lat, lng float64
}

func main() {
bradbury := location{-4.5895, 137.4417}
// 结构在赋值时被复制
curiosity := bradbury
curiosity.lng += 0.0106
fmt.Println(bradbury, curiosity)
}

{-4.5895 137.4417} {-4.5895 137.4523}

转为json

type location struct {
Lat float64 `json:"latitude"` // 字段必须大写
Lng float64 `json:"longitude"`
}

func main() {
bradbury := location{-4.5895, 137.4417}
bytes, _ := json.Marshal(bradbury)
fmt.Println(string(bytes))
}

{"latitude":-4.5895,"longitude":137.4417}

组合与转发

import "fmt"

type temperature struct {
low int
high int
}

func (t temperature) average() int {
return (t.low + t.high) / 2
}

// report嵌入temperature类型,可以使用temperature的属性和方法。
type report struct {
sol int
temperature // 结构嵌入
}

func main() {
report := report{
sol: 100,
temperature: temperature{low: 10, high: 33},
}
fmt.Printf("average temperature: %v, low: %v, high: %v, sol: %v\n",
report.average(), report.low, report.high, report.sol) // 自动转发
}

average temperature: 21, low: 10, high: 33, sol: 100

存在命名冲突时,如果没有调用那么编译器只会指出命名冲突但程序可以继续运行,如果有调用那么编译器会报错。

接口

var t interface {
talk() string
}

type cat struct{}
type dog int

func (c cat) talk() string {
return "miao"
}

func (d dog) talk() string {
return strings.Repeat("wang ", int(d))
}

func main() {
t = cat{}
fmt.Println(t.talk())
t = dog(3)
fmt.Printf(t.talk())
}

miao
wang wang wang

只要提供了满足接口要求的方法,都可以赋值给接口变量。

深入Go语言

指针

一般在struct、数组等类型上使用,map被赋值或实参传递时不会被复制所以不用指针。

// 通过指针修改切片
func reclassify(planets *[]string) {
*planets = (*planets)[0:8]
}

指针和接口

type talker interface {
talk() string
}

// martian &martian 均满足talker接口
func (m martian) talk() string
// &laser 满足talker接口,laser不满足talker接口
func (l *laser) talk() string

错误处理

defer关键字。使用defer延迟操作,会在函数返回前触发。

处理惊恐

func main() {
defer func() {
if e := recover(); e != nil {
fmt.Println(e)
}
}()
panic("I forgot my towel")
}

panic会在退出程序之前执行所有被延迟的操作,如果被延迟的函数调用了recover,那么惊恐会停止,程序会继续执行。

并发编程

goroutine和并发

go关键字:启动goroutine。goroutine会并发执行。

make可以创建通道,通道需要指定类型。

func executor(i int, c chan int) {  // 通道形参也需要指定类型
time.Sleep(1 * time.Second)
fmt.Println("> id", i, "done!")
c <- i // 向通道传值
}

func main() {
c := make(chan int) // 创建通道
for i := 0; i < 5; i++ {
go executor(i, c)
}
for i := 0; i < 5; i++ {
r := <-c // 从通道中取值
fmt.Println("executor", r, "done")
}
}

select语句包含的每个case分支都持有一个针对通道的接收或发送操作,select会等待直到某个分支的操作就绪,然后执行该操作及其关联的分支语句。

time.After 会返回一个通道,会在经过特定时间之后从通道接收到一个值(time.Time类型)

func executor(i int, c chan int) {
time.Sleep(time.Duration(rand.Intn(4000)) * time.Millisecond)
fmt.Println("> id", i, "done!")
c <- i
}

func main() {
c := make(chan int)
timeout := time.After(2 * time.Second)
for i := 0; i < 5; i++ {
go executor(i, c)
}
for i := 0; i < 5; i++ {
select {
case r := <-c:
fmt.Println("executor", r, "done")
case <-timeout:
fmt.Println("timeout!")
return
}
}
}

当goroutine在等待通道的发送或者接收操作时会被阻塞。

关闭通道:close(c) 。通道被关闭之后将无法写入任何值,尝试写入会引发惊恐,尝试读取会获得通道类型对应的零值。

// 检查通道是否已经被关闭
v, ok := <- c
// 如果ok为false,说明通道已被关闭

并发状态

import "sync"

var mu sync.Mutex

func main() {
mu.Lock() // 上锁
defer mu.Unlock() // 函数返回之前解锁
//
}