当前位置: 首页 > article >正文

go函数传值是值传递?还是引用传递?slice案例加图解

先说下结论

Go语言中所有的传参都是值传递(传值),都是一个副本,一个拷贝。

  • 值语义类型:参数传递的时候,就是值拷贝,这样就在函数中就无法修改原内容数据。

    • 基本类型:byte、int、bool、float32、float64、string等;
    • 复合类型:array、struct和指针等
  • 引用语义类型:参数传递的时候,也是值拷贝,不过是这样就可以修改原内容数据。

    • map、slice、chan和接口

引用类型值传递图解

下图以 slice 为例,说明引用类型的变量作为实参传递给函数形参时是值传递(拷贝):

在这里插入图片描述

在Go语言里,虽然只有传值,但是我们也可以修改原内容数据,因为参数是引用类型,在函数传递引用类型的变量时,会如上图进行值拷贝,拷贝的数据里有引用变量引用的数据的地址。

值语义类型的参数传递

package main

import "fmt"

func main() {
	var by byte = 123
	var i int64 = 10
	var boolean bool = false
	var f32 float32 = 3.14
	var f64 float64 = 3.1415926
	var str string = "hello world"
	fmt.Printf("mian 函数中的变量 by 的内存地址是 %p\n", &by)
	fmt.Printf("mian 函数中的变量 i 的内存地址是 %p\n", &i)
	fmt.Printf("mian 函数中的变量 boolean 的内存地址是 %p\n", &boolean)
	fmt.Printf("mian 函数中的变量 f32 的内存地址是 %p\n", &f32)
	fmt.Printf("mian 函数中的变量 f64 的内存地址是 %p\n", &f64)
	fmt.Printf("mian 函数中的变量 str 的内存地址是 %p\n", &str)
	fmt.Println("=======================函数调用前=============")
	callByValue(by, i, boolean, f32, f64, str)
	fmt.Println("=======================函数调用后=============")
	fmt.Printf("被调函数中修改形参的值,main 函数中打印结果为(不变): %v\n", by)
	fmt.Printf("被调函数中打印结果为:%v\n", i)
	fmt.Printf("被调函数中打印结果为:%v\n", boolean)
	fmt.Printf("被调函数中打印结果为:%v\n", f32)
	fmt.Printf("被调函数中打印结果为:%v\n", f64)
	fmt.Printf("被调函数中打印结果为:%v\n", str)
}

func callByValue(by byte, i int64, boolean bool, f32 float32, f64 float64, str string) {
	fmt.Printf("被调函数中,形参的 by 内存地址是:%p\n", &by)
	fmt.Printf("被调函数中,形参的 i 内存地址是:%p\n", &i)
	fmt.Printf("被调函数中,形参的 boolean 内存地址是:%p\n", &boolean)
	fmt.Printf("被调函数中,形参的 f32 内存地址是:%p\n", &f32)
	fmt.Printf("被调函数中,形参的 f64 内存地址是:%p\n", &f64)
	fmt.Printf("被调函数中,形参的 str 内存地址是:%p\n", &str)

	by = 10
	i = 20
	boolean = true
	f32 = 13.14
	f64 = 13.146666
	str = "hello golang"
}
mian 函数中的变量 by 的内存地址是 0xc00010200a , 值为: 123
mian 函数中的变量 i 的内存地址是 0xc000102020 , 值为: 10
mian 函数中的变量 boolean 的内存地址是 0xc000102028 , 值为: false
mian 函数中的变量 f32 的内存地址是 0xc00010202c , 值为: 3.14
mian 函数中的变量 f64 的内存地址是 0xc000102030 , 值为: 3.1415926
mian 函数中的变量 str 的内存地址是 0xc000104140 , 值为: hello world
=======================函数调用前=============
被调函数中,形参的 by 内存地址是:0xc000102048 ,值为:123 
被调函数中,形参的 i 内存地址是:0xc000102050 ,值为:10 
被调函数中,形参的 boolean 内存地址是:0xc000102058 ,值为:false 
被调函数中,形参的 f32 内存地址是:0xc00010205c ,值为:3.14 
被调函数中,形参的 f64 内存地址是:0xc000102060 ,值为:3.1415926 
被调函数中,形参的 str 内存地址是:0xc000104160 ,值为:hello world 
=======================函数内部修改值=============
=======================函数调用后=============
被调函数中修改形参的值,main 函数中打印结果为(不变): 123
被调函数中打印结果为:10
被调函数中打印结果为:false
被调函数中打印结果为:3.14
被调函数中打印结果为:3.1415926
被调函数中打印结果为:hello world

