收集帖 本帖主要收集关于Slice,Map,Struct遇到的坑,不定时更新,总结

Slice

Slice底层是一个数组,在进行Slice赋值操作时,底层的数组是不改变的,只是Slice的可见长度改变了,容量也是会不变的。Slice的数据结构是包含指针,长度,容量三个值的结构体。

package main

import "fmt"
func changeSlice(arr []int){
	fmt.Printf("changeSlice中传入的arr %p %v 长度:%d 容量%d\n",&arr,arr,len(arr),cap(arr))
	arr[0]++
	fmt.Printf("changeSlice中传入的arr change后 %p %v 长度:%d 容量%d\n",&arr,arr,len(arr),cap(arr))
	arr=append(arr,0)
	fmt.Printf("changeSlice中传入的arr add后 %p %v 长度:%d 容量%d\n",&arr,arr,len(arr),cap(arr))
}
func main(){
	arr1:=[]int{1,2,3}
	arr2:=arr1
	fmt.Printf("main的arr1 %p %v 长度:%d 容量%d \n",&arr1,arr1,len(arr1),cap(arr1))
	changeSlice(arr1)
	fmt.Printf("main的arr1 change后 %p %v 长度:%d 容量%d \n",&arr1,arr1,len(arr1),cap(arr1))

	fmt.Printf("main的arr2 %p %v 长度:%d 容量%d\n",&arr2,arr2,len(arr2),cap(arr2))
	changeSlice(arr2)
	fmt.Printf("main的arr2 change后 %p %v 长度:%d 容量%d\n",&arr2,arr2,len(arr2),cap(arr2))
}

输出

main的arr1 0xc000004078 [1 2 3] 长度:3 容量3 
changeSlice中传入的arr 0xc0000040c0 [1 2 3] 长度:3 容量3
changeSlice中传入的arr change后 0xc0000040c0 [2 2 3] 长度:3 容量3
changeSlice中传入的arr add后 0xc0000040c0 [2 2 3 0] 长度:4 容量6
main的arr1 change后 0xc000004078 [2 2 3] 长度:3 容量3 
main的arr2 0xc000004090 [2 2 3] 长度:3 容量3
changeSlice中传入的arr 0xc000004150 [2 2 3] 长度:3 容量3
changeSlice中传入的arr change后 0xc000004150 [3 2 3] 长度:3 容量3
changeSlice中传入的arr add后 0xc000004150 [3 2 3 0] 长度:4 容量6
main的arr2 change后 0xc000004090 [3 2 3] 长度:3 容量3

