Go中都是值传递,切记! 你所了解的引用传递等知识经验从今天开始彻底抛弃!

简介: Go中都是值传递,切记! 你所了解的引用传递等知识经验从今天开始彻底抛弃!

Go中都是值传递



我看在技术大群里面有人问我,到底值类型,引用类型以及指针类型有什么区别?为什么别人说函数传参是引用传递或者值传递,有的人说是指针传递?


想要彻底搞清楚这个问题,你得先通过表现来理解一下,然后在深入源码理解下你很快就会明白的。


所以网上说的有可能都是错误的,大家千万别被误导!


我们接下来重点解决函数传递参数到底是值传递还是其他类型传递!


三者区别


  • 值类型 就是变量赋值的时候将直接获得一个真实的数据副本,请大家再次看清楚,真实的"数据"副本。比如var int a = 10,b:=a 这就相当于a持有10,b持有a的数据的副本 那就将10拷贝到b指向的内存。
  • 引用类型 就是仅仅是把对象的引用赋给变量,这样就可能导致多个变量引用到一个实际对象实例上。比如a := make([]int, 0),b:=a[2:],那么a和b持有的就是底层对象的引用,说白了a和b就是底层对象的别名。
  • 指针类型 就是赋给变量的是一个内存地址,这个地址指向真实的数据,比如var int a = 10,p:=&a,这个p就是指针变量,它存储的就是a的地址。

注意:在Go中弱化了指针,所以一般只需要关注值和引用类型就可以。


函数传参


首先声明一点:在Go中除了slicemapchannel类型之外的变量都是值类型。

那就好说了,引用类型就三种:slice, map和channel,其他都是值类型。


在Go中引用类型的变量初始化默认是make,但是new也是可以的,比如:


package main
import "fmt"
type A []int 
func main() {
 a := new(A)
 *a = append(*a, 1)
 fmt.Println(*a) //[1]
 a1 := make([]int, 0)
 a1 = append(a1, 1)
 fmt.Println(a1) //[1]
}


当然map以及channel也是可以用makenew的,大家下去试试哈。

至于makenew的区别大家下去自己看哈,这不是本文的重点。


接下来我们重点来聊聊函数传参的问题,我们都知道值传递是copy一份,所以这个大家都能理解,我们重点讲解大家疑惑的问题,引用类型的传参问题。


首先大家看一个例子,即看看它的表现是什么:

package main
import "fmt"
func CopyList(a []int)  {
 a = append(a, 2)
 fmt.Printf("copylist is %p \n", &a)
 fmt.Println("copylist value is ", a)
}
func main() {
 a := make([]int, 0)
 a = append(a, 1)
 fmt.Println("main value is ", a)
 fmt.Printf("main address is %p \n", &a)
 CopyList(a)
}
输出:
main value is  [1]
main address is 0xc0000a6018 
copylist is 0xc0000a6048 
copylist value is  [1 2]


我们通过结果分析下引用传递会有什么影响。


  1. 首先函数参数是a []int
  2. main中定义的引用类型变量是a并且也做了初始化
  3. 然后调用CopyList(a)
  4. 看在main中a的地址和在CopyList中a的地址,发现不一样?为什么不一样呢?因为Go语言在设计的时候明确表示函数参数传递是值传递,所以相当于函数CopyList拷贝了一份变量,这个时候次a非比a。但是那你可能说:为什么拷贝了一份,但是从结果来看就是引用啊,因为函数CopyList改变了外面main中a的值。


答案就是Go在设计引用类型的时候,比如这三者map,slice和channel,他们的结构体中并没有直接保存数据,而是保存了指向数据的指针,那么在函数中传递这三种的变量其实只是拷贝了一份它们自己的一份结构体,但是结构体里面具体保存的指针指向的地址还是同一份,没有变化的。所以这也就说明函数中为什么可以改变引用类型变量的值了,因为底层指针指向的地址是同一个。


分析完了slice,其它两个都比较简单,这里就不再演示了,大家下去自己尝试。

640.png


type slice struct {
 array unsafe.Pointer //指针 到时候slice初始化的时候会用mallocgc分配内存,把这块内存地址保存到array中。
 len   int
 cap   int
}

map和channel里面也是有指针,指向一块内存空间。


小结


