go 函数或者方法参数调用的过程
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
前言
最近做项目使用go开发但是在发生函数调用传参数时对指针的指针的传递有难以理解的代码就此分析过程。尤其是对于多重指针作为参数而且对于一些内置函数的修改逻辑也需深入的理解。
1. demo
package slice_demo
import (
"fmt"
"testing"
)
func TestSliceCall(t *testing.T) {
s := make([]int, 1, 10)
fmt.Printf("1.---- %p %p %v\n", &s, s, s)
a := append(s, 2, 3)
fmt.Printf("1.---- %p %p %v\n", &a, a, a)
changeSlice(a)
changeSlicePoint(&a)
fmt.Println(a, s)
}
func changeSlice(b []int) {
b = append(b, 5, 6)
b[0] = 111
fmt.Printf("2.---- %p %p %v\n", &b, b, b)
}
func changeSlicePoint(p *[]int) {
*p = append(*p, 10, 12)
fmt.Printf("3.---- %p %p %p %v\n", &p, p, *p, p)
}
从demo看有2种方式传参数修改切片的值有下标修改有append修改结果有点出乎我们日常逻辑结果如下
=== RUN TestSliceCall
1.---- 0xc000118090 0xc000154140 [0]
1.---- 0xc0001180d8 0xc000154140 [0 2 3]
2.---- 0xc000118120 0xc000154140 [111 2 3 5 6]
3.---- 0xc000100028 0xc0001180d8 0xc000154140 &[111 2 3 10 12]
[111 2 3 10 12] [111]
--- PASS: TestSliceCall (0.00s)
PASS
可以看到append后的指针跟append前的切片是一样的切片本身是引用类型实现逻辑是数组且是同一个数组没扩容且内存地址一样但是值不一样。
而且切片在传入函数参数后通过append并没有修改 传入的参数切片引用可以通过下标修改但是通过切片指针通过append却可以修改原理是什么😅
2. 分析
2.1 先分析append的情况
结合这2行打印分析append的过程
可以看到创建了2个变量栈的引用 所以分配了2个内存地址同时指向同一个数组因为切片本身是指针然而s和a的值却不一样。那么这是什么原因实际上数组的内存地址仅仅是数组开始的内存地址数组是连续的内存空间是一个初始地址+offset或者position定位。看slice结构体可以明白实际上内存地址后面的值已经被改掉了。
2.2 slice的结构体
type slice struct {
array unsafe.Pointer
len int
cap int
}
slice.go定义了结构体实际上slice是数组指针与len cap组成的slice的扩容就是创建复制数组 修改len和cap。
2.3 slice直接传入函数参数
实际上append未生效并不是未生效已经修改了数组后面的连续内存的值了只是因为没有对原始的变量赋值没有修改len与cap因为切片的结构体slice是基本数据类型内存拷贝是2个结构体所以读取值是没变的。
分析原理画成图跟实际结果符合。
2.4 slice传入函数指针
实际上切片没必要使用指针因为切片本身就是指针很多教程也推荐切片不使用指针直接用但是指针却有特殊的意义可以修改传入的参数的数据值。
因为传入的切片的指针所以p可以认为是a因为是a的指针内存地址操作a的指针内存地址就等于操作变量a所以对*p赋值可以修改a的值。
2.5 切片操作相互影响
因为切片的实现是数组因为是指针所以变量的修改会相互影响。这个就很容易理解了
总结
这里的问题是切片本身是指针如果再加入指针就是指针的指针很难理解。而且再结合函数的参数本身函数的参数是一个引用栈变量自己又会分配内存地址就更难理解了😅。这里的关键还有切片是一个结构体存储的但是结构体又是内存值拷贝而非内存地址引用。实际上可以结合内存分配的流程结合函数的入栈出栈外加参数的存储结构很容易就明白原理了。