[]*T *[]T *[]*T 傻傻分不清楚(下)

简介: 作为一个 Go 语言新手,看到一切”诡异“的代码都会感到好奇;比如我最近看到的几个方法;

值传递带来的误会


在上述例子中,在 appendB 中调用 append 函数追加数据后会发现 main 函数中并没有受到影响,这里我稍微调整了一下示例代码:


func TestAppendB(t *testing.T) {
  //x:=[]int{1,2,3}
  x := make([]int, 3,5)
  x[0] = 1
  x[1] = 2
  x[2] = 3
  appendB(x)
  fmt.Printf("main %v len=%v,cap=%v\n", x,len(x),cap(x))
}
func appendB(x []int) {
  x = append(x, 444)
  fmt.Printf("appendB %v len=%v,cap=%v\n", x,len(x),cap(x))
}


主要是修改了切片初始化方式,使得容量大于了长度,具体原因后续会说明。


输出结果如下:


appendB [1 2 3 444] len=4,cap=5
main [1 2 3] len=3,cap=5


main 函数中的数据看样子确实没有受到影响;但细心的朋友应该会注意到  appendB 函数中的 x 在 append() 之后长度 +1 变为了4。


而在 main 函数中长度又变回了3.


这个细节区别就是为什么 append() "看似" 没有生效的原因;至于为什么要说“看似”,再次调整了代码:


func TestAppendB(t *testing.T) {
  //x:=[]int{1,2,3}
  x := make([]int, 3,5)
  x[0] = 1
  x[1] = 2
  x[2] = 3
  appendB(x)
  fmt.Printf("main %v len=%v,cap=%v\n", x,len(x),cap(x))
  y:=x[0:cap(x)]
  fmt.Printf("y %v len=%v,cap=%v\n", y,len(y),cap(y))
}


在刚才的基础之上,以 append 之后的 x 为基础再做了一个切片;该切片的范围为 x 所引用数组的全部数据。


再来看看执行结果如何:


appendB [1 2 3 444] len=4,cap=5
main [1 2 3] len=3,cap=5
y [1 2 3 444 0] len=5,cap=5


会神奇的发现 y 将所有数据都打印出来,在 appendB 函数中追加的数据其实已经写入了数组中,但为什么 x 本身没有获取到呢?


网络异常,图片无法展示
|


看图就很容易理解了:


  • appendB中确实是对原始数组追加了数据,同时长度也增加了。


  • 但由于是值传递,所以 slice 这个结构体即便是修改了长度为4,也只是对复制的那个对象修改了长度,main 中的长度依然为3.


  • 由于底层数组是同一个,所以基于这个底层数组重新生成了一个完整长度的切片便能看到追加的数据了。


所以这里本质的原因是因为 slice 是一个结构体,传递的是值,不管方法里如何修改长度也不会影响到原有的数据(这里指的是长度和容量这两个属性)。


切片扩容


还有一个需要注意:


刚才特意提到这里的例子稍有改变,主要是将切片的容量设置超过了数组的长度;


如果不做这个特殊设置会怎么样呢?


func TestAppendB(t *testing.T) {
  x:=[]int{1,2,3}
  //x := make([]int, 3,5)
  x[0] = 1
  x[1] = 2
  x[2] = 3
  appendB(x)
  fmt.Printf("main %v len=%v,cap=%v\n", x,len(x),cap(x))
  y:=x[0:cap(x)]
  fmt.Printf("y %v len=%v,cap=%v\n", y,len(y),cap(y))
}
func appendB(x []int) {
  x = append(x, 444)
  fmt.Printf("appendB %v len=%v,cap=%v\n", x,len(x),cap(x))
}


输出结果:


appendB [1 2 3 444] len=4,cap=6
main [1 2 3] len=3,cap=3
y [1 2 3] len=3,cap=3


这时会发现 main 函数中的 y 切片数据也没有发生变化,这是为什么呢?


网络异常,图片无法展示
|


这是因为初始化 x 切片时长度和容量都为3,当在 appendB 函数中追加数据时,会发现没有位置了。


这时便会进行扩容:


  • 将老数据复制一份到新的数组中。


  • 追加数据。


  • 将新的数据内存地址返回给 appendB 中的 x .


同样的由于是值传递,所以 appendB 中的切片换了底层数组对 main 函数中的切片没有任何影响,也就导致最终 main 函数的数据没有任何变化了。


传递切片指针


有没有什么办法即便是在扩容时也能对外部产生影响呢?


func TestAppendC(t *testing.T) {
  x:=[]int{1,2,3}
  appendC(&x)
  fmt.Printf("main %v len=%v,cap=%v\n", x,len(x),cap(x))
}
func appendC(x *[]int) {
  *x = append(*x, 4)
  fmt.Printf("appendC %v\n", x)
}


输出结果为:


appendC &[1 2 3 4]
main [1 2 3 4] len=4,cap=6


这时外部的切片就能受到影响了,其实原因也很简单;


刚才也说了,因为 slice 本身是一个结构体,所以当我们传递指针时,就和平时自定义的 struct 在函数内部通过指针修改数据原理相同。