大家一定要记住在Go中函数传递参数一定是值传递,千万别搞混淆了,至于为什么上面也说清楚了,如果大家还不懂就进群,大牛比较多为你继续解惑哈。

- END -

相关文章
|
2月前
|
Go
go值传递和引用传递
go值传递和引用传递
12 0
|
3月前
|
Java Go 调度
Go语言并发编程原理与实践:面试经验与必备知识点解析
【4月更文挑战第12天】本文分享了Go语言并发编程在面试中的重要性,包括必备知识点和面试经验。核心知识点涵盖Goroutines、Channels、Select、Mutex、Sync包、Context和错误处理。面试策略强调结构化回答、代码示例及实战经历。同时,解析了Goroutine与线程的区别、Channel实现生产者消费者模式、避免死锁的方法以及Context包的作用和应用场景。通过理论与实践的结合,助你成功应对Go并发编程面试。
59 3
【Go 进阶】Go 语言到底是值传递,还是引用传递?(四)
【Go 进阶】Go 语言到底是值传递,还是引用传递?(四)
【Go 进阶】Go 语言到底是值传递,还是引用传递?(三)
【Go 进阶】Go 语言到底是值传递,还是引用传递?(三)
【Go 进阶】Go 语言到底是值传递,还是引用传递?(二)
【Go 进阶】Go 语言到底是值传递,还是引用传递?(二)
|
存储 Java 程序员
【Go 进阶】Go 语言到底是值传递,还是引用传递?(一)
【Go 进阶】Go 语言到底是值传递,还是引用传递?(一)
|
9天前
|
JSON 中间件 Go
go语言后端开发学习(四) —— 在go项目中使用Zap日志库
本文详细介绍了如何在Go项目中集成并配置Zap日志库。首先通过`go get -u go.uber.org/zap`命令安装Zap,接着展示了`Logger`与`Sugared Logger`两种日志记录器的基本用法。随后深入探讨了Zap的高级配置,包括如何将日志输出至文件、调整时间格式、记录调用者信息以及日志分割等。最后,文章演示了如何在gin框架中集成Zap,通过自定义中间件实现了日志记录和异常恢复功能。通过这些步骤,读者可以掌握Zap在实际项目中的应用与定制方法
go语言后端开发学习(四) —— 在go项目中使用Zap日志库
|
2天前
|
安全 Java Go
探索Go语言在高并发环境中的优势
在当今的技术环境中,高并发处理能力成为评估编程语言性能的关键因素之一。Go语言(Golang),作为Google开发的一种编程语言,以其独特的并发处理模型和高效的性能赢得了广泛关注。本文将深入探讨Go语言在高并发环境中的优势,尤其是其goroutine和channel机制如何简化并发编程,提升系统的响应速度和稳定性。通过具体的案例分析和性能对比,本文揭示了Go语言在实际应用中的高效性,并为开发者在选择合适技术栈时提供参考。
|
6天前
|
运维 Kubernetes Go
"解锁K8s二开新姿势!client-go:你不可不知的Go语言神器,让Kubernetes集群管理如虎添翼,秒变运维大神!"
【8月更文挑战第14天】随着云原生技术的发展,Kubernetes (K8s) 成为容器编排的首选。client-go作为K8s的官方Go语言客户端库,通过封装RESTful API,使开发者能便捷地管理集群资源,如Pods和服务。本文介绍client-go基本概念、使用方法及自定义操作。涵盖ClientSet、DynamicClient等客户端实现,以及lister、informer等组件,通过示例展示如何列出集群中的所有Pods。client-go的强大功能助力高效开发和运维。
27 1
|
6天前
|
SQL 关系型数据库 MySQL
Go语言中使用 sqlx 来操作 MySQL
Go语言因其高效的性能和简洁的语法而受到开发者们的欢迎。在开发过程中,数据库操作不可或缺。虽然Go的标准库提供了`database/sql`包支持数据库操作,但使用起来稍显复杂。为此,`sqlx`应运而生,作为`database/sql`的扩展库,它简化了许多常见的数据库任务。本文介绍如何使用`sqlx`包操作MySQL数据库,包括安装所需的包、连接数据库、创建表、插入/查询/更新/删除数据等操作,并展示了如何利用命名参数来进一步简化代码。通过`sqlx`,开发者可以更加高效且简洁地完成数据库交互任务。
13 1