Go 值传递与引用传递的方法

问题引入

  • 什么时候选择 T 作为参数类型,什么时候选择 *T 作为参数类型?
  • [ ] T 是传递的指针还是值?选择 [ ] T 还是 [ ] *T ?
  • 哪些类型复制和传递的时候会创建副本?
  • 什么情况下会发生副本创建?

T 和 *T 当做函数参数时都是传递它的副本

先看传 T 的情况:

type user struct {
  id int
  name string
}

func passByValue(_u user){
  _u.id++
  _u.name="jack"

  // when printing structs, the plus flag (%+v) adds field names
  fmt.Printf("_u 值:%+v;地址:%p; \n",_u,&_u)
}

func exp2(){
  u:=user{1,"peter"}
  fmt.Printf("原始 u 值:%+v; 地址: %p;\n",u,&u)
  passByValue(u)
  fmt.Printf("执行完函数后 u 值:%+v; 地址: %p;\n",u,&u)
}

执行 exp2 方法,输出结果为:

结果说明:

  • _u 是 u 的一份拷贝,地址不同
  • 函数内对参数的改变不影响原始的对象

再看传 *T 的情况:

type user struct {
  id int
  name string
}

func passByPointer(_u *user){
  _u.id++
  _u.name="jack"
  fmt.Printf("_u 值:%+v ;u指向的地址:%p; u本身存放地址:%p; \n",*_u,_u,&_u)
}

func exp3(){
  u:=&user{1,"peter"}
  fmt.Printf("原始u 值:%+v; 指向的地址: %p;u本身存放地址: %p; \n",*u,u,&u)
  passByPointer(u)
  fmt.Printf("原始u 值:%+v; 指向的地址: %p;u本身存放地址: %p; \n",*u,u,&u)
}

执行 exp3 方法的输出结果为:

注意到,虽然参数 _u 仍然是 u 的一份拷贝对象,但是原始对象的值还是改变了。可以这么理解,因为 u 指针和 _u 指针都指向同一个对象,即 0xc0000484a0 地址上存放的对象,_u.name="jack"可以看做*(_u).name="jack,即取值后再改变值。

改变指针参数的地址

type user struct {
  id int
  name string
}

func changeAddress(_u *user){
  _u=&user{2,"jack"}
  fmt.Printf("参数_u 值:%+v ;u指向的地址:%p; u本身存放地址:%p; \n",*_u,_u,&_u)
  return
}

func exp4(){
  u:=&user{1,"peter"}
  fmt.Printf("原始u 值:%+v; 指向的地址: %p;u本身存放地址: %p; \n",*u,u,&u)
  changeAddress(u)
  fmt.Printf("执行函数后 u 值:%+v; 指向的地址: %p;u本身存放地址: %p; \n",*u,u,&u)
}

输出结果如下:

注意,执行函数后 u 值没有改变!改变了参数指向的地址,原来的对象肯定就不受影响了。

传递数组参数 vs 传递切片参数

func passSlice(_s []int){
  _s[0]=99
  fmt.Printf("_s 值:%v,地址:%p\n",_s,&_s)
}

func exp6(){
  s:=[]int{11,22,33,44}
  fmt.Printf("s 值:%v,地址:%p\n",s,&s)
  passSlice(s)
  fmt.Printf("执行函数后 s 值:%v,地址:%p\n",s,&s)
}

对切片参数的修改会影响原来的切片。

再看传递数组

func passArray(_a [3]int){
  _a[0]=99
  fmt.Printf("_a 值:%v,地址:%p\n",_a,&_a)
}

func exp7(){
  a:=[3]int{22,33,44}
  fmt.Printf("a 值:%v,地址:%p\n",a,&a)
  passArray(a)
  fmt.Printf("执行函数后 a 值:%v,地址:%p\n",a,&a)
}

对数组参数的修改并不会影响原来的切片。

总结会发生副本创建的情况

  • 赋值操作,如 u1:=u2。包括 slice,map,array 在初始化和按索引设置的时候都会创建副本
  • for-range循环也是将元素的副本赋值给循环变量,但注意一点,循环变量是被复用的,所以地址不会变
  • 将变量作为参数传递。但注意一点, slice,map,chanel 三者都和 *T 一样,属于引用传递,虽然是发生了副本创建,但是函数内对参数的值进行修改会影响原来的值。而数组不同于 slice,函数内对数组参数的值进行修改不会影响原来数组
  • 将返回值赋值给其它变量或者传递给其它的函数和方法
  • 字符串比较特殊,它的值不能修改,任何想对字符串的值做修改都会生成新的字符串
  • 函数也是一个指针类型,对函数对象的赋值只是又创建了一个对次函数对象的指针。

总结指针类型

  • slice
  • map
  • chanel
  • 函数

如何选择 T 和 *T

