golang随笔之slice+append的陷阱

前言

有关slice底层点击下面博文阅读
Golang底层原理剖析之slice类型与扩容机制

问题初探

package main

import "fmt"

func main() {
    
    
	s1 := []int{
    
    1, 2}
	s2 := s1
	s2 = append(s2, 3)
	Test1(s1)
	Test1(s2)
	fmt.Println(s1, s2)
}

func Test1(s []int) {
    
    
	s = append(s, 0)
	for i := range s {
    
    
		s[i]++
	}
}

A:[2,3] [2,3,4]
B:[1,2] [1,2,3]
C:[1,2] [2,3,4]
D:[2,3,1] [2,3,4,1]

思考

今天看书看到这一题,一看,这肯定选D嘛,一翻答案,选C,离谱!!

我知道s2 = append(s2, 3)这一句话会因为容量不够而发送扩容导致s1和s2的底层不一样,那传入Test1的时候,不是说好slice传递是引用吗,那么s = append(s, 0)扩容换底层array后,s1和Test1的s,他们是引用关系,那他们还是相同的一个slice吗?结果是,如果发生扩容了,那么就相当于值传递,那这样解释的话,s1是能说得通,那s2呢,s2在Test1中并没有发生扩容,为什么s2不是2,3,4,1?来看下面这个例子

package main

import "fmt"

func main() {
    
    
	s3:=make([]int,2,10)
	fmt.Println(s3)
	Test2(s3)
	fmt.Println(s3)

	s4:=s3[0:10]
	fmt.Println(s4)
}
func Test2(s []int){
    
    
	s=append(s,6)
	s=append(s,6)
	s=append(s,6)
	fmt.Println(s)
}
[0 0]
[0 0 6 6 6]
[0 0]
[0 0 6 6 6 0 0 0 0 0]

我们发现在没有发生扩容的情况下,s3的底层数组,和函数内的s一样,那么为什么s3没有变成006666呢?

解析

将一个切片作为函数参数传递给函数时,其实采用的是值传递,因此传递给函数的参数其实是切片结构体的值拷贝

type SliceHeader struct {
    
    
    Data uintptr
    Len int
    Cap int
}

我们以值传递的方式传递切片结构体的时候,同时也是传递了Len和Cap的值拷贝,因为这两个成员并不是指针,因此,当函数返回时,原切片结构体的Len和Cap并没有改变。

那如果想在函数中用append怎么办,这个时候就要传slice的指针了,如下面代码

package main

import "fmt"

func main() {
    
    
	arr := []int{
    
    1, 2, 3, 4}
	fmt.Println(arr)

	Test1(&arr)

	fmt.Println(arr)
}

func Test1(s *[]int) {
    
    
	*s = append(*s, 5)
	fmt.Println(*s)
}
[1 2 3 4]
[1 2 3 4 5]
[1 2 3 4 5]

Go中没有引用传递

In a function call, the function value and arguments are evaluated in the usual order. After they are evaluated, the parameters of the call are passed by value to the function and the called function begins execution.
文档地址:https://golang.org/ref/spec#Calls

不能单纯因为函数内部的修改可以反馈到外面就认为是传递的引用。

我说怎么有的书上写slice map func的引用转递,有的地方说go都是值传递,看来还是没有说的透彻。

记住,go中全部都是值传递。

Guess you like

Origin blog.csdn.net/qq_42956653/article/details/121500197