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

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

前言


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


func FindA() ([]*T,error) {
}
func FindB() ([]T,error) {
}
func SaveA(data *[]T) error {
}
func SaveB(data *[]*T) error {
}


相信大部分刚入门 Go 的新手看到这样的代码也是一脸懵逼,其中最让人疑惑的就是:


[]*T
*[]T
*[]*T


这样对切片的声明,先不看后面两种写法;单独看 []*T 还是很好理解的: 该切片中存放的是所有 T 的内存地址,会比存放 T 本身来说要更省空间,同时 []*T 在方法内部是可以修改 T 的值,而[]T 是修改不了。


func TestSaveSlice(t *testing.T) {
  a := []T{{Name: "1"}, {Name: "2"}}
  for _, t2 := range a {
    fmt.Println(t2)
  }
  _ = SaveB(a)
  for _, t2 := range a {
    fmt.Println(t2)
  }
}
func SaveB(data []T) error {
  t := data[0]
  t.Name = "1233"
  return nil
}
type T struct {
  Name string
}


比如以上例子打印的是


{1}
{2}
{1}
{2}


只有将方法修改为


func SaveB(data []*T) error {
  t := data[0]
  t.Name = "1233"
  return nil
}


才能修改 T 的值:


&{1}
&{2}
&{1233}
&{2}


示例


下面重点来看看 []*T*[]T 的区别,这里写了两个 append 函数:


func TestAppendA(t *testing.T) {
  x:=[]int{1,2,3}
  appendA(x)
  fmt.Printf("main %v\n", x)
}
func appendA(x []int) {
  x[0]= 100
  fmt.Printf("appendA %v\n", x)
}


先看第一种,输出是结果是:


appendA [1000 2 3]
main [1000 2 3]


说明在函数传递过程中,函数内部的修改能够影响到外部。


下面我们再看一个例子:


func appendB(x []int) {
  x = append(x, 4)
  fmt.Printf("appendA %v\n", x)
}


最终结果却是:


appendA [1 2 3 4]
main [1 2 3]


没有影响到外部。


而当我们再调整一下会发现又有所不同:



最终的结果:


appendA &[1 2 3 4]
main [1 2 3 4]


可以发现如果传递切片的指针时,使用 append 函数追加数据时会影响到外部。


slice 原理


在分析上面三种情况之前,我们先来了解下 slice 的数据结构。


直接查看源码会发现 slice 其实就是一个结构体,只是不能直接对外访问。


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


源码地址 runtime/slice.go


其中有三个重要的属性:


属性 含义
array 底层存放数据的数组,是一个指针。
len 切片长度
cap 切片容量 cap>=len


提到切片就不得不想到数组,可以这么理解:


切片是对数组的抽象,而数组则是切片的底层实现。


其实通过切片这个名字也不难看出,它就是从数组中切了一部分;相对于数组的固定大小,切片可以根据实际使用情况进行扩容。


所以切片也可以通过对数组"切一刀"获得:


x1:=[6]int{0,1,2,3,4,5}
x2 := x[1:4]
fmt.Println(len(x2), cap(x2))


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


其中 x1 的长度与容量都是6。


x2 的长度与容量则为3和5。


  • x2 的长度很容易理解。


  • 容量等于5可以理解为,当前这个切片最多可以使用的长度。


因为切片 x2 是对数组 x1 的引用,所以底层数组排除掉左边一个没有被引用的位置则是该切片最大的容量,也就是5。


同一个底层数组


以刚才的代码为例:


func TestAppendA(t *testing.T) {
  x:=[]int{1,2,3}
  appendA(x)
  fmt.Printf("main %v\n", x)
}
func appendA(x []int) {
  x[0]= 100
  fmt.Printf("appendA %v\n", x)
}


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


在函数传递过程中,main 中的 x 与 appendA 函数中的 x 切片所引用的是同个数组。

所以在函数中对 x[0]=100main函数中也能获取到。


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


本质上修改的就是同一块内存数据。



相关文章
|
6月前
|
移动开发 C语言
C语言中的#和##你还傻傻分不清楚吗
简单介绍两者的用途并以代码的形式举例
|
存储 安全 Python
python多线程------>这个玩意很哇塞,你不来看看吗
python多线程------>这个玩意很哇塞,你不来看看吗
|
存储 编译器 C语言
带你们偷瞄编程绕不开的C语言(三)
带你们偷瞄编程绕不开的C语言(三)
76 0
|
存储 Java
i++和++i傻傻分不清楚?这里给你最清楚的解答
i++和++i傻傻分不清楚?这里给你最清楚的解答
i++和++i傻傻分不清楚?这里给你最清楚的解答
|
Java 编译器 C#
带你们偷瞄编程绕不开的C语言(一)
带你们偷瞄编程绕不开的C语言(一)
177 0
|
C语言
带你们偷瞄编程绕不开的C语言(二)
带你们偷瞄编程绕不开的C语言(二)
49 0
|
IDE 开发工具 Python
这样的奇技淫巧,劝你不用也罢
这样的奇技淫巧,劝你不用也罢
139 0
有点迷糊的题
2541. 使数组中所有元素相等的最小操作数 II - 力扣(LeetCode)
72 0
|
设计模式 架构师 Java
为什么有些蛮厉害的人,后来都不咋样了
写这篇文章目的是之前在一篇文章中谈到,我实习那会有个老哥很牛皮,业务能力嘎嘎厉害,但是后面发展一般般,这引起我的思考,最近有个同事发了篇腾讯pcg的同学关于review 相关的文章,里面也谈到架构师的层次,也再次引起我关于架构师的相关思考,接下来我们展开聊聊吧~
157 0