将切片作为参数传入函数并使用append方法遇到的问题

2023-10-27

切片的内部结构:

type SliceHeader struct {
    Data uintptr
    Len int
    Cap int
}

由切片的结构定义可知,切片的结构由三个信息组成:

  • 指针Data,指向底层数组中切片指定的开始位置
  • 长度Len,即切片的长度
  • 容量Cap,也就是最大长度,即切片开始位置到数组的最后位置的长度

问题:
将切片作为函数参数传入时,在函数内使用append方法并不能改变切片。

如下述代码所示:

func main() {
   //创建一个长度和容量均为3的切片
   arr := []int{1,2,3}
   fmt.Println(arr) // [1 2 3]
   //-------
   addNum(arr)      
   //-------
   fmt.Println(arr) // [1,2,3]
}

func addNum(sli []int){
   //使用appedn添加"4"
   sli = append(sli,  4)
   fmt.Println(sli) // [1,2,3,4]
}

初步分析:
切片是作为值传递给函数的,而非引用传递,因此在函数中会创建一个拷贝切片,而拷贝切片指针也是指向原切片指针所指地址。

在函数中使用append方法,切片的底层数组进行了扩容处理,因此在拷贝切片中,指针指向了新的数组,而原切片并没有指向新的数组,因此原切片不会添加新的值。

测试如下:

func main() {
   arr := []int{1,2,3}
   fmt.Printf("%p\n",arr) //0xc000014150
   //-------
   addNum(arr)
   //-------
   fmt.Printf("%p\n",arr) //0xc000014150
}

func addNum(sli []int){
   sli = append(sli,  4)
   fmt.Printf("%p\n",sli) //0xc00000c360
}

从输出结果可看出,拷贝切片指针发生改变,而原切片指针没有变化。

一个例子:
此时创建一个长度为3,容量为4的切片。

再次使用append方法在函数中对切片进行添加操作。

代码如下:

func main() {
   arr := make([]int,3,4)//创建一个长度为3,容量为4的切片
   fmt.Printf("%p\n",arr)  //0xc000012200
   // -----
   addNum(arr)
   // -----
   fmt.Printf("%p\n",arr)  //0xc000012200
}

func addNum(sli []int){
   sli = append(sli,  4)
   fmt.Printf("%p\n", sli) //0xc000012200
}

从结果可以看出,因为初始时,已经设置了切片的容量为4,所以拷贝切片并没有因为扩容指向新的数组。

那此时原切片是否会发生改变?

func main() {
   arr := make([]int, 3, 4) //创建一个长度为3,容量为4的切片
   fmt.Printf("%p\n", arr) //0xc000012200
   fmt.Println(arr)        //[0 0 0]
   // -------
   addNum(arr)
   // -------
   fmt.Printf("%p\n", arr) //0xc000012200
   fmt.Println(arr)        //[0 0 0]
}

func addNum(sli []int) {
   sli = append(sli, 4)
   fmt.Printf("%p\n", sli) //0xc000012200
   fmt.Println(sli)        //[0 0 0 4]
}

从代码运行结果可以看出,原切片并没有发生改变。

因为,虽然原切片的底层数组发生了变化,但长度Len没有发生变化,因此原切片的值仍然不变。

func main() {
	arr := make([]int, 3, 4) //创建一个长度为3,容量为4的切片
	fmt.Println(arr, len(arr), cap(arr)) //[0 0 0] 3 4
	// -----
	addNum(arr)
	// -----
	fmt.Println(arr, len(arr), cap(arr)) //[0 0 0] 3 4
}

func addNum(sli []int) {
	sli = append(sli, 4)
	fmt.Println(sli, len(sli), cap(sli)) //[0 0 0 4] 4 4
}

另一个例子:
仍然将切片作为参数传入函数,在函数中修改切片的值。

代码如下

func main() {
   arr := []int{1, 2, 3, 4}
   fmt.Println(arr) //[1 2 3 4]
   // -----
   editNum(arr)
   // -----
   fmt.Println(arr) //[666 2 3 4]
}

func editNum(sli []int) {
   sli[0] = 666
   fmt.Println(sli) //[666 2 3 4]
}

此时,从结果上,看起来切片的传参是采用引用传递。

但实际上,切片的传参是使用值传递。

函数能够对切片进行修改,是因为在函数中,拷贝切片所指的数组发生了变化,因此原切片的结果也发生变化。