仔细观察上面代码,可以得出以下结论:

  1. Slice是值传递,因为无论是赋值还是函数传参,获取到的地址都是不一样的,%p表示获取指针

  2. 虽然是值传递,这个值是指Slice指向的结构体的值,他们仍然共享同一个底层数组,修改变量时,arr改变了,arr1和arr2都会跟着改变

  3. 注意append,当数组元素增加大于容量时,会发生扩容(扩容是2倍扩容,>1000是1.5倍扩容),这时候arr指向的数组和arr1和arr2指向的就不是同一块底层数组了。从函数退出后,arr1和arr2的元素值、长度和容量不同就可以看出来

  4. 建议如果要对传入的arr进行修改,请必须返回修改后的arr!

    示例如下:

    package main
    
    import "fmt"
    func changeSlice(arr []int)[]int{
    	fmt.Printf("changeSlice中传入的arr %p %v 长度:%d 容量%d\n",&arr,arr,len(arr),cap(arr))
    	arr[0]++
    	fmt.Printf("changeSlice中传入的arr change后 %p %v 长度:%d 容量%d\n",&arr,arr,len(arr),cap(arr))
    	arr=append(arr,0)
    	fmt.Printf("changeSlice中传入的arr add后 %p %v 长度:%d 容量%d\n",&arr,arr,len(arr),cap(arr))
    	return arr
    }
    func main(){
    	arr1:=[]int{1,2,3}
    	arr2:=arr1
    	fmt.Printf("main的arr1 %p %v 长度:%d 容量%d \n",&arr1,arr1,len(arr1),cap(arr1))
    	arr1=changeSlice(arr1)
    	fmt.Printf("main的arr1 change后 %p %v 长度:%d 容量%d \n",&arr1,arr1,len(arr1),cap(arr1))
    
    	fmt.Printf("main的arr2 %p %v 长度:%d 容量%d\n",&arr2,arr2,len(arr2),cap(arr2))
    	arr2=changeSlice(arr2)
    	fmt.Printf("main的arr2 change后 %p %v 长度:%d 容量%d\n",&arr2,arr2,len(arr2),cap(arr2))
    }
    

    输出

    main的arr1 0xc000004078 [1 2 3] 长度:3 容量3 
    changeSlice中传入的arr 0xc0000040c0 [1 2 3] 长度:3 容量3
    changeSlice中传入的arr change后 0xc0000040c0 [2 2 3] 长度:3 容量3
    changeSlice中传入的arr add后 0xc0000040c0 [2 2 3 0] 长度:4 容量6
    main的arr1 change后 0xc000004078 [2 2 3 0] 长度:4 容量6 
    main的arr2 0xc000004090 [2 2 3] 长度:3 容量3
    changeSlice中传入的arr 0xc000004150 [2 2 3] 长度:3 容量3
    changeSlice中传入的arr change后 0xc000004150 [3 2 3] 长度:3 容量3
    changeSlice中传入的arr add后 0xc000004150 [3 2 3 0] 长度:4 容量6
    main的arr2 change后 0xc000004090 [3 2 3 0] 长度:4 容量6
    

    for range

    package main
    
    import "fmt"
    func main(){
    	arr:=[]int{1,2,3}
    	for ind,val:=range arr{
    		fmt.Printf("val %p %v\n",&val,val)
    		fmt.Printf("arr[ind] %p %v\n",&arr[ind],arr[ind])
    		val++
    		fmt.Printf("add后 val %p %v\n",&val,val)
    		fmt.Printf("add后 arr[ind] %p %v\n",&arr[ind],arr[ind])
    	}
    }
    
    

    输出

    val 0xc00000a098 1
    arr[ind] 0xc000012150 1
    add后 val 0xc00000a098 2
    add后 arr[ind] 0xc000012150 1
    val 0xc00000a098 2
    arr[ind] 0xc000012158 2
    add后 val 0xc00000a098 3
    add后 arr[ind] 0xc000012158 2
    val 0xc00000a098 3
    arr[ind] 0xc000012160 3
    add后 val 0xc00000a098 4
    add后 arr[ind] 0xc000012160 3
    

    通过代码可以得出如下结论:

    1. for range得到的val的地址是同一个,只是每次更新值
    2. 修改val对Slice不会造成任何影响!

    Map

    • Map的Key必须是可比较的类型,Slice,channel,map不可比较,另外结构体是否可比较是根据结构体中的元素判断的。
    package main
    
    import "fmt"
    
    func main(){
    	a:=make(chan int,1)
    	b:=make(chan int,1)
    	a<-1
    	b<-1
    	if a==b{
    		fmt.Println("可比较")
    	}
    }
    

    chan虽然不报错,但是上述代码并不能得出正确的比较结果,而Slice和Map一旦作比较就报错了。

    • Map使用时,必须要初始化

    也就是

    var a map[int]int
    a:=make(map[]int,0)
    

    第一个是声明,第二个是初始化

    • Map的Val是结构体时,一定要使用结构体指针传值,否则无法修改结构体元素
    package main
    
    import "fmt"
    
    type person struct {
    	name string
    	age int
    }
    func main(){
    	a:=make(map[string]person)
    	a["007"]=person{"小花",15}
    	a["008"]=person{"小刘",18}
    	fmt.Println(a)
    	a["008"].name="小白"
    }
    

    最后一行 a["008"].name="小白"是错的

    package main
    
    import "fmt"
    
    type person struct {
    	name string
    	age int
    }
    func main(){
    	a:=make(map[string]*person)
    	a["007"]=&person{"小花",15}
    	a["008"]=&person{"小刘",18}
    	fmt.Println(a)
    	a["008"].name="小白"
    	fmt.Println(a["008"])
    }
    

    所以传结构体时,使用指针传递最好

    Struct

    package main
    
    import "fmt"
    
    type person struct {
    	name string
    	age int
    }
    func changeStruct(p person){
    	fmt.Println("changeStruct person %p %v",&p,p)
    	p.age=17
    	fmt.Println("changeStruct person change后 %p %v",&p,p)
    }
    func main(){
    	person1:=person{"小刘",18}
    	fmt.Println("main person1 %p %v",&person1,person1)
    	changeStruct(person1)
    	fmt.Println("main person1 change后 %p %v",&person1,person1)
    }
    
    

    输出

    main person1 %p %v &{小刘 18} {小刘 18}
    changeStruct person %p %v &{小刘 18} {小刘 18}
    changeStruct person change后 %p %v &{小刘 17} {小刘 17}
    main person1 change后 %p %v &{小刘 18} {小刘 18}
    

    从以上可以得出:

    1. Struct是值传递,传入的只是Struct值,所以会经过值拷贝,这样也消耗很大
    2. 不如传入指针
    package main
    
    import "fmt"
    
    type person struct {
    	name string
    	age int
    }
    func changeStruct(p *person){
    	fmt.Println("changeStruct person %p %v",&p,p)
    	p.age=17
    	fmt.Println("changeStruct person change后 %p %v",&p,p)
    }
    func main(){
    	person1:=person{"小刘",18}
    	fmt.Println("main person1 %p %v",&person1,person1)
    	changeStruct(&person1)
    	fmt.Println("main person1 change后 %p %v",&person1,person1)
    }
    
    

    输出

    main person1 %p %v &{小刘 18} {小刘 18}
    changeStruct person %p %v 0xc000006030 &{小刘 18}
    changeStruct person change后 %p %v 0xc000006030 &{小刘 17}
    main person1 change后 %p %v &{小刘 17} {小刘 17}
    

    注:结构体指针和结构体调用成员的方法是一样的,都是p.name,当然对于结构体指针也支持(*p).name,对于结构体也支持&p.name。