go语言切片做函数参数传递+append()函数扩容

go语言切片函数参数传递+append()函数扩容

给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。

二叉树递归go代码:

var ans [][]int
func pathSum(root *TreeNode, targetSum int) ( [][]int) {
	ans := make([][]int, 0) 
    path := []int{}
    dfs(root, targetSum,path)
    return ans
}
func dfs(node *TreeNode, left int,path []int) {
        if node == nil {
            return
        }
        left -= node.Val
        path = append(path, node.Val)
        if node.Left == nil && node.Right == nil && left == 0 {
            ans = append(ans, append([]int(nil), path...))//在二维切片中添加一维切片
            return
        }
        dfs(node.Left, left,path)
        dfs(node.Right, left,path)
}

我的疑惑由这道题的代码产生,可以看到在dfs递归函数中,使用了参数path切片作为变量,而学过Go的都知道切片slice是引用类型,那么在函数传递时是引用传递吗。
可以知道path记录的是目前遍历过的数据,而我的疑惑就是对切片path一直添加了数据,那么为什么作为参数传进path后,在下层递归函数中的修改不会影响到上层path,以下是我查阅许多资料的解释。

1.Go中切片的结构体

在这里插入图片描述
如图所示,切片结构体包含了三部分,第一部分是指向底层数组的指针,其次是切片的大小len和切片的容量cap。

2.切片的扩容机制

切片的容量(cap)表示切片可以使用的底层数组的最大长度。当切片的长度(len)超过了容量时,切片就会自动扩容,即分配一个更大的底层数组,并将原有的数据复制过去。这个过程是由append函数完成的,我们不需要手动操作。
根据Go语言源码中的注释,切片扩容的规则如下:

如果原始容量小于1024,则新容量是原始容量的2倍;
如果原始容量大于等于1024,则新容量是原始容量的1.25倍;
如果连续扩容5次,且没有触发上述两种情况,则新容量是原始容量的1.5倍;
如果分配失败,则触发内存溢出。

**切片在函数内部进行扩容或缩容操作时,会导致切片指向一个新的底层数组,此时在函数内部和外部就不再共享同一个底层数组了。**因此当追加超出原本容量时,再改变切片内容后,对原来的数组是没有影响的

3.Go中的append函数

在这里插入图片描述
在这里插入图片描述
如上图可以看到,如果进行append后,

  1. 切片没有进行扩容,那么会直接添加或修改切片指向底层数组中后一位的值,故底层数组会受到改变;
  2. 而如果进行扩容,则会导致切片指向一个新的底层数组,对原来的数组是没有影响的

4.Go函数传参只有值传递一种方式,传地址必须加上*——其实也是传地址变量的值

看过有些博客说Go中切片是传地址,但其实这是错误的,
Go官方文档声明:Go中函数传参只有传值,传地址必须加上*,这其实也是传地址变量的值

再回到切片作为函数参数的问题上,因为Go里面函数传参只有值传递一种方式,所以当切片作为参数时,其实也是切片的拷贝,但是在拷贝的切片中,其包含的指针成员变量的值是一样的,也就是说它们指向的底层数组数据源是一样,因此在调用函数内修改形参能影响实参。

通常,我们把在传值拷贝过程中,修改形参能直接修改实参的数据类型称为引用类型。
Go语言中所有的传参都是值传递(传值),都是一个副本,一个拷贝。

因为拷贝的内容有时候是非引用类型(int、string、struct等这些),这样就在函数中就无法修改原内容数据;有的是引用类型(指针、map、slice、chan等这些),这样就可以修改原内容数据。

这里要注意的是:引用类型和传引用是两个概念。
在这里插入图片描述
在这里插入图片描述

总结:

slice切片或者array数组作为函数参数传递的时候,本质是传值而不是传地址。因为slice依赖其底层的array,修改slice本质是修改array,而array又是有大小限制,当超过slice的容量,即数组越界的时候,需要通过动态规划的方式创建一个新的数组块。把原有的数据复制到新数组,这个新的array则为slice新的底层依赖。

传值的过程复制一个新的切片,这个切片也指向原始变量的底层数组。函数中无论是直接修改切片,还是append创建新的切片,都是基于共享切片底层数组的情况作为基础,最外面的原始切片是否改变,取决于函数内的操作和切片本身容量,是否修改了底层数组。

  • 如果要修改切片的值,那么一定对底层数组做了修改,为影响到函数外的切片
  • 如果是append操作,则要看切片是否扩容
    • 切片没有进行扩容,那么会直接添加或修改切片指向底层数组中后一位的值,故底层数组会受到改变,函数外切片改变;
    • 而如果进行扩容,则会导致切片指向一个新的底层数组,一切修改都对函数外的原切片无影响
      .

