2023-12-05 18:42:18 Go Go小卡片
所有的函数参数传参都会先进行参数拷贝,拿下面这个案例,也就是在传参时,a
会先拷贝一份副本,然后再将副本传入modifyInt
函数
func modifyInt(a int) {
// 此时传入的参数a是独立copy的a,拥有变量a的值,但是所属地址不相同
// 进而无法通过修改此处a的值影响传入的参数a
a++
}
func main() {
a := 0 // 先拷贝一份a
modifyInt(a) // 然后再传递拷贝的副本a给函数
}
这意味什么呢?
这就意味着不仅原本的变量a
在计算机中占有一块内存空间,拷贝后传入modifyInt
的参数a
会再在内存空间中重新开辟一块内存,因为两者地址不同,所以函数里的参数a
发生改变,自然就不会影响到函数外的变量a
思考:如果我想在函数里面修改传过来的
参数a
,进而修改函数外的变量a
呢?
很简单,只需要将变量a
的指针传入函数,那么我们对参数的操作都是基于参数地址进行修改了
说人话就是,通过传进函数中的变量a
的地址,对变量a
原有内容修改(说了,但好像又没说-_-||)
func modifyInt(a *int) {
(*a)++ // 此时就是在a的地址上,通过解引用(或则说解析a的地址),就能精准定位对a的值进行修改
}
func main() {
a := 0
modifyInt(&a) // 将a的地址传入函数,通过地址精准定位进而修改a的值
}
很显然,如果我们企图通过函数,对原本的变量a
做出一定修改,那我们在进行函数传参时就得考虑传入地址了。当然接受返回的新结果也未曾不可
省流(实则是后面原理讲的感觉挺烂的做出的一个总结):对于切片,我们如果需要对原切片进行扩容删除的会对切片长度有影响的操作时,就需要我们传入地址;当我们只是希望对切片中某个元素进行修改,因为本身传入切片时,会传入切片的底层数组首地址,所以可以直接对切片元素进行访问修改
通过前面的引言案例,我们知道要修改传进来的原参数,需要传入这个参数的指针,然后通过这个指针定位数据存储的地址,进而通过解引用修改这块地址所存放的值
那么切片是否也能通过传入切片的地址(指针),进而修改呢?
我们先来试试不传指针可不可以
func modifyInt(s []int) {
s[0] = 9 // 修改切片第一个元素值为9
}
func main() {
var s []int // 创建一个空切片
// s := make([]int, 3) // 或则通过make创建一个初始长度为3的空切片
modifyInt(s) // 传入切片
}
当我们执行这段代码的时候,会惊讶的发现,和之前不同,这次居然可以修改,切片的第一个元素成功被赋值为了9!
为什么这个时候又不需要传入地址就能对原参数操作了呢?
这就不得不提一下切片的本质了
在Go语言中,切片(slice)是一种动态数组的抽象。切片提供了对数组的部分或全部元素的访问,并且可以通过对切片进行操作来动态改变其大小。切片的本质可以从以下几个方面来理解:
底层数组:
长度和容量:
动态调整:
引用语义:
总的来说,切片可以看作是对底层数组的一个动态窗口,它提供了便捷的方式来操作数组的子序列,并且具有动态调整大小的能力。理解切片的本质有助于更好地利用它们来管理和操作数据。
说人话,可以把切片看做一个特殊的结构体,这个特殊的结构体披着能给底层数组逻辑实现扩容的外衣,并且有三个成员(或则说特性):
当我们向modifyInt
传入切片参数时,事实上是传入的三个信息:指针,长度和容量
而当我们对切片进行修改时,就会通过指针指向的底层数组的地址,来对原切片所包含的底层数组内容进行修改
所以当我们期望用s[0] = 9
的形式,修改切片首元素为9时,事实上是通过传入的切片的指针这一“特性”,找到切片所指向的底层数组并对内容进行修改。所以当我们对切片首元素进行修改时,会影响到原来的切片。
毕竟此时就是在原来切片所指向底层数组的首元素地址上进行操作的
当我们打算用append
为切片新增元素时,或则删除原本某个切片元素时
func appendEle(s []int) {
s = append(s, 9) // 新增元素9
}
func removeEle(s []int) {
n := len(s) // 计算传进来的切片长度
s = (s)[0 : n-1] // 截取掉最后一个元素
}
func main() {
func main() {
s := make([]int, 3) // [0 0 0]
appendEle(s)
fmt.Println(s) // [0 0 0]
removeEle(s)
fmt.Println(s) // [0 0 0]
}
上面这个案例清晰的反馈了,如果想追加元素或删除尾部元素都不行
让我们回到append
本质上
最开始我们通过arr := make([]int, 3)
创建出arr
切片后,此时会产生一个底层数组,当我们通过append(arr, 9)
希望为切片arr
新增一个元素9
,此时在底层数组中也会新增一个元素9(毕竟,切片实际上就是为底层数组扩容的一层规则外衣)
我们通过创建brr := append(arr, 9)
而不是arr := append(arr, 9)
的形式,是为了清晰的知道,事实上通过append扩容的过程,就是给切片进行长度加一,此时的arr
和brr
是完全不同切片!只不过他们的内核都是基于同一个底层数组
当我们通过下面这段代码时,其实是开辟了一个新的空间,这个新的空间内核还是原来切片的底层数组,但是换了件衣服,系统就认不出来了
func appendEle(s []int) {
s = append(s, 9) // 新增元素9
}
函数内部的s
是一个新的,就像是前面修改函数内参数a
不会影响函数外的变量a
一样,都不是一个东西,如何能修改成功呢?
所以这里我们就需要通过传入切片的地址,让系统知道我们需要扩容的是原来的切片
// 此时传入的是切片的地址
func appendEle(s *[]int) {
*s = append(*s, 9) // 对切片解引用,获得传入切片地址所指向的值新增元素9
}
// 同理
func removeEle(s *[]int) {
n := len(*s) // 计算传进来的切片长度
*s = (s)[0 : n-1] // 截取掉最后一个元素
}
func main() {
func main() {
s := make([]int, 3) // [0 0 0]
appendEle(&s) // 传入切片的地址
fmt.Println(s) // [0 0 0 9]
removeEle(&s) // 传入切片的地址
fmt.Println(s) // [0 0 0]
}