总结
将一个切片作为函数参数传递给函数时,其实采用的是值传递,因此传递给函数的参数其实是切片结构体的值拷贝。因为Data是一个指向数组的指针,所以对该指针进行值拷贝时,得到的指针仍指向相同的数组,所以通过拷贝的指针对底层数组进行修改时,原切片的值也会发生相应变化。

但是,我们以值传递的方式传递切片结构体的时候,同时也是传递了Len和Cap的值拷贝,因为这两个成员并不是指针,因此,当函数返回时,原切片结构体的Len和Cap并没有改变。

所以可以解释如下现象:当传递切片给函数时,并且在函数中通过append方法向切片中增加值,当函数返回的时候,切片的值没有发生变化。

其实底层数组的值是已经改变了的(如果没有触发扩容的话),但是由于长度Len没有发生改变,所以显示的切片的值也没有发生改变。

如果需要在函数中进行append操作怎么办?
答:一个方法就是用指针。

实例代码如下:

func main() {
   arr := []int{1, 2, 3, 4}
   fmt.Println(arr)    //[1 2 3 4]
   // -----
   addNum(&arr)
   // -----
   fmt.Println(arr)   //[1 2 3 4 5]
}

func addNum(sli *[]int) {
   *sli = append(*sli, 5)
   fmt.Println(*sli) //[1 2 3 4 5]
}

补充信息:Go中没有引用传递

Go中函数调用只有值传递。

但网上有很多的说法,最多的是slice,map和chan作为参数传递到函数中时是传的引用,其实这个说法不准确,我们不能单纯因为函数内部的修改可以反馈到外面就认为是传递的引用。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