当然,如果为了修改原始变量,可以指定参数的类型为指针类型。传递的就是slice的内存地址。函数内的操作都是根据内存地址找到变量本身。

递归代码分析

func dfs(node *TreeNode, left int,path []int) {
        if node == nil {
            return
        }
        left -= node.Val
        path = append(path, node.Val)
        if node.Left == nil && node.Right == nil && left == 0 {
            ans = append(ans, append([]int(nil), path...))//在二维切片中添加一维切片
            return
        }
        dfs(node.Left, left,path)
        dfs(node.Right, left,path)
}

故在该递归函数dfs中,我们首先要知道,每次递归都是从左到右,递归完一条路径才会返回,故第一次递归时就会导致path达到叶子节点。且由path代表路径的定义,易知path的长度代表着当前遍历结点的深度
在每一次传递的参数path都是传的切片值,而不是path的地址,故都是不同的切片值,只是指向的底层数组一样,且每次在函数内部进行append时,可能扩容,也可能不扩容

  1. 如果append后导致path扩容,会导致切片指向一个新的底层数组,此时在函数内部path和外部path就不再共享同一个底层数组了,因此不会影响上层递归函数的path
  2. 如果append后没有导致path扩容,那么会直接添加或修改切片指向底层数组中后一位的值;
    • 如果是第一次遍历到当前深度,那么就会在底层数组中添加值,那么直接添加,并判断当前结点有无子树,若没有子树则加入ans中
    • 如果已经遍历过当前深度,那么应该修改底层数组的切片后一位的值,这样确实会影响path的底层数组,但对所求的答案ans没有影响,因为修改之前的path路径已经在之前的递归中添加进了ans中;对之后的递归中的path也没有影响,因为之后的path在遍历到当前深度进行append时也会修改该深度的path值

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/12675.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ActiveMQ使用(四):在JavaScript中发送的MQTT消息在SpringBoot中变为字节数组

ActiveMQ使用(四):在JavaScript中发送的MQTT消息在SpringBoot中变为字节数组 1. 问题描述 JmsListener(destination "test_producer", containerFactory "topicListenerContainer")public void receiveTestProducer(String message) throws JMSExceptio…

spring bean

图灵课堂学习笔记 1. BeanFactory与ApplicationContext的关系 p56 ApplicationContext在BeanFactory基础上对功能进行了扩展,例如:监听功能、国际化功能等。BeanFactory的API更偏向底层,ApplicationContext的API大多数是对这些底层API的封…

JVM OOM问题排查与解决思路

OOM原因 1. 堆溢出 报错信息: java.lang.OutOfMemoryError: Java heap space 代码中可能存在大对象分配,无法获得足够的内存分配 可能发生内存泄露,导致内存被无效占用以至于耗尽 2. 永久代/元空间溢出 报错信息: java.lang.O…

C#,码海拾贝(19)——一般实矩阵的QR分解(QR Decomposition)方法之C#源代码,《C#数值计算算法编程》源代码升级改进版

1 实矩阵 实矩阵,指的是矩阵中所有的数都是实数的矩阵。如果一个矩阵中含有除实数以外的数,那么这个矩阵就不是实矩阵。 2 QR(正交三角)分解法 QR(正交三角)分解法是求一般矩阵全部特征值的最有效并广泛应…

Flowable从入门到源码分析

什么是工作流? 工作流,是把业务之间的各个步骤以及规则进行抽象和概括性的描述。使用特定的语言为业务流程建模,让其运行在计算机上,并让计算机进行计算和推动。 工作流解决的痛点在于,解除业务宏观流程和微观逻辑的…

jenkins gitlab asp.net core持续集成

什么是jenkins Jenkins直接取自其官方文档,是一个独立的开源自动化服务器,您可以使用它来自动执行与构建、测试、交付或部署软件相关的各种任务。 jenkins可以干什么 Jenkins 通过自动执行某些脚本来生成部署所需的文件来工作。这些脚本称为JenkinsFi…

2023_深入学习HTML5

H5 基于html5和 css3和一部分JS API 结合的开发平台(环境) 语义化标签 header : 表示头部,块级元素 footer : 表示底部,块级元素 section :区块 nav : 表示导航链接 aside : 表示侧边栏 output &am…

二叉搜索树(BSTree)