最终在 appendC 中的 x 的指针指向了扩容后的结构体,因为传递的是 main 函数中 x 的指针,所以同样的 main 函数中的 x 也指向了该结构体。


总结


所以总结一下:


  • 切片是对数组的抽象,同时切片本身也是一个结构体。


  • 参数传递时函数内部与外部引用的是同一个数组,所以对切片的修改会影响到函数外部。


  • 如果发生扩容,情况会发生变化,同时扩容会导致数据拷贝;所以要尽量预估切片大小,避免数据拷贝。


  • 对切片或数组重新生成切片时,由于共享的是同一个底层数组,所以数据会互相影响,这点需要注意。


  • 切片也可以传递指针,但场景很少,还会带来不必要的误解;建议值传值就好,长度和容量占用不了多少内存。


相信使用过切片会发现非常类似于  Java  中的 ArrayList,同样是基于数组实现,也会扩容发生数据拷贝;这样看来语言只是上层使用的选择,一些通用的底层实现大家都差不多。


这时我们再看标题中的 []*T *[]T *[]*T 就会发现这几个并没有什么联系,只是看起来很像容易唬人。


相关文章
|
存储 对象存储
使用Ceph对象存储的Amazon S3接口(基于nautilus版本)
使用Ceph对象存储的Amazon S3接口(基于nautilus版本)
1153 0
|
自然语言处理 负载均衡 Kubernetes
分布式系统架构2:服务发现
服务发现是分布式系统中服务实例动态注册和发现机制,确保服务间通信。主要由注册中心和服务消费者组成,支持客户端和服务端两种发现模式。注册中心需具备高可用性,常用框架有Eureka、Zookeeper、Consul等。服务注册方式包括主动注册和被动注册,核心流程涵盖服务注册、心跳检测、服务发现、服务调用和注销。
633 13
|
存储 JSON NoSQL
redis基本数据结构(String,Hash,Set,List,SortedSet)【学习笔记】
这篇文章是关于Redis基本数据结构的学习笔记,包括了String、Hash、Set、List和SortedSet的介绍和常用命令。文章解释了每种数据结构的特点和使用场景,并通过命令示例演示了如何在Redis中操作这些数据结构。此外,还提供了一些练习示例,帮助读者更好地理解和应用这些数据结构。
redis基本数据结构(String,Hash,Set,List,SortedSet)【学习笔记】
|
SQL 分布式计算 DataWorks
DataWorks产品使用合集之如何在SQL语句里使用CASE WHEN语句
DataWorks作为一站式的数据开发与治理平台,提供了从数据采集、清洗、开发、调度、服务化、质量监控到安全管理的全套解决方案,帮助企业构建高效、规范、安全的大数据处理体系。以下是对DataWorks产品使用合集的概述,涵盖数据处理的各个环节。
534 2
|
分布式计算 物联网 虚拟化
《深度揭秘:软总线如何实现异构网络组网及其独特优势》
软总线技术是解决异构网络组网难题的关键力量,通过协议抽象与归一化、总线中枢智能解析、动态拓扑构建及设备虚拟化管理等方式,实现高效互联。它降低开发复杂度,提升设备兼容性与扩展性,保障数据传输的高效性与稳定性,为万物互联提供坚实支撑。在智能家居、智能工厂和交通等领域,软总线展现出强大优势,推动多设备协同与智能化发展,助力数字化转型与未来创新。
625 3
|
机器学习/深度学习 编解码 自然语言处理
视频字幕生成案例
8月更文挑战第3天
1119 0
|
人工智能 自动驾驶 安全
人工智能的最终目标:超越人类智能的未来
人工智能(AI)已经成为当今世界最引人注目的技术领域之一,其应用范围涵盖了从医疗保健到自动驾驶汽车的各个领域。然而,尽管AI在许多任务上已经表现出惊人的能力,但其最终目标是什么?这是一个备受争议的问题,但大多数研究人员和科技领袖都同意,人工智能的最终目标是超越人类智能。
|
存储 安全 网络协议
操作系统的心脏:内核探秘
在数字世界的每一次跳动中,都有一个不为人知的英雄在默默支撑着整个系统的运行。它就像是一台精密的时钟,虽不见其形,却能感知到它的存在和重要性。这篇文章将带你走进操作系统最为核心的部分——内核,探索它的设计哲学、主要功能以及它如何影响我们日常的数字生活。准备好了吗?让我们一同揭开操作系统内核的神秘面纱。
|
供应链 算法 数据挖掘
一文看懂:销售数据分析怎么做?
今天跟大家分享数据分析里最高频的一个工作:销售分析。不管是实打实挣钱的公司,还是指望上市圈钱的公司,销售业绩都是领导们最看重的指标。 很多人从事数据分析工作,也是从基础的“销售统计专员”做起的。今天就简单分享下,销售分析该如何做。
1329 0
一文看懂:销售数据分析怎么做?
|
缓存 监控 Java
优化Spring Boot应用的数据库访问性能
优化Spring Boot应用的数据库访问性能