将切片作为参数传入函数并使用append方法遇到的问题 的相关文章

  • Go的并发的退出

    有时候我们需要通知goroutine停止它正在干的事情 比如一个正在执行计算的web服务 然而它的客户端已经断开了和服务端的连接 Go语言并没有提供在一个goroutine中终止另一个goroutine的方法 由于这样会导致goroutin
  • 【Golang入门】Golang第一天心得

    生活所迫 入门一下Go 很奇葩的第一点 接口 package main import fmt 定义一个接口 type Shape interface Area float64 定义一个矩形类型 type Rectangle struct W
  • Go Web编程实战(6)----反射

    目录 反射 反射的3大原则 接口类型变量 转换为 反射类型对象 反射类型对象 转换为 接口类型变量 反射类型对象 修改 值必 可写的 反射 与其他语言一样 Go语言的反射同样是指 计算机程序在运行时 可以访问 检测和修改它本身状态或行为的一
  • Go语言实现区块链与加密货币-Part3(交易优化,单机模拟多节点通信)

    交易 二 在这个系列文章的一开始 我们就提到了 区块链是一个分布式数据库 不过在之前的文章中 我们选择性地跳过了 分布式 这个部分 而是将注意力都放到了 数据库 部分 到目前为止 我们几乎已经实现了一个区块链数据库的所有元素 今天 我们将会
  • Golang-使用 goroutine 运行闭包的“坑”

    介绍 在 Go 语言中 函数支持匿名函数 闭包就是一种特殊的匿名函数 它可以用于访问函数体外部的变量 需要注意的是 在 for range 中 使用 goroutine 执行闭包时 经常会掉 坑 因为匿名函数可以访问函数体外部的变量 而 f
  • Qt webengine 显示web页面、前后端通信以及下载详解

    概述 官方文档 https doc qt io archives qt 5 11 qtwebengine overview html 翻译文档 Qt5 9 WebEngine 概述 一花一世界 一叶一乾坤 博客园 从Qt5 5开始 Qt W
  • beego+goAdmin+mysql+docker+natapp作为微信小程序地服务器“伪部署”

    写在前面的话 1 为什么我要叫伪部署 答 因为我把它们放在服务器运行 都是开发模式 生产模式实在不会弄 所以就这样了 2 系统环境 答 腾讯云服务器 系统为 ubuntu 版本不记得 应该是比较高的 3 前提假设 答 假设你的服务器已经安装
  • Go语言里面的各种疑难杂症

    什么是闭包 闭包有什么缺陷 func AddUpper func int int var n int 10 return func x int int n n x return n func main f AddUpper fmt Prin
  • Golang 内存对齐视频

    https www bilibili com video BV1Ja4y1i7AF 简而言之 就是注意写代码的时候要把相同类型的元素放在一起 更进一步需要自己将结构体配对为32位或64位的整数倍 有助于减少额外空间消耗
  • 基于Go语言实现简易Web应用

    目录 前言 Go语言特点 写在使用Go语言实现Web应用前面 创建Web服务器 声明一个结构体操作 加入中间件的使用 使用静态文件服务器 最后 前言 在编程语言中 近几年问世的几个新语言都是非常不错的 比如Go Python Rust等等
  • 为什么最近听说 Go 岗位很少很难?

    大家好 我是煎鱼 其实这个话题已经躺在我的 TODO 里很久了 近来很多社区的小伙伴都私下来交流 也有在朋友圈看到朋友吐槽 Go 上海的大会没什么人 还不如 Rust 大会 比较尴尬 今天主要是看看为什么 Go 岗位看起来近来很难的样子 也
  • 深入理解 Go 语言中的接口(interface)

    一 GoLang 接口的定义 1 GoLang 中的接口 在 Go 语言中接口 interface 是一种类型 一种抽象的类型 接口 interface 定义了一个对象的行为规范 只定义规范不实现 由具体的对象来实现规范的细节 实现接口的条
  • go-zero开发入门之网关往rpc服务传递数据2

    go zero 的网关服务实际是个 go zero 的 API 服务 也就是一个 http 服务 或者说 rest 服务 http 转 grpc 使用了开源的 grpcurl 库 当网关需要往 rpc 服务传递额外的数据 比如鉴权数据的时候
  • go-zero开发入门-API服务开发示例

    接口定义 定义 API 接口文件 接口文件 add api 的内容如下 syntax v1 info title API 接口文件示例 desc 演示如何编写 API 接口文件 author 一见 date 2023年12月07日 vers
  • go-zero开发入门之gateway深入研究1

    创建一个 gateway 示例 main go package main import flag fmt gateway middleware github com zeromicro go zero core conf github co
  • “go mod tidy”之错误“not a valid zip file”

    执行 go mod tidy 时 遇到如下错误 rpc imports github com zeromicro go zero zrpc imports github com zeromicro go zero zrpc resolver
  • 【go语言】error错误机制及自定义错误返回类型

    简介 Go 语言通过内置的 error 接口来处理错误 该接口定义如下 type error interface Error string 这意味着任何实现了 Error 方法的类型都可以作为错误类型 在 Go 中 通常使用 errors
  • go开发--操作mysql数据库

    在 Go 中访问 MySQL 数据库并进行读写操作通常需要使用第三方的 MySQL 驱动 Go 中常用的 MySQL 驱动有 github com go sql driver mysql 和 github com go xorm xorm
  • 这套Go语言开发框架组合真的非常高效

    我尝试过很多框架 从Django Flask和Laravel到NextJS和SvelteKit 到目前为止 这是我唯一可以使用的不会让我感到疯狂或者放弃项目的堆栈 框架 我喜欢所有这些框架 但我只是不太适应它们的设计方式 实际上 我是一个弱
  • Go、Docker、云原生学习笔记全攻略:从零开始,一步步走向精通!(2024版)

    第一章 Go语言学习宝典 一 介绍 01 Go 语言的前生今世 二 开发环境搭建 01 Go 语言开发环境搭建 三 初识GO语言 01 Go 多版本管理工具 02 第一个 Go 程序 hello world 与 main 函数 03 Go

