我的编程空间,编程开发者的网络收藏夹
学习永远不晚

golang中的[]*T、*[]T和*[]*T分别是什么

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

golang中的[]*T、*[]T和*[]*T分别是什么

这篇文章主要讲解了“golang中的[]*T、*[]T和*[]*T分别是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“golang中的[]*T、*[]T和*[]*T分别是什么”吧!

最近看到一段十分诡异的代码,包含了“[]*T”“*[]T”和“*[]*T”,乍一看都是一样的,但我们仔细观察就发现他们的不同之处。今天我们就来介绍一下golong的“[]*T”“*[]T”和“*[]*T”,了解一下他们之间的不同,一起来看看

作为一个 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]

没有影响到外部。

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

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

最终的结果:

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]=100,main函数中也能获取到。

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

值传递带来的误会

在上述例子中,在 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=5main [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=5main [1 2 3] len=3,cap=5y [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=6main [1 2 3] len=3,cap=3y [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 就会发现这几个并没有什么联系,只是看起来很像容易唬人。

感谢各位的阅读,以上就是“golang中的[]*T、*[]T和*[]*T分别是什么”的内容了,经过本文的学习后,相信大家对golang中的[]*T、*[]T和*[]*T分别是什么这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

golang中的[]*T、*[]T和*[]*T分别是什么

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

golang中的[]*T、*[]T和*[]*T分别是什么

这篇文章主要讲解了“golang中的[]*T、*[]T和*[]*T分别是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“golang中的[]*T、*[]T和*[]*T分别是什么”吧!最近看
2023-06-20

Java 中super T和extends T的区别是什么

Java 中super T和extends T的区别是什么,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。extendsList< extends Number> foo3的通配
2023-06-17

java中什么是T?

java中什么是T?T是Java泛型中的一个标记符号,代表Type(Java 类)。Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就
java中什么是T?
2020-03-15

c++中\n和\t有什么区别

c++kquote>\n和\t的区别:换行符 (\n):添加新行,将光标移动到下一行。制表符 (\t):添加水平制表符,将光标移动到下一个制表符位置(默认间隔 8 个字符)。c++中\n和\t的区别\n 和 \t 是 C++ 中的两个转
c++中\n和\t有什么区别
2024-05-01

c++中/n和/t的区别

c++ 中 \n 为换行符,将光标移至下一行的开头;\t 为制表符,将光标移至下一个制表位。它们用于格式化输出,\n 创建新行,\t 给文本缩进。C++ 中 \n 和 \t 的区别在 C++ 中,\n 和 \t 是转义序列,它们表示特殊字
c++中/n和/t的区别
2024-05-01

python中的T检验是什么

这篇文章主要介绍“python中的T检验是什么”,在日常操作中,相信很多人在python中的T检验是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”python中的T检验是什么”的疑惑有所帮助!接下来,请跟
2023-06-20

java泛型中T和?的区别

T 代表一种类型。加在类上:class SuperClass{}加在方法上:public void fromArrayToCollection(T[] a, Collection c){}(免费学习视频教程分享:java视频教程)方法上的代表括号里面要用到泛型
java泛型中T和?的区别
2014-10-15

vue项目中$t()指的是什么

本篇内容主要讲解“vue项目中$t()指的是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“vue项目中$t()指的是什么”吧!vue中$t()的意思一般项目vue文件中将会把中文字符的数量拉
2023-06-30

SQLServer中的T-SQL是什么意思

T-SQL是Transact-SQL的简称,它是SQLServer中的扩展语言,用于管理和操作数据库。T-SQL包含了SQL的基本语法,同时还包含了一些扩展功能,如存储过程、触发器、函数等。通过T-SQL,用户可以执行各种数据库操作,如查询
SQLServer中的T-SQL是什么意思
2024-04-09

Java泛型中<?>和<T>的区别浅析

<T>和<?>的区别<T>是参数类型,常常用于泛型类或泛型方法的定义,下面这篇文章主要给大家介绍了关于Java泛型中<?>和<T>区别的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
2022-12-19

Java泛型中T和问号的区别

本篇内容主要讲解“Java泛型中T和问号的区别”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java泛型中T和问号的区别”吧!类型本来有:简单类型和复杂类型,引入泛型后把复杂类型分的更细了.概述
2023-05-30

WF4.0 Beta2中的Switch<T>是什么

这篇文章将为大家详细讲解有关WF4.0 Beta2中的Switch是什么,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。对于微软的WF工作流,很多开发人员都有过接触。对于新版的WF4.0 Beta2,
2023-06-17

java中\t,\n,\r,\b,\f的作用是什么

本篇内容主要讲解“java中\t,\n,\r,\b,\f的作用是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“java中\t,\n,\r,\b,\f的作用是什么”吧!\t,\n,\r,\b,
2023-07-02

Java泛型中T、E、K、V含义是什么

这篇文章主要介绍了Java泛型中T、E、K、V含义是什么的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Java泛型中T、E、K、V含义是什么文章都会有所收获,下面我们一起来看看吧。泛型是Java中一个非常重要的
2023-06-16

SAP MM T-code MD04的使用前提是什么

今天就跟大家聊聊有关SAP MM T-code MD04的使用前提是什么,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。SAP MM T-code MD04的使用,是有前提的!笔者所在
2023-06-05

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录