无题

Golang 笔记

基础

Println 与 Printf 区别

  • Println: 可以打印出字符串,和变量
    fmt.Println(a)
  • Printf: 只可以打印出格式化的字符串,可以输出字符串类型的变量, 不可以输出整形变量和整形
    fmt.Printf("%d", a)  // right
    fmt.Println("abc")  // right
  • Sprintf: 格式化之后的字符串

枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func enums() {
const (
a = iota
_
b
c
)
// a = 0 b = 2 c= 3
const (
a = 10 * iota
_
b
c
)
// a = 0 b = 20 c= 30
}

if 双重判断

1
2
3
4
5
6
const filename = "abc.txt"
if contents, err := ioutil.ReadFile(filename); err == nil {
fmt.Println(string(contents))
} else {
fmt.Println(err)
}

拿函数名称

1
2
p := reflect.ValueOf(function).Pointer()
funcName := runtime.FuncForPC(p).Name()

指针

1
2
3
num := 1
func a(num *int){}
a(&num)

数组(一般不用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var arr1 [5]int
arr1 := [3]{1,3,5} // 固定数量
arr2 := [...]int {2, 4, 6, 8, 10} // 编译器自己数
var grid [4][5]int // 4行5列的数组,0填充

for index, value := range arr {
fmt.Println(i, v)
}

// 数组是值类型,并且函数规定传多少位的就必须多少位,要引用传递必须传指针
func printArr(arr *[5]int) {
fmt.Println(arr)
}
arr2 := [...]int{1, 2, 3, 4, 5}
printArr(&arr2)

切片 slice,数组的视图

要改变原数组,先转slice,下面操作后,原数组会被改变,slice 同样会被改变。
slice 为引用类型

1
2
3
4
func p(s []int) {
s[0] = 100
}
p(arr[:])

slice 属性有3个

  • prt 指向数组第一个
  • len slice 长度
  • cap slice 的底层数组

slice 为cap的切片 slice 可以切到后面的数据(cap),不能向前切
下标取值不可超过len(s1),向后切不可超过cap(s1)

1
2
3
4
5
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
s1 := arr[2:6]
s2 := s1[3:5] // 可以取到后面的值,但是s1[4]取不到
fmt.Printf("s1=%v, len=%d, cap= %d \n", s1, len(s1), cap(s1)) // s1=[2 3 4 5], len=4, cap= 6
fmt.Printf("s1=%v, len=%d, cap= %d \n", s2, len(s2), cap(s2)) // s1=[5 6], len=2, cap= 3 这里cap包含了 7

1
2
len(s1) // slice 长度
cap(s1) // 底层数组长度,view层看不见,但可以切片取到

append

  • append 超过 view 层则替换 原cap数组 数据,如果大于 cap 长度则开一个更大的 Array,原 cap 数组不会新增,可能被垃圾回收。
  • 需要一个值来接受append的返回值
  • len 将大于cap时,会拷贝一份slice,并添加新的数据
  • len可以一个一个增长,cap会为2的次方数,并且大于len

新建数组 var s []int

新建len=16的数组,被 0 填充 s2 := make([]int, 16)

新建len=16的数组,cap=32 s3 := make([]int, 16, 32)

copy copy(s2, s1) // s1复制到s2,s2的前面几位变成s1,

删除,s2截取 0 - 2位 + 4 - 结束 s2 = append(s2[:3], s2[4:]...)

去掉头,尾

1
2
s2 = s2[1:]
s2 = s2[:len(s2)-1]

map, [key]value

1
2
3
4
5
m := map[string]string {
"a": "b",
}
m1 := make(map[string]int) // == empty map
var m3 map[string]int // == nil

遍历

1
2
3
for key, value := range m {
fmt.Println(key, value)
}

获取,不存在则为空

1
2
name, isTrue := m['a'] // 第二个参数返回是否存在
if name, isTrue := m['a']; isTrue {} else {}

删除 delete(m, 'a')

个数 len(m)

Key 的类型

  • 除了 slice, map, func 的内建类型
  • struct 类型不包含上述字段,编译时检查此项

字符串,rune

1
2
3
4
5
6
s := "中文字符串"
len(s) == len([]byte(s)) // true
utf8.RuneCountInString(s) // 5
for index, zh := range []rune(s) {
fmt.Println(index, zh)
}

结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type treeNode struct {
value int
left, right *treeNode
}

root = treeNode{value: 3} //未赋值的为nil
root.right = &{5, nil, nil}
root.right.left = new(treeNode) // 建立空的treeNode, 返回地址

var pnode *treeNode // TODO:

// treeNode 结构的 slice
nodes := []treeNode {
{value: 3},
{},
{6, nil, &root}
}

工厂函数, 返回局部变量地址
不需要知道创建再堆还是栈

  • 如果没有取地址返回,则在栈上,退出即回收
  • 如果取地址返回,则在堆上,参与垃圾回收
    1
    2
    3
    func createNode(value int) *treeNode {
    return &treeNode{value: value}
    }

接收者调用,值传递,需要时传指针,使用时一样

1
2
3
4
5
6
7
8
func (node s) print () {}
s.print()

func print (node s) {}
print(s)

func (*node s) print () {}
s.print()

扩展类型
定义别名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type node struct {
value int
left, right *node
}

type myNode struct {
node *node
}

func (theNode *myNode) post() {
if theNode == nil || theNode.node == nil {
return
}
fmt.Print("post")
}

func main() {
var root node
myRoot := myNode{&root}
myRoot.post()
}

使用组合 (最常用)

1
2
3
func (mynode *node) post() {
// 改变mynode的值
}

内嵌
相当于 node 平铺到点了 myNode 层

1
2
3
4
5
6
7
type myNode struct {
*node
}

func (theNode *myNode) post() {
fmt.Print(theNode.left)
}

接口

任何类型
type Queue []interface{}

强制转换interface()
head.(int)

组合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type P interface {
Post()
}
type G interface {
Get()
}
type Pg interface {
P
G
}

func test (r Pg) {
r.Get()
r.Post()
}

panic

  • 停止当前函数执行
  • 一直向上返回,执行每一层的 defer
  • 如果没有遇见 recover ,程序退出
1
2
3
4
5
6
7
8
9
10
11
func tryRecover() {
defer func() {
r := recover()
if err, ok := r.(error); ok {
fmt.Println("error: ", err)
} else {
panic(r)
}
}()
panic(errors.New("is error"))
}

表格驱动测试

样例 resp_test.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package main

import (
util "server/util"
"testing"
)

func TestNewRespMsg(t *testing.T) {
var tests = &[]util.RespMsg{
{
-1, "错误", nil,
},
}
for _, tt := range *tests {
data := *util.NewRespMsg(tt.Code, tt.Msg, tt.Data)
data1 := util.RespMsg{ -1, "错误", nil, }
if (data != data1) {
t.Errorf("error")
}
}
}

// out: BenchmarkTestNewRespMsg-12 66670740 18.3 ns/op
// 运行 66670740 次,每次耗时 18.3ns
func BenchmarkTestNewRespMsg(b *testing.B) {
var tests = &util.RespMsg{-1, "错误", nil,}

b.Log(tests)
// 上面的生成不算时间,有 for 循环等情况使用
b.ResetTimer()

for i:= 0; i<b.N; i++ {
data := *util.NewRespMsg(tests.Code, tests.Msg, tests.Data)
data1 := util.RespMsg{ -1, "错误", nil, }
if (data != data1) {
b.Errorf("error")
}
}
}

代码覆盖率

1
2
go test -coverprofile=c.out
go tool cover -html=c.out

协程

  • 轻量级 “线程”
  • 非抢占式多任务处理,由协程主动交出控制权
  • 编译器/解释器/虚拟机层面的多任务
  • 多个协程可能在一个或多个线程上运行
  • 子程序是协程的一个特例
  • 映射到物理线程执行,4核就占用4个,自动调度

主动交出控制 runtime.Gosched()

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
var a [1000]int
for i:= 0; i<1000; i++ {
go func(i int) {
for {
a[i]++
runtime.Gosched()
}
}(i)
}

time.Sleep(time.Millisecond)
fmt.Println(a)
}

可能切换的点,只是参考,不能保证在其他地方不切换

  • I/O, select: print,读写文件等
  • channel
  • 等待锁
  • 函数调用(可能)
  • runtime.Gosched()

channel

不要通过共享内存来通信,通过通信来共享内存

https://www.jianshu.com/p/36e246c6153d https://www.jianshu.com/p/a3c9a05466e1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
func worker(id int, c chan int) {
// 两种方式检测发送完成

//for {
// n, ok := <-c
// if !ok {
// break
// }
// fmt.Printf("Worker %d received %d \n",
// id, n) // <-c 读取
//}

for n := range c {
fmt.Printf("Worker %d received %d \n",
id, n) // <-c 读取
}

}

// chan<- 这个chan用来发送数据
// <-chan 这个chan用来接收数据

func createWorker(id int) chan<- int {
c := make(chan int)
go func() {
for {
fmt.Printf("Worker %d received %c \n",
id, <-c) // <-c 读取
}
}()
return c
}

func chanDemo() {
var channels [10]chan<- int
for i := 0; i<10; i++ {
channels[i] = createWorker(i)
}

for i := 0; i<10; i++ {
channels[i] <- 'a' + i
}

for i := 0; i<10; i++ {
channels[i] <- 'A' + i
}
time.Sleep(time.Millisecond)
}

// 缓冲
func bufferedChannel() {
c := make(chan int, 3) // 缓冲区,超过缓冲则报错
go worker(0, c)
c <- 1
c <- 2
c <- 3
close(c) // 关闭发送,由发送方关闭
time.Sleep(time.Millisecond)
}

func main() {
chanDemo()
bufferedChannel()
}

等待多个 goroutine 结束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
type worker struct {
in chan int
done func()
}

func doWorker(id int, w worker) {
for n := range w.in {
fmt.Printf("Worker %d received %c \n",
id, n) // <-c 读取
w.done()
}
}

func createWorker(id int, wg *sync.WaitGroup) worker {
w := worker {
in: make(chan int),
done: func() {
wg.Done()
},
}
go doWorker(id, w)
return w
}

func chanDemo() {
// 等待多个任务做完
var wg sync.WaitGroup

var workers [10]worker
for i := 0; i<10; i++ {
workers[i] = createWorker(i, &wg)
}
wg.Add(20)

for i, worker := range workers {
worker.in <- 'a' + i
}

for i, worker := range workers {
worker.in <- 'A' + i
}

wg.Wait()
}

func main() {
chanDemo()
}

select 谁来的快接受谁的参数

1
2
3
4
5
6
7
8
var c1,c2 chan int
select {
case n:= <-c1:
xxx
case n:= <-c2:
xxx
default:
}

QA

声明的几种方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type node struct {
value int
left, right *node
}

var node1 node
node1 = node{}
fmt.Print(node1) // {0 <nil> <nil>} 声明,赋值
fmt.Print("\n")

var node2 *node
fmt.Print(node2) // <nil> 仅声明
fmt.Print("\n")

var node3 = new(node)
fmt.Print(node3) // &{0 <nil> <nil>} 声明,赋值,返回地址