对函数的参数或者返回值定义成 T 还是 *T 要考虑以下几点:

  • 一般的判断标准是看副本创建的成本和需求。
  • 如果不想变量被函数所修改,那么选择类型 T
  • 如果变量是一个很大的struct或者数组,副本的创建相对会影响性能,这个时候要考虑使用*T,只创建新的指针
  • 对于函数作用域内的参数,如果定义成 T , Go 编译器尽量将对象分配到栈上,而 *T 很可能会分配到对象上,这对垃圾回收会有影响

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 使用go来操作redis的方法示例

    Redis简单介绍 简介 关于Redis的讨论,其实在现在的后台开发中已经是个老生常谈的问题,基本上也是后端开发面试的基本考察点.其中 Redis的背景介绍和细节说明在这里就不赘述.不管怎么介绍,核心在于Redis是一个基于内存的key-value的多数据结构存储,并可以提供持久化服务.基于内存的特性决定了Redis天然适合高并发的数据读写缓存优化,同时也带来了内存开销过大的问题.所以在一些特定情景下,Redis是一把无往不利的大杀器,值得深入学习. 安装redis, 运行如下go代码: pac

  • Golang 函数执行时间统计装饰器的一个实现详解

    背景 最近在搭一个新项目的架子,在生产环境中,为了能实时的监控程序的运行状态,少不了逻辑执行时间长度的统计.时间统计这个功能实现的期望有下面几点: 实现细节要剥离:时间统计实现的细节不期望在显式的写在主逻辑中.因为主逻辑中的其他逻辑和时间统计的抽象层次不在同一个层级 用于时间统计的代码可复用 统计出来的时间结果是可被处理的. 对并发编程友好 实现思路 统计细节的剥离 最朴素的时间统计的实现,可能是下面这个样子: func f() { startTime := time.Now() logicSt

  • 使用go xorm来操作mysql的方法实例

    在golang中要实现对数据库的操作, 一个比较好用的方式就是使用XORM,xorm是一个简单而强大的Go语言ORM库,通过它可以使数据库操作非常简便. 在xorm里面,可以同时存在多个ORM引擎,一个ORM引擎称为Engine,一个Engine一般只对应一个数据库. golang的连接池配置 这里初始化一个全局的xorm.Engine对象, xorm.Engine通过调用xorm.NewEngine生成在很多其他的API都需要传入该对象作为参数. 这里需要理解这里设定的3个参数的意义: Set

  • Golang 使用接口实现泛型的方法示例

    在C/C++中我们可以使用泛型的方法使代码得以重复使用,最常见例如stl functions:vector<int> vint or vector<float> vfloat等.这篇文章将使用interface{...}接口使Golang实现泛型. interface{...}是实现泛型的基础.如一个数组元素类型是interface{...}的话,那么实现了该接口的实体都可以被放置入数组中.注意其中并不一定必须是空接口(简单类型我们可以通过把他转化为自定义类型后实现接口).为什么i

  • golang time包的用法详解

    在我们编程过程中,经常会用到与时间相关的各种务需求,下面来介绍 golang 中有关时间的一些基本用法,我们从 time 的几种 type 来开始介绍. 时间可分为时间点与时间段,golang 也不例外,提供了以下两种基础类型 - 时间点(Time) - 时间段(Duration) 除此之外 golang 也提供了以下类型,做一些特定的业务 - 时区(Location) - Ticker - Timer(定时器) 我们将按以上顺序来介绍 time 包的使用. 时间点(Time) 我们使用的所有与

  • golang时间、时区、格式的使用方法

    前几天,因为需要实现海外服务端定时停机,涉及到时区的概念.网上搜索了一下,大部分都是谈time.Format中的Layout,非常不成体系,这里就简单总结一下其中的时间初始化.时区转化及格式转换. 开发中,我们对时间的使用是比较多的,其应用场景,按照使用概率,从大到小,通常是: 获取当前或数据库中存储的时间 比较两个时间点的先后 显示打印时间 时区转换 对应到go,也就是几个基本定义: 时间点与时间段:Time,Duration.好比MVC中的M. 时 区:Location,在时间转换上,好比是

  • go语言的panic和recover函数用法实例

    Golang 有2个内置的函数 panic() 和 recover(),用以报告和捕获运行时发生的程序错误,与 error 不同,panic-recover 一般用在函数内部.一定要注意不要滥用 panic-recover,可能会导致性能问题,我一般只在未知输入和不可靠请求时使用. golang 的错误处理流程:当一个函数在执行过程中出现了异常或遇到 panic(),正常语句就会立即终止,然后执行 defer 语句,再报告异常信息,最后退出 goroutine.如果在 defer 中使用了 re

  • golang读取文件的常用方法总结

    使用go语言读取文件的各种方式整理. 一次性加载到内存中 // * 整个文件读到内存,适用于文件较小的情况 //每次读取固定字节 //问题容易出现乱码,因为中文和中文符号不占一个字符 func readAllIntoMemory(filename string) (content []byte, err error) { fp, err := os.Open(filename) // 获取文件指针 if err != nil { return nil, err } defer fp.Close(

  • Go JSON编码与解码的实现

    在开发应用程序时,客户端(前端页面或APP)与服务端交互是在所难免的,在交互过程传递数据时,最通用和流行格式便是JSON,Go语言提供了encoding/json包,用于处理JSON数据的编码与解码. 除了JSON,XML也常用于前后端的数据交互,不过由于简洁性.可读性和流行程度,JSON用得更加广泛. JSON简介 1. 什么是JSON? JSON全称为Javascript Object Notation,一种数据结构化交互的标准协议,易于阅读与编写,所以在数据交互时广泛使用. 2. JSON

  • go module使用本地包的方法示例

    go module的使用非常简单 初始化go.mod go mod init 整理依赖包 go mod tidy 如果想缓存到vendor目录 go mod vendor 执行命令之后都会自动把依赖搞定. 但是, 如果我们是本地开发的包, 还没有远程仓库的时候, 要怎么解决本地包依赖问题呢? 使用replace将远程包替换为本地包服务 幸运的是, go module 提供了另外一个方案, replace, 这个replace怎么使用的呢? 我们先看一下一个最基本的mod文件 module GoR

随机推荐