本文介绍: 1.GO中只有值传递没有引用传递2.如果需要函数内部修改影响函数外部,那就传指针3.map/chan本身就是指针,是引用类型的,直接传其本身即可4.slice传递过程中,本质上传递的是其本身的内存地址 array,也即是指针,直接slice本身即可5.sliceappend操作需要修改结构体的len或者cap,类似于struct。如果需要传递函数外部需要传指针,或者通过函数返回值返回结果

官方说法Go中只有值传递没有引用传递

而Go语言中的一些让你觉得它是引用传递原因,是因为Go语言类型引用类型,但是它们都是值传递

类型 有intfloatboolstringarraysturct

引用类型slicemapchannelinterfacefunc

值类型:内存变量存储的是具体的值。 比如: var num intnum存放的是具体的int值,但是变量在内存中的地址可以通过 &num获取

引用类型:变量直接存放的就是一个地址值,这个地址指向空间存的才是值。

代码测试

func main() {
	slice := []int{1, 2, 3}
	arr := [2]int{1, 2}
	m := make(map[string]string)
	a := 13
	var i *int = &a

	ch := make(chan string)
	fmt.Printf("[main array] %pn", &arr)
	fmt.Printf("[main pointer] %pn", &i)
	fmt.Printf("[main map] %pn", &m)
	fmt.Printf("[main slice] %pn", &slice)
	fmt.Printf("[main chan] %pn", &ch)
	fmt.Printf("[main slice 第一个元素地址: ] %pn", &slice[0])

	fmt.Println()
	get(arr, slice, m, i, ch)
}

func get(arr [2]int, s []int, m map[string]string, i *int, ch chan string) {
	fmt.Printf("[main array] %pn", &arr)
	fmt.Printf("[get pointer] %pn", &i)
	fmt.Printf("[get map] %pn", &m)
	fmt.Printf("[get slice] %pn", &s)
	fmt.Printf("[get chan] %pn", &ch)
	fmt.Printf("[get slice 第一个元素地址: ] %pn", &s[0])
}

测试结果

可以发现数组、slicemap、chan、指针在传递过程中,地址都发生了变化。这说明传递的是一份拷贝这里需要特意强调切片第一个元素地址前后没有发生改变

但是我们日常go代码发现,在函数里修改slicemap函数外的值也会改变,这是为什么呢?

接下来就逐个分析下。源码版本是1.21.3,这里就只是查看下源码创建slice,map时的返回值而已,不会讲解过多的源码内容

引用类型分析

slice

 slice一个长度可变的连续数据序列,其是个结构体,其中包含字段包括:指向内存空间地址起点的指针 array一个表示存储数据长度len分配空间长度cap

type slice struct {
	array unsafe.Pointer
	len   int
	cap   int
}

func makeslice(et *_type, len, cap int) unsafe.Pointer {    
    .....................
	return mallocgc(mem, et, true)
}

创建slice时候返回的是数组地址

那么 slice 在传递过程中,本质上传递的是 slice实例中的内存地址 array。

因为slice是引用类型,指向的是同一个数组。也通过前面测试代码结果,可以看到函数内外,slice本身的地址&slice变了,但是两个指针指向底层数据,也就是&slice[0]数组首元素的地址是不变的

所以在函数内部的修改可以影响函数外部这个很容易理解

那再来看看对slice使用append代码如下

func main() {
	arr := make([]int, 0) //容量cap不够的情况
	// arr := make([]int, 0, 5) //容量cap足够的情况
	arr = append(slice, 2, 4)
	fmt.Printf("main1 slice地址:%p, 底层数组地址:%p ,len:%d, cap:%dn", &arr, &arr[0], len(arr), cap(arr))
	appendSlice(arr)
	fmt.Printf("main2 slice地址:%p, 底层数组地址:%p ,len:%d, cap:%dn", &arr, &arr[0], len(arr), cap(arr))
}

func appendSlice(arr []int) {
	fmt.Printf("传递参数后,append前 slice地址:%p, 底层数组地址:%p ,len:%d, cap:%dn", &arr, &arr[0], len(arr), cap(arr))
	arr = append(arr, 1)
	fmt.Println()
	fmt.Printf("append后 slice地址:%p, 底层数组地址:%p ,len:%d, cap:%dn", &arr, &arr[0], len(arr), cap(arr))
}

主要就有两种情况:

切片make不够容量

即是append时需要扩容

1.首先,外部传入一个slice,引用类型。

2.也还是值传递(slice地址发生了改变),但是两个arr指向的底层数组首元素&arr[0]没有改变,也就是array unsafe.Pointer不变。

3.在内部调用append,因为cap容量不够,要扩容重新在新的地址空间分配底层数组,所以数组首元素的地址改变了

4.回到函数外部外部的slice指向的底层数组为原数组,内部的修改不影响原数组

切片make够容量

结果一样是[2 4],虽然函数内部append结果同样不影响外部的输出,但是原理却不一样。

不同之处:

 

 不管cap容量够不够,其都没有改变外部slice的len和cap,所以最终看到的slice的len和cap都是没有改变的。

想要改变长度的的话,要传slice的指针

传指针进去,拷贝就是这个指针。指针指向对象,也就是slice本身,是不变的。

func appendSlicePointer(arr *[]int) {
	*arr = append(*arr, 5)
}

map

map使用的时候都是通过make来创建例如

mymap := make(map[int]string)

 通过查看源码我们可以看到,实际上make底层调用的是makemap函数。而在src/runtime/hashmap.go源代码305行发现makemap函数返回的是一个hmap类型的指针*hmap。也就是说map===*hmap。hmap是个结构体。

// makemap implements Go map creation for make(map[k]v, hint).
func makemap(t *maptype, hint int, h *hmap) *hmap {
	.......................
	return h
}

而对于指针类型的参数来说,只是复制了指针本身,指针所指向的地址还是之前的地址。所以对map的修改是可以影响到函数外部的。

chan

管道的创建

channel := make(chan int, 5)

通过查看src/runtime/chan.go源代码72行发现makechan函数返回的是一个hchan类型的指针*hchan。hchan也是个结构体。所以chan类型和map类型是本质是一样的。

func makechan(t *chantype, size int) *hchan {
	.................
	var c *hchan
	.....................
	return c
}

总结

1.Go中只有值传递,没有引用传递

2.如果需要函数内部的修改能影响到函数外部,那就传指针

3.map/chan本身是指针,是引用类型,直接传其本身即可

4.slice 在传递过程中,本质上传递的是其内存地址 array,也即是指针,直接传slice本身即可

5.slice的append操作需要修改结构体的len或者cap,类似于struct。若要影响到函数外部,需要传指针,或通过函数返回值返回结果

原文地址:https://blog.csdn.net/m0_57408211/article/details/134695649

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_37240.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注