V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
frankphper
V2EX  ›  Go 编程语言

Go 语言为什么建议多使用切片,少使用数组?

  •  
  •   frankphper · 113 天前 · 3577 次点击
    这是一个创建于 113 天前的主题,其中的信息可能已经有所发展或是发生改变。

    大家好,我是 frank ,「 Golang 语言开发栈」公众号作者。

    01 介绍

    在 Go 语言中,数组固定长度,切片可变长度;数组和切片都是值传递,因为切片传递的是指针,所以切片也被称为“引用传递”。

    读者朋友们在使用 Go 语言开发项目时,或者在阅读 Go 开源项目源码时,发现很少使用到数组,经常使用到切片。

    本文通过讲解 Golang 切片的一些特性,介绍 Go 语言为什么建议多使用切片,少使用数组。

    02 切片

    切片的底层是数组,它是可变长度,可以在容量不足时自动扩容。

    type SliceHeader struct {
    	Data uintptr
    	Len  int
    	Cap  int
    }
    

    阅读上面这段代码,SliceHeader 结构体是切片在运行时的表现,由 3 部分组成,分别是指向底层数组的指针 Data,长度 Len 和容量 Cap

    声明方式

    切片的声明方式有多种,分别是:

    var s1 []int
    var s2 []int{1, 2, 3}
    var s3 []int = make([]int, 3)
    var s4 []int = make([]int, 3, 5)
    

    阅读上面这段代码,s1 只声明未初始化,值为 nils2 字面量初始化,编译时会自动推断切片的长度,容量与长度相同;

    s3 声明切片,并使用内置函数 make 初始化切片的长度为 3,因为未指定容量,所以容量与长度相同;s4 声明切片,并使用内置函数 make 初始化切片的长度为 3,切片的容量为 5,容量必须大于等于长度。

    字面量初始化与使用内置函数 make 初始化的区别是,字面量初始化,编译时在数据区创建一个数组,并在堆区创建一个切片,程序启动时将数据区的数据复制到堆区;

    使用内置函数 make 初始化,编译时根据切片大小判断分配到栈区,还是分配到堆区,小于 64KB 则分配到栈区,大于等于 64KB 则分配到堆区。

    数组则是根据数组长度判定是否在栈区初始化,数组长度小于 4 时,编译时在栈区初始化数组。

    “引用传递”

    数组和切片在作为函数参数传递时,属于值传递,如果使用数组,特别是大数组时,我们需要特别小心,可以考虑使用数组指针;如果使用切片,本身就是拷贝的内存地址,所以切片也被称为“引用传递”。

    自动扩容

    切片可以使用内置函数 append 追加元素到切片,如果原切片容量不足时,切片可以自动扩容;数组是固定长度,如果数组长度不足时,编译时则报错,或者只能声明一个新数组,并将旧数组中的数据拷贝到新数组。

    需要注意的是,虽然使用内置函数 append 追加元素,当切片容量不足时可以自动扩容切片,但是会涉及到内存分配,原切片容量小于 1024 ,新切片容量是原切片容量的 2 倍;

    如果原切片容量大于等于 1024 ,新切片容量按照原切片容量的 1/4 步长循环扩容,直到新切片的容量大于等于新切片的长度为止。

    03 总结

    本文我们介绍 Go 语言为什么建议多使用切片,少使用数组。

    主要是因为切片值传递的成本更低,更加适合作为函数参数,并且使用内置函数 append 追加切片元素时,当切片容量不足时可以自动扩容。

    需要注意的是,虽然切片可以自动扩容,但在扩容时会涉及内存分配,造成系统开销,尽量在创建切片时,预估出切片的最终容量。

    18 条回复    2024-01-07 12:46:34 +08:00
    0o0O0o0O0o
        1
    0o0O0o0O0o  
       113 天前   ❤️ 7
    /t/1004805 我觉得你 append 就行了。而且如果你只是把 V2EX 当作又一个输出你公众号的平台,并不跟之前回复的人讨论,我感觉你可能会挨骂。
    kneo
        2
    kneo  
       113 天前 via Android
    怎么看着像 ai 写的。
    wusheng0
        3
    wusheng0  
       113 天前 via Android
    > 数组和切片在作为函数参数传递时,属于值传递

    > 如果使用切片,本身就是拷贝的内存地址,所以切片也被称为“引用传递”

    什么意思?
    值传递是函数传参按值,这里的引用传递也是是函数传参吗?
    xdeng
        4
    xdeng  
       112 天前
    数组和切片都是值传递,因为切片传递的是指针,所以切片也被称为“引用传递”。 ??? 啥意思?切片到底是值传递 还是引用传递?
    frankphper
        5
    frankphper  
    OP
       112 天前
    @0o0O0o0O0o APPEND 不太好找对应主题
    frankphper
        6
    frankphper  
    OP
       112 天前
    @0o0O0o0O0o APPEND 不太好找对应主题
    @wusheng0 带引号的引用传递,因为虽然是值传递,但是传递的是指向数组的内存地址。
    frankphper
        7
    frankphper  
    OP
       112 天前
    @xdeng 值传递,这里说的是带引号的引用传递
    Vegetable
        8
    Vegetable  
       112 天前
    @xdeng
    @wusheng0 因为切片本身就是一种底层数组的指针,所以传递切片的值,相当于传递底层数组的指针。这块他确实没说明白,我觉得这个文章还需要打磨。
    xiangyuecn
        9
    xiangyuecn  
       112 天前
    跟 php 的数组一个尿性,把屎当宝 he tui~
    EminemW
        10
    EminemW  
       112 天前
    因为大部分场景是不固定长度的啊。真没多少场景是能提前知道需要多大的数组
    Exxfire
        11
    Exxfire  
       112 天前
    "需要注意的是,虽然切片可以自动扩容,但在扩容时会涉及内存分配,造成系统开销,尽量在创建切片时,预估出切片的最终容量。"
    这个切片的扩容和使用数组时重新分配一个更大的数组再拷贝一遍的花销相比怎么样?
    另外, 从一个 初级 C 程序员的角度看来, 所有高级语言的这些特性都是华而不实的...
    nagisaushio
        12
    nagisaushio  
       112 天前 via Android
    @Exxfire 扩容的本质就是你说的这样,只不过切片的自动扩容会比较科学的帮你选取新数组的大小
    GeekGao
        13
    GeekGao  
       112 天前
    没我的 AI 助手写的简洁,我分享一下它的回答:
    +++

    Go 语言建议多使用切片,少使用数组,主要是因为切片具有更大的灵活性和功能。以下是主要的原因:

    1.动态长度:数组在定义时需要指定长度和元素类型,长度是固定的,不能动态改变。而切片是一个长度可变的数据类型,其长度在定义时可以为空,也可以指定一个初始长度。这使得切片在处理长度未知或可能变化的情况时更为方便 。

    2.动态内存分配:数组的内存空间是在定义时分配的,其大小是固定的。而切片的内存空间是在运行时动态分配的,其大小是可变的。这使得切片在处理需要动态增长的数据时更为方便 。

    3.函数参数传递:当数组作为函数参数时,函数操作的是数组的一个副本,不会影响原始数组。而当切片作为函数参数时,函数操作的是切片的引用,会影响原始切片。这使得切片在处理需要在函数内部修改数据的情况时更为方便 。

    4.容量概念:切片还有容量的概念,它指的是分配的内存空间。这使得切片在处理需要管理内存使用的情况时更为方便 。

    5.多维切片:切片可以通过分片的分片(或者切片的数组),长度可以任意动态变化,所以 Go 语言的多维切片可以任意切分。而且,内层的切片必须单独分配(通过 make 函数)。这使得切片在处理多维数据时更为方便 2 。

    因此,Go 语言建议多使用切片,少使用数组,主要是因为切片具有更大的灵活性和功能。
    SaltyKitkat
        14
    SaltyKitkat  
       112 天前
    @Exxfire 起码泛型,模块和自动内存管理是好东西
    ShuWei
        15
    ShuWei  
       112 天前
    一句话能说清楚,非要啰啰嗦嗦一长篇,还没写清楚,你用的这个 ai 明显水平还不到家呀
    lijiangang886
        16
    lijiangang886  
       112 天前
    因为大道智减
    herozzm
        17
    herozzm  
       112 天前 via iPhone
    同样的帖子 你发了几遍了吧
    xuanbg
        18
    xuanbg  
       112 天前
    我也来贴个 AI 的
    有几个原因为什么 Go 使用切片而不是数组。

    * **切片更高效。** 当你创建一个切片时,Go 不需要复制底层数组的所有元素。这是因为切片只是对底层数组的引用,切片的元素只有在修改时才会被复制。
    * **切片更灵活。** 你可以创建任何长度的切片,并且你可以随时更改切片的长度。这在数组中是不可能的,数组必须创建一个特定的长度。
    * **切片更方便。** 创建和使用切片比数组更容易。这是因为切片在创建时不需要指定数组的长度。

    因此,Go 使用切片而不是数组。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5320 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 45ms · UTC 05:45 · PVG 13:45 · LAX 22:45 · JFK 01:45
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.