随机推荐

  • 【C++ STL容器】:vector存放数据以及存放自定义的数据类型

    前言 时不可以苟遇 道不可以虚行 STL 中最常用的容器为 vector 暂且把它理解为我们之前学过的数组Array 一 创建一个vector容器 数组 添加头文件 include
  • react scss.modules中使用iconfont

    全局引入详见全局引入scss 全局的scss文件中引入iconfont css use font iconfont css 然后就可以正常使用啦
  • MySQL互为主从

    MySQL互为主从 Mysql A 192 168 189 140 Mysql B 192 168 189 141 在A B上操作 安装 root localhost yum y install mysql mysql server 修改配
  • RHCE(KVM——配置虚拟机网络连接)

    1 了解虚拟网络 主机硬件必须协助虚拟机 VM 连接到网络上的其他设备和位置 以下小节解释了虚拟机网络连接的机制 并描述了默认虚拟机网络设置 1 1 虚拟网络的工作方式 虚拟网络使用了虚拟网络交换机的概念 虚拟网络交换机是在主机机器中运行的
  • keil提示No Browse Information available in ‘..\OBJ\SPI‘解决方法

    1 No Browse Information available in OBJ SPI 解决方法 https blog csdn net frozennet article details 107213145
  • Maven入门详解与安装配置

    Maven Maven出现前的问题 假设你现在做了一个项目 项目中肯定要用到一些jar包 比如说mybatis log4j JUnit等 除了这些之外 你有可能用到你的同事开发的其他的东西 比如说别人做了一个财务模块或做了一个结算的模块 你
  • Gradle 入门到精通(三)

    前言 根据我们上一篇的介绍 我们知道了项目的结构以及构建的流程 根据上面的知识 我们知道了构建的规则实际就是我们写在build gradle的内容 gradle android插件读取这个文件的内容后 最后完成构建工作 在讲解实际内容前 我
  • 基于JSP的医院预约挂号管理系统

    项目背景 网络的广泛应用给生活带来了十分的便利 所以把医院预约挂号管理与现在网络相结合 利用java技术建设医院预约挂号系统 实现医院预约挂号的信息化 则对于进一步提高医院预约挂号管理发展 丰富医院预约挂号管理经验能起到不少的促进作用 医院
  • 通俗易懂解释知识图谱

    通俗易懂解释知识图谱 Knowledge Graph 1 前言 2 知识图谱定义 3 数据类型和存储方式 4 知识图谱的架构 4 1 逻辑架构 4 2 技术架构 5 信息抽取 5 1 实体抽取 Entity Extraction 5 2 关
  • linux日志筛选查找命令

    日志实时监控 tail f spring log 关键字实时监控 tail f spring log grep key 如果没有特殊字符 可以不用引号 如果关键字有引号使用单引号和双引号配合使用 输出匹配内容上下行 输出匹配行以及下面5行
  • python函数中文手册-Python参考手册(第4版)

    第一部分 Python语言 第1章 Python简介 2 1 1 运行Python 2 1 2 变量和算术表达式 3 1 3 条件语句 5 1 4 文件输入和输出 6 1 5 字符串 7 1 6 列表 8 1 7 元组 9 1 8 集合 1
  • 一文搞懂常见的git操作

    git branch 查看本地所有分支 git status 查看当前状态 git commit 提交 git branch a 查看所有的分支 git branch r 查看远程所有分支 git commit am nit 提交并且加注释
  • Vue中如何进行自定义动画与动画效果设计

    Vue中如何进行自定义动画与动画效果设计 在Vue中 动画效果是非常有用的 它可以使用户界面变得更加生动 有趣 从而提高用户体验 Vue提供了一套非常方便的动画系统 使得我们可以非常容易地实现动画效果 在本文中 我们将学习如何在Vue中进行
  • 【Qt教程】1.10 - Qt5模态与非模态对话框( QDialog)

    1 对话框简介 对话框简介 通常是一个顶层窗口 出现在程序最上层 用于实现短期任务或者简洁的用户交互 对话框分为模态对话框和非模态对话框 模态对话框 会阻塞同一应用程序中其他窗口的输入 非模态对话框 可以在显示的同时 也能对其他窗口进行操作
  • 下载csdn的文章

    下载csdn的文章 在文章界面点击开发者选项 到console界面 输入 function side remove comment title comment list comment bar comment form announce a
  • vue 角色权限控制页面,页面内的按钮。总结思路

    页面权限控制 动态路由 前端创建asyncRoutes 添加meta auth true auth 为false时不受权限控制 都会显示 后端返回有权限的menu tab button tab routes push resRoutes T
  • 复习之Linux系统中的用户管理

    1 用户及用户组的意义 在Linux中 用户 User 和用户组 Group 是管理系统权限和资源访问的重要概念 1 用户 用户是指系统中的一个身份标识 每个用户都有自己的用户名和密码 每个用户可以拥有自己的文件 进程和权限 通过用户名和密
  • 了解SpringBoot自动配置原理一

    一 自动配置原理入门 一 SpringBootApplication注解 此注解包含三个注解 SpringBootConfiguration EnableAutoConfiguration ComponentScan 一 SpringBoo
  • Ubuntu系统下多版本cuda切换

    Ubuntu系统下多版本cuda切换 操作步骤 参考链接 操作步骤 查看当前cuda软链接的指向 cd usr local stat cuda 删除旧的软链接 rm rf usr local cuda 根据需要创建新的软链接 ln s us
  • 将切片作为参数传入函数并使用append方法遇到的问题

    切片的内部结构 type SliceHeader struct Data uintptr Len int Cap int 由切片的结构定义可知 切片的结构由三个信息组成 指针Data 指向底层数组中切片指定的开始位置 长度Len 即切片的长