go语言基础学习笔记

记录在go语言学习过程中的一些易错点,使用技巧,以及值得思考的地方

“一个良好的程序永远不应该发生panic异常”

基本数据类型注意点:

  • “尽管Go语言提供了无符号数和运算,即使数值本身不可能出现负数我们还是倾向于使用有符号的int类型”,因此,内置的len()返回的就是一个int型
1
2
3
4
5
medals := []string{"gold", "silver", "bronze"}
for i := len(medals) - 1; i >= 0; i-- {
fmt.Println(medals[i]) // "bronze", "silver", "gold"
}
// 如果len()返回的i为uint的话,i-- 永远不可能为负数
  • fmt使用技巧
1
2
3
4
5
6
7
o := 0666
fmt.Printf("%d %[1]o %#[1]o\n", o) // "438 666 0666"
x := int64(0xdeadbeef)
fmt.Printf("%d %[1]x %#[1]x %#[1]X\n", x)
// Output:
// 3735928559 deadbeef 0xdeadbeef 0XDEADBEEF
// #副词告诉 printf保留前缀,[1]副词告诉printf继续使用第一个操作数
1
2
3
4
5
6
7
ascii := 'a'
unicode := '国'
newline := '\n'
fmt.Printf("%d %[1]c %[1]q\n", ascii) // "97 a 'a'"
fmt.Printf("%d %[1]c %[1]q\n", unicode) // "22269 国 '国'"
fmt.Printf("%d %[1]q\n", newline) // "10 '\n'"
// 字符使用%c参数打印,或者是用%q参数打印带单引号的字符
  • Go语言中所有的参数传递都是值传递,所谓的”引用传递”也是地址值的传递,例如slice,map
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
//slice底层类似与这样一个结构体,维护着一个数组指针(ptr),切片的长度(len),数组的长度(cap)
type TSlice struct {
ptr *[]T
len, cap int
}

//map底层由hmap实现
type hmap struct {
count int // 元素的个数
flags uint8 // 标记读写状态,主要是做竞态检测,避免并发读写
B uint8 // 可以容纳 2 ^ N 个bucket
noverflow uint16 // 溢出的bucket个数
hash0 uint32 // hash 因子

buckets unsafe.Pointer // 指向数组buckets的指针
oldbuckets unsafe.Pointer // growing 时保存原buckets的指针
nevacuate uintptr // growing 时已迁移的个数

extra *mapextra
}

type mapextra struct {
overflow *[]*bmap
oldoverflow *[]*bmap

nextOverflow *bmap
}
  • slice需要注意的是,在扩容之后,自身所引用的底层数组可能会发生替换,所以通常是将append返回的结果直接赋值给输入的slice变量:
1
runes = append(runes, r)
  • 关于map底层hmap解释,这篇博客介绍的比较贴切map底层结构
    • key的哈希值分为高八位和低八位,低位用于寻找当前key属于hmap中的哪个bucket,而高位用于寻找bucket中的哪个key。
    • 加载因子越小,空间利用率越低,越”稀疏”,加载因子越大,空间利用率越大,越”拥挤”
    • map扩容之后,并不会立即把新数据做迁移,而是当访问原来旧bucket的数据的时候,才把旧数据做迁移
1
2
3
4
5
6
7
8
9
10
11
// map 零值是nil 但是要往里面插值必须要创建map(不同于slice,即使声明一个空slice也能往里append值)
var ages map[string]int
fmt.Println(ages == nil) // "true"
fmt.Println(len(ages) == 0) // "true"
// 创建map 这三种方法都可以
ages := make(map[string]int)
ages := map[string]int{
"alice": 31,
"charlie": 34,
}
ages := map[string]int{}
  • 函数的声明格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 下面几种全部等同与"func(int, int) int",
func add(x int, y int) int {return x + y}
func sub(x, y int) (z int) { z = x - y; return}
func first(x int, _ int) int { return x }
func zero(int, int) int { return 0 }

// 如果声明的变量名的话就等同于在函数体中进行了该变量的声明,return后面省略的话就相当返回所有变量
func CountWordsAndImages(url string) (words, images int, err error) {
resp, err := http.Get(url)
if err != nil {
return
}
doc, err := html.Parse(resp.Body)
resp.Body.Close()
if err != nil {
err = fmt.Errorf("parsing HTML: %s", err)
return
}
words, images = countWordsAndImages(doc)
return
}
  • 内存逃逸
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var global *int

// f函数退出后x变量依旧可以通过包级变量global访问到,导致x无法被释放,此时x就逃逸了
// 所以x变量的内存就会在堆中进行分配
func f() {
var x int
x = 1
global = &x
}

// y不会逃逸,因此可以在栈中分配
func g() {
y := new(int)
*y = 1
}
  • T类型的值不拥有所有*T指针的方法,但是只要给T一个变量,就能通过变量调*T的方法 因为会隐式的去获取变量的地址
    反之,T类型所拥有的方法,*T当然也都拥有
1
2
3
4
5
6
7
8
type IntSet struct { /* ... */ }
func (*IntSet) String() string
var _ = IntSet{}.String() // compile error: 因为匿名结构体无法获取地址
var s IntSet
var _ = s.String() // OK: 因为会隐式的获取变量s的地址值

var _ fmt.Stringer = &s // OK
var _ fmt.Stringer = s // compile error: IntSet本身是没有实现string方法的
  • 适配器:让对象的方法去实现接口,完成适配
1
2
3
4
5
6
7
package http

type HandlerFunc func(w ResponseWriter, r *Request)

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
  • 为避免“循环变量快照”的问题,匿名函数循环调用时,需要显示的传入参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func main() {
worklist := make(chan []string)

// Start with the command-line arguments.
go func() { worklist <- os.Args[1:] }()

// Crawl the web concurrently.
seen := make(map[string]bool)
for list := range worklist {
for _, link := range list {
if !seen[link] {
seen[link] = true
go func(link string) {
worklist <- crawl(link)
}(link) // 显式传参
}
}
}
}
  • goroutines泄漏,goroutines的channel因为没有人接收而被永远阻塞,称为goroutines泄漏

  • 手动实现控制并发量

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
func main() {
worklist := make(chan []string) // lists of URLs, may have duplicates
unseenLinks := make(chan string) // de-duplicated URLs

// Add command-line arguments to worklist.
go func() { worklist <- os.Args[1:] }()

// Create 20 crawler goroutines to fetch each unseen link.
for i := 0; i < 20; i++ {
go func() {
for link := range unseenLinks {
foundLinks := crawl(link)
go func() { worklist <- foundLinks }()
}
}()
}

// The main goroutine de-duplicates worklist items
// and sends the unseen ones to the crawlers.
seen := make(map[string]bool)
for list := range worklist {
for _, link := range list {
if !seen[link] {
seen[link] = true
unseenLinks <- link
}
}
}
}