golang开发中经常遇到的坑

127 阅读3分钟

我正在参加「掘金·启航计划」

1、切片追加

1.1、append会改变切片的地址

append的本质是向切片找中追加数据,而随着切片中元素逐渐增加,当切片底层的数组将满时,切片会发生扩容,扩容会导致产生一个新的切片(拥有容量更大的底层数组),所以可能会返回一个新的切片,这也是append函数返回值为切片的原因,实际使用中应该总是接收该返回值。

var s []bool
fmt.Printf("s:%p\n", s) //s:0x0
s = append(s, true)
fmt.Printf("s:%p\n", s) //s:0xc00001a0a8

由于初始切片长度为0,后面append追加时就会产生一个新的切片并迅速抛弃(被gc回收)。

1.2、append可以追加nil值

var s []error
fmt.Printf("s length :%d\n", len(s)) //s length :0
s = append(s, nil)
fmt.Printf("s length :%d\n", len(s)) //s length :1

nil 是一个预定义的值,即空值,可以向切片中追加并不会报错,后续可能会导致根据切片长度判断时有歧义。

2、循环变量绑定

2.1、现象

task := []int{1, 2, 3, 4, 5}
for _, v := range task {
    go func() {
        fmt.Print(v)
    }()
}

正常应该输出12345,实际输出55555,why?

2.2、原因

循环变量是易变得

循环变量只是一个普通的变量,语句for index, value := range xxx 中,每次循环index和value都会被重新赋值(并非生成新的变量),如果循环体中会启动协程(并且协程会使用循环变量),这个时候就需要注意了,因为很可能循环结束后协程才开始执行,所以协程使用的循环变量有可能被改写。是否改写取决于引用循环变量的方式。

2.3、解决

循环变量需要绑定,在创建协程时,循环变量作为函数参数传递给了协程。参数传递的过程实际上也生成了新的变量,也即间接的完成了绑定。

task := []int{1, 2, 3, 4, 5}
for _, v := range task {
    go func(vv int) {
        fmt.Print(vv)
    }(v)
}

2.4、总结

  • 如果循环体没有并发出现,则引用循环变量一般不会出现问题。
  • 如果循环体有并发,则根据引用循环变量的位置不同而有区别
    • 通过参数完成绑定,则一般没有问题
    • 函数体中引用循环变量,则需要显式的绑定

3、strings.Split字符串分隔

strings.Split函数用于通过指定的分隔符切割字符串,并返回切割后的字符串切片。

strings.Split(s, sep) //s待分割字符串,sep分隔符

示例:

func split(){
	//示例1
	s := "abc"
	newS := strings.Split(s, "")
	fmt.Printf("s split length:%d, type:%T,value:%#v\n", len(newS), newS, newS)
	//示例2
	s = ""
	newS = strings.Split(s, ",")
	fmt.Printf("s split length:%d, type:%T,value:%#v\n", len(newS), newS, newS)
}

输出:

s split length:3, type:[]string,value:[]string{"a", "b", "c"}
s split length:1, type:[]string,value:[]string{""}

如果按照正常思维,示例2中length应该是0才对,但是结果是1,来看下split的底层实现:

func genSplit(s, sep string, sepSave, n int) []string {
	if n == 0 {
		return nil
	}
	if sep == "" {
		return explode(s, n)
	}
	if n < 0 {
		n = Count(s, sep) + 1
	}

	a := make([]string, n)
	n--
	i := 0
	for i < n {
		m := Index(s, sep)
		if m < 0 {
			break
		}
		a[i] = s[:m+sepSave]
		s = s[m+len(sep):]
		i++
	}
	a[i] = s
	return a[:i+1]
}

可以看到最后的返回的slice最少也有一个值。