将切片作为参数传入函数并使用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 常见数据结构实现原理之 反射

    目录 一 golang 是如何实现反射的 如何比较两个对象完全相等 一 golang 是如何实现反射的 参考博客Go 语言问题集 Go Questions Go 语言在 reflect 包里定义了各种类型 实现了反射的各种函数 通过它们可以
  • 服务计算hw7

    任务目标 设计一个 web 小应用 展示静态文件服务 js 请求支持 模板输出 表单处理 Filter 中间件设计等方面的能力 不需要数据库支持 基本要求 支持静态文件服务 支持简单 js 访问 提交表单 并输出一个表格 对 unknown
  • golang之跨语言ipc通信

    1 golang之跨语言ipc通信 文章目录 1 golang之跨语言ipc通信 1 1 unix domain Socket unix域套接字 介绍 1 2 IPC SOCKET通信 1 2 1 函数及地址定义介绍 1 2 2 UNIX
  • Golang-使用 goroutine 运行闭包的“坑”

    介绍 在 Go 语言中 函数支持匿名函数 闭包就是一种特殊的匿名函数 它可以用于访问函数体外部的变量 需要注意的是 在 for range 中 使用 goroutine 执行闭包时 经常会掉 坑 因为匿名函数可以访问函数体外部的变量 而 f
  • Go_关键字、编译、转义字符

    关键字 关键字是指被go语言赋予了特殊含义的单词 共25个 关键字不能用于自定义名字 只能在特定语法结构中使用 break default func interface select case defer go map struct cha
  • Golang连接Jenkins获取Job Build状态及相关信息

    文章目录 1 连接Jenkins 2 controller 3 module 4 router 5 效果展示 第三方包 gojenkins 方法文档 gojenkins docs 实现起来很简单 利用第三方库 连接jenkins 调用相关方
  • Go语言包管理(一)

    Go语言中的包 我们在使用其他语言 比如Java Python 都有类似包的概念 Go也不例外 其核心思想即为分组和模块化 人的大脑对庞大和复杂的事情很难掌控 可以对其采用分而治之的策略 使其模块化 从而更容易管理 如下是标准库中net包的
  • Go_接口、多态、接口继承、空接口、类型断言

    接口 接口是把所有具有共性的方法定义在一起 是方法集 任何类型实现了接口中所有的方法 就是实现了这个接口 接口可以实现多态 接口传递的是地址值 接口定义及调用 定义格式 tepe 接口名 interface 方法名 参数 返回值 调用格式1
  • Golang三剑客之Pflag、Viper、Cobra

    如何构建应用框架 想知道如何构建应用框架 首先你要明白 一个应用框架包含哪些部分 在我看来 一个应用框架需要包含以下 3 个部分 命令行参数解析 主要用来解析命令行参数 这些命令行参数可以影响命令的运行效果 配置文件解析 一个大型应用 通常
  • 深入理解 Go 语言中的接口(interface)

    一 GoLang 接口的定义 1 GoLang 中的接口 在 Go 语言中接口 interface 是一种类型 一种抽象的类型 接口 interface 定义了一个对象的行为规范 只定义规范不实现 由具体的对象来实现规范的细节 实现接口的条
  • 有哪些不错的 Golang 开源项目?

    目前人在字节做 Go 开发 寻找 Golang 开源项目学习目的可能是 想学习或者提高自己对 Go 项目的组织和编排能力 想学习 Go 项目的框架设计 想在一些 Go 语法上细节的优化和进阶 我推荐两个项目 一 tinode 这是一个开源的
  • 48.Go简要实现令牌桶限流与熔断器并集成到Gin框架中

    文章目录 一 简介 二 限流器与熔断器在微服务中的作用 1 限流器 对某个接口单位时间内的访问量做限制 2 熔断器 当服务连续报错 超过一定阈值时 打开熔断器使得服务不可用 三 具体实现 1 限流器实现逻辑 以令牌桶算法为例 2 限流器集成
  • go-zero开发入门之网关往rpc服务传递数据2

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

    开发一个 API 网关 代理 https blog csdn net Aquester article details 134856271 中的 RPC 服务 网关完整源代码 file main go package main import
  • go-zero 的 etcd 配置

    实现代码在 core discov config go 文件中 type EtcdConf struct Hosts string Key string ID int64 json optional User string json opt
  • GoLong的学习之路,进阶,微服务之序列化协议,Protocol Buffers V3

    这章是接上一章 使用 RPC包 序列化中没有详细去讲 因为这一块需要看的和学习的地方很多 并且这一块是RPC中可以说是最重要的一块 也是性能的重要影响因子 今天这篇主要会讲其使用方式 文章目录 Protocol Buffers V3 背景以
  • GoLong的学习之路,进阶,Viper(yaml等配置文件的管理)

    本来有今天是继续接着上一章写微服务的 但是这几天有朋友说 再写Web框架的时候 遇到一个问题 就是很多的中间件 redis 微信 mysql mq 的配置信息写的太杂了 很不好管理 希望我能写一篇有管理配置文件的 所以这篇就放到今天写吧 微
  • 【golang】go执行shell命令行的方法( exec.Command )

    所需包 import os exec cmd 的用法 cmd exec Command ls lah ls是命令 后面是参数 e cmd Run 多个参数的要分开传入 如 ip link show bond0 cmd
  • go开发--操作mysql数据库

    在 Go 中访问 MySQL 数据库并进行读写操作通常需要使用第三方的 MySQL 驱动 Go 中常用的 MySQL 驱动有 github com go sql driver mysql 和 github com go xorm xorm
  • [每周一更]-(第55期):Go的interface

    参考地址 https juejin cn post 6978322067775029261 https gobyexample com interfaces https go dev tour methods 9 介绍下Go的interfa

随机推荐

  • 【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 即切片的长