目录 一、二叉搜索树 二、二叉搜索树的接口及实现 1、二叉搜索树的查找 2、二叉搜索树的插入 3、二叉搜索树的删除 三、二叉搜索树的递归版本 本期博客主要分享二叉搜索树的底层实现。(主要是笔记,供自己复习使用😂) 一、二叉搜索树 二叉搜索树(B…

MybatisPlus主键策略

Mybatis默认主键策略是TableId(type IdType.ASSIGN_ID) 这是默认策略雪花算法 此时主键类型可以是String 数据表字段类型可以是bigint int varchar 无需数据表主键自增 TableId(type IdType.ASSIGN_AUTO) 是主键自增策略:该策略为跟随数据库表的主键递增策略&…

一致性框架设计方案

补充组件依赖 前言 对于供应链业务,一般对数据一致性要求高。且由于业务复杂,可能会存在一个业务功能触发几个异步操作的场景,且要保证相关操作同时触发或不触发。 为了降低技术设计难度、代码编写难度,特意设计最终一致性框架&a…

ChatGPT+Ai绘图【stable-diffusion实战】

ai绘图 stable-diffusion生成【还有很大的提升空间】 提示词1 Picture a planet where every living thing is made of light. The landscapes are breathtakingly beautiful, with mountains and waterfalls made of swirling patterns of color. What kind of societies m…

程序员跳槽,要求涨薪50%过分吗?

如果问在TI行业涨工资最快的方式是什么? 回答最多的一定是:跳槽! 前段时间,知乎上这样一条帖子引发了不少IT圈子的朋友的讨论 ,有网友提问 “程序员跳槽要求涨薪50%过分吗?” 截图来源于知乎,…

摄影知识整理

目录 焦距 焦距分类 对焦 相机的MF与AF 自动对焦操作 自动对焦方式 镜头防抖 防抖模式 景深 景深的作用 影响景深的因素 景深预览 摄影三大元素 光圈 光圈的作用 光圈与景深的关系 感光度(ISO) 注意 感光度的作用 快门 B门与T门 快门速度 闪…

【SSM】SpringMVC(三:SpringMVC拦截器)

文章目录 1. 登录案例2. 拦截器2.1 应用2.2 拦截器的执行原理2.3 拦截器执行的时机2.4 拦截器的实现方法2.5 拦截器的实现步骤2.6 开发拦截器 1. 登录案例 【login.jsp】 <%--Created by IntelliJ IDEA.User: BeyongDate: 2023/4/17Time: 11:43To change this template use…

SQL的函数

文章目录 一、SQL LCASE() 函数二、SQL MID() 函数三、SQL LEN() 函数四、SQL ROUND() 函数五、SQL NOW() 函数六、SQL FORMAT() 函数总结 一、SQL LCASE() 函数 LCASE() 函数把字段的值转换为小写。 SQL LCASE() 语法 SELECT LCASE(column_name) FROM table_name;用于 SQL …

入行IC选择国企、私企还是外企?(内附各IC大厂薪资福利情况)

不少人想要转行IC&#xff0c;但不知道该如何选择公司&#xff1f;下面就来为大家盘点一下IC大厂的薪资和工作情况&#xff0c;欢迎大家在评论区补充。 一&#xff0e;老 牌 巨 头 在 IC 设计领域深耕许久&#xff0c;流程完善、技术扎实&#xff0c;公司各项制度都很完善、前…

IT知识百科:什么是暴力破解?

暴力破解是一种常见的网络安全攻击方法&#xff0c;它利用计算机程序自动尝试大量的密码组合来破解密码。这种攻击方法通常用于获取未经授权的访问权限&#xff0c;如入侵网络系统或个人账户。在本文中&#xff0c;我们将探讨暴力破解的原理、工具和防范方法。 暴力破解的原理 …

TCP/UDP协议 (详解)

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了 博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点!人生格言&#xff1a;当你的才华撑不起你的野心的时候,你就应该静下心来学习! 欢迎志同道合的朋友一起加油喔&#x1f9be;&am…

Linux搭建SVN服务器详细教程

前言 本文讲解 Linux 系统下如何搭建 SVN 服务器&#xff0c;详细说明各配置项的功能&#xff0c;最终实现可管控多个项目的复杂配置。 SVN 是 subversion 的缩写&#xff0c;是一个开放源代码的版本控制系统&#xff0c;通过采用分支管理系统的高效管理&#xff0c;实现最终集…

HANA SDA连接外部数据库到BW的步骤

咱都知道&#xff0c;我们不能直接从BW连接到外部数据库。第一步得从HANA database通过SDA去建一个到外部DB的连接。 数据库连接好了&#xff0c;那么接下来别忘了&#xff0c;还得建一个源系统。 也就是说第一步&#xff0c;我们要用HANA SDA通过Linux ODBC driver去连接外部…