从日志中可以发现:main 函数实参的地址和被调函数callByValue中形参的地址不同,在被调函数中修改形参的值并不会 影响实参变量的值。

指针类型

形参和实际参数内存地址不一样,证明是值传递。由于形参和实参是指针类型,指向同一个变量,函数内对指针指向变量的修改,会修改原内容数据。

package main

import "fmt"

func main() {
	var i int64 = 1
	fmt.Printf("main 函数中 i 内存地址是 %p\n", &i)     //0xc000104020 
	ip := &i
	callByPointer(ip)
	fmt.Printf("改动后的值是: %v\n", i)
}

func callByPointer(ip *int64) { //这里定义的args就是形式参数
	fmt.Printf("callByPointer形参的内存地址是:%p\n", &ip) //0xc000108068
	fmt.Printf("callByPointer形参的值是:%p\n", ip)       //0xc000104020
	*ip = 10 //解引用
}
main 函数中 i 内存地址是 0xc000104020 
callByPointer形参的内存地址是:0xc000108068
callByPointer形参的值是:0xc000104020
改动后的值是: 10

引用语义类型变量的参数传递

package main

import "fmt"

func main() {
	//切片
	var s = make([]int64, 5, 10)
	s[0] = 1
	s[1] = 2
	s[2] = 3
	s[3] = 4
	s[4] = 5
	fmt.Printf("原始切片 len %v ,cap %v", len(s), cap(s))
	var p = &s
	fmt.Printf("原始切片   取地址(&s):%p ; \n直接对原始切片取地址( p):%p \n", &s, p)
	fmt.Printf("原始切片   底层数组的内存地址(s):     %p  \n原始切片   第一个元素的内存地址(&s[0]): %p\n", s, &s[0])
	callBySliceParam(s)
	fmt.Printf("改动后的值是: %v\n", s)
}

func callBySliceParam(s1 []int64) {
	fmt.Printf("函数里,函数参数(切片)取地址 %p\n", &s1)
	fmt.Printf("函数里,函数参数(切片)的底层数组的内存地址是 %p \n", s1)
	fmt.Printf("函数里,函数参数(切片)第一个元素的内存地址: %p \n", &s1[0])
	s1[0] = 10
}
原始切片 len 5 ,cap 10原始切片   取地址(&s):0xc0001120a8 ; 
直接对原始切片取地址( p):0xc0001120a8 
原始切片   底层数组的内存地址(s):     0xc00012c000  
原始切片   第一个元素的内存地址(&s[0]): 0xc00012c000
函数里,函数参数(切片)取地址 0xc0001120d8
函数里,函数参数(切片)的底层数组的内存地址是 0xc00012c000 
函数里,函数参数(切片)第一个元素的内存地址: 0xc00012c000 
改动后的值是: [10 2 3 4 5]

通过输出日志,可以清楚地看到切片作为引用类型的特点:传递切片时,实际上是传递了切片的副本,但这个副本仍然指向同一个底层数组。因此,对切片的修改会影响到原始切片。


http://www.kler.cn/a/390172.html

相关文章:

  • 3.Qt Quick-QML地图引擎之v4.3版本(新增动态轨迹线/海图/天地图街道/天地图卫星)
  • C语言初阶习题【30】字符串左旋
  • SQL BETWEEN 操作符
  • An FPGA-based SoC System——RISC-V On PYNQ项目复现
  • C#中的ref struct
  • 【WEB】网络传输中的信息安全 - 加密、签名、数字证书与HTTPS
  • 数据分析-44-时间序列预测之深度学习方法TCN
  • Autosar CP Network Management模块规范导读
  • C#中 layout的用法
  • 易泊车牌识别相机:4S 店的智能之选
  • Spring Boot与Spring Data JPA:简化数据库操作
  • Python毕业设计选题:基于django+vue的医院挂号系统设计与实现
  • stm32 踩坑笔记
  • 通过EtherNetIP转Profinet网关实现跨品牌EthernetIP协议的PLC通讯
  • 【系统设计】数据库压缩技术详解:从基础到实践(附Redis内存优化实战案例)
  • C#中break和continue的区别?
  • HARCT 2025 分论坛4:智能系统传感、传感器开发和数据融合中的智能数据分析
  • 【NOIP普及组】统计单词数
  • Redis数据结构及实现
  • AI Prompt 提示词的5大经典模型
  • 30.1 时序数据库TSDB的典型特点
  • 准确--FastDFS快速单节点部署
  • 【LeetCode】【算法】3. 无重复字符的最长子串
  • 单目视觉测量及双目视觉测量
  • AndroidStudio 加载grade失败问题解决
  • 企业级大数据安全架构