首页 > 其他分享 >从一道题来看看golang中的slice作为参数时的现象

从一道题来看看golang中的slice作为参数时的现象

时间:2023-11-15 23:55:39浏览次数:40  
标签:slice -- fmt cap value 题来 golang add

1、题目

最近看群友在群里问一道关于golang中slice的题,题目如下:

package main

import "fmt"

func main() {
	k := []int{1, 2, 3, 4}
	k = append(k, 5, 6)
	fmt.Printf("k --> value: %v, add: %p, cap: %d\n", k, k, cap(k))

	ap(k)
	fmt.Printf("k --> value: %v, add: %p, cap: %d\n", k, k, cap(k))

}

func ap(k []int) {
	k = append(k, 7, 8)
	fmt.Printf("k --> value: %v, add: %p, cap: %d\n", k, k, cap(k))
}

执行结果:

k --> value: [1 2 3 4 5 6], add: 0xc00001e180, cap: 8
k --> value: [1 2 3 4 5 6 7 8], add: 0xc00001e180, cap: 8
k --> value: [1 2 3 4 5 6], add: 0xc00001e180, cap: 8

乍一看,还挺奇怪的,变量k的地址都是一样的,为啥会执行ap函数时,打印出来的东西不一样呢?

其实对于初次接触 golang 的 gopher 而言,这个问题确实有点奇怪,书上不是说slice是引用类型,golang 中的函数传参是值拷贝,那么在函数传递 slice 时,传递也是地址,为啥对地址指向的内容做了修改后,并没有影响到其他指向同一地址的变量呢?

想要理解这里面的原理,需要了解下面的基础知识,接下来我们先看看前置知识,学习完这些前置的理论后,相信大家都已经有了自己的理解与答案。

PS: 要是有理解不对的地方,请不吝赐教哈,谢谢。

2、前置理论

2.1、切片的本质

下面的介绍基于 go 1.18,golang中关于 slice 封装的源码位于 runtime/slice.go 中。

切片的本质就是对底层数组的封装,切片实际上是一个 struct ,包含了三个字段:底层数组的指针、切片的长度(len)和切片的容量(cap)

type slice struct {
	array unsafe.Pointer  // 数组指针
	len   int  // 长度
	cap   int  // 容量
}

slice 作为参数传递的时候,是将slice struct中的各个字段逐一复制到新的变量中去的,其中 array 字段是底层数组的首地址

我们一起来看看题目中变量K的初始化

k := []int{1, 2, 3, 4}
k = append(k, 5, 6)

变量 K 示意图:

执行 ap 函数后

func ap(k []int) {
	k = append(k, 7, 8)  // 无需扩容,容量足够
	fmt.Printf("k --> value: %v, add: %p, cap: %d\n", k, k, cap(k))
}

函数内变量k的示意图:

2.2、格式化字符串%p打印slice时显示的是什么

这个问题呢,推荐大家看下这篇文章,比我说得清楚写。

[golang slice切片到底是指针吗?为什么%p输出的切片是地址?](https://segmentfault.com/a/1190000042430248)

这里我们写一个demo验证下

func main() {
	k := []int{1, 2, 3, 4}
	fmt.Printf("k    --> add: %p\n", k)
	fmt.Printf("k[0] --> add: %p\n", &k[0])
}


执行结果:
k    --> add: 0xc000136000
k[0] --> add: 0xc000136000

3、再看题目

了解了上面的知识后,再看开头的题目就很简单了,变量k 传给 ap 函数函数时,虽然函数 ap 的形参也叫 k,但是已经不是同一个变量了,只是两个 slice 指向的底层数组是同一个而已,所以使用 %p 打印时,显示的地址是一样的。

package main

import "fmt"

func main() {
	k := []int{1, 2, 3, 4}
	k = append(k, 5, 6)
	fmt.Printf("k --> value: %v, add: %p, len: %d, cap: %d\n", k, k, len(k), cap(k))
	fmt.Printf("k --> add: %p\n", &k)

	ap(k)
	fmt.Printf("k --> value: %v, add: %p, len: %d, cap: %d\n", k, k, len(k), cap(k))
	fmt.Printf("k --> add: %p\n", &k)
}

func ap(k []int) {
	k = append(k, 7, 8)
	fmt.Printf("k --> value: %v, add: %p, len: %d, cap: %d\n", k, k, len(k), cap(k))
	fmt.Printf("k --> add: %p\n", &k)
}

执行结果:

k --> value: [1 2 3 4 5 6], add: 0xc00001e180, len: 6, cap: 8
k --> add: 0xc00000c030
k --> value: [1 2 3 4 5 6 7 8], add: 0xc00001e180, len: 8, cap: 8
k --> add: 0xc00000c078
k --> value: [1 2 3 4 5 6], add: 0xc00001e180, len: 6, cap: 8
k --> add: 0xc00000c030

想要 ap 函数执行后的结果,能够改变外面的变量k也很简单,将函数中的形参k返回出去就可以了。类似这样:

func ap(k []int) []int {
	k = append(k, 7, 8)
	return k
}

k = ap(k)

是不是有点像 append 内置函数

标签:slice,--,fmt,cap,value,题来,golang,add
From: https://www.cnblogs.com/huageyiyangdewo/p/17835167.html

相关文章

  • windows系统使用终端和goland编辑器打包golang程序方法
    上一篇文章说了,windows系统,如何使用goland编辑器打包exe和linux程序,这篇文章再补充一下,使用终端和goland编辑器打包的对比情况。这里的终端可以是,cmd、WindowsPowerShell、MINGw64这里,我使用goland编辑器里面的Terminal,也就是WindowsPowerShelll来操作1、goland编辑器打......
  • 【golang】Golang 哈希码 hashcode 输入一个字符串,得到一个唯一标识码
    如何输入一个字符串,得到一个唯一的hashcode?例子如下:packagemainimport("fmt""hash/crc32")//Stringhashesastringtoauniquehashcode.////crc32returnsauint32,butforouruseweneed//andnonnegativeinteger.Herewec......
  • golang slice
    slice是golang的切片,动态数组底层结构//runtime/slice.gotypeslicestruct{ arrayunsafe.Pointer//底层数组 lenint capint}golang的slice底层是一个数组,也就是可以通过&s[0]来获取底层数组的地址,len记录的当前,cap记录的是底层数组的大小也就是当前......
  • golang版本升级(Mac GoLand版)
    本地的go版本是1.18,想升级到1.21,运用GoLand做快速升级第一步  进入GoLand-》Preferences第二步  进入Go-〉GOROOT点击“+”号,选择“Download”第三步选择需要更新的版本 点击“OK”后“Apply”,最新版的go就会下载到你的GOPATH里面了 升级完新版本后,deb......
  • Golang workSpace工作区
    创建myworkspace主项目mkdirmyworkspacecdmyworkspace创建common子项目mkdircommon&&cdcommongomodinitcommonvimcommon.gomyworkspace/common/common.gopackagecommonimport"fmt"funcDosomething(){fmt.Println("dosomet......
  • JavaScript slice 方法用法与实例解析
    在JavaScript中,slice()是一个常用的数组方法,用于从现有数组中提取一部分元素,然后返回一个新的数组。它是一个非常有用的工具,可以帮助你在不改变原始数组的情况下操作数组的子集。本文将介绍slice()的基本概念、使用方法、实践案例和互动练习,以帮助你更好地理解和掌握这一功能......
  • Golang反射
    反射的结构体//reflect/type.gotypeTypeinterface{//该类型内存分配大小(内存对齐单位子节) Align()int //该类型作为结构体字段时内存分配大小(内存对齐单位子节) FieldAlign()int//根据indexin[0,NumMethod())获取方法按lexicographic排序 Method(i......
  • 关于Golang三个内存区域的形象比喻
    当我们使用Go语言编写程序时,可以将这三个内存区域类比为一个大的游乐场。Arena区就像是整个游乐场的主要区域,它是用于分配和管理大对象的地方。在这个区域,我们可以找到各种大型游乐设施,比如大型滑梯、蹦床和攀爬架等。这些设施需要更多的空间和资源来支持,因此它们被分配在Arena区域......
  • Golang内存泄漏的代码示例
    以下是几个可能导致内存泄漏的Go代码示例:资源未关闭:funcreadFile(){ file,err:=os.Open("filename.txt") iferr!=nil{ //错误处理 return } //使用file进行读取操作 //...}在上述代码中,readFile函数打开了一个文件,但没有在使用完后调用file.Close()来关闭文......
  • Golang select语句代码示例
    在Go语言中,select语句用于多路选择(multiplexing),允许在多个通信操作中选择可用的操作进行执行。select语句的语法如下:select{case<-channel1://当channel1有数据可读时执行的代码casedata:=<-channel2://当channel2有数据可读时执行的代码,同时将读取的数据存储在......