《go语言趣学指南》学习笔记
命令式编程
格式化输出
%v
:默认格式打印%b
:以二进制格式打印整数%f
:浮点数打印。%05.2f
表示宽度为5,精度为2,前面补0。%c
:打印Unicode字符%s
:以字符串打印%[1]v
:以默认格式打印第1个参数。参数索引以1开始。
循环和分支
// if语句
if 条件1 {
} else if 条件2 {
} else {
}
// switch语句
switch 变量 {
case 值1: 表达式1
case 值2: 表达式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() // 函数返回之前解锁
//
}