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

为什么在 Go 数组(array)被设计成值,而不跟 C/C++或 Java 一样,设计为一个引用?

  •  
  •   frankhuu · 2020-05-19 10:32:36 +08:00 · 6624 次点击
    这是一个创建于 1684 天前的主题,其中的信息可能已经有所发展或是发生改变。
    最近在入门学习 Go 中的 array 和 slice,能初步理解两者的区别,但是总是差一点意思,为什么数组要设计为值?为什么 Go 不能像 Java 等其他语言一样没有 slice,slice 究竟有多大的威力?
    42 条回复    2020-05-23 19:43:16 +08:00
    fighterlyt
        1
    fighterlyt  
       2020-05-19 10:34:30 +08:00
    slice 彻底突破了 array 的概念
    piglovesx
        2
    piglovesx  
       2020-05-19 10:37:10 +08:00
    建议用 ArrayList 和 slice 进行比较,底层都是 array 。
    ethego
        3
    ethego  
       2020-05-19 10:40:06 +08:00
    C 的 array 就是值啊
    thet
        4
    thet  
       2020-05-19 10:40:27 +08:00
    slice 可变长,数组日常使用起来肯定不方便
    mornlight
        5
    mornlight  
       2020-05-19 10:49:49 +08:00
    array 固定长度,一旦初始化分配好,就固定占用那么多内存空间,是连续的,不能缩短不能加长。
    slice 帮你实现了动态伸缩数组的需求。其他语言肯定也有类似的结构。
    mornlight
        6
    mornlight  
       2020-05-19 10:51:05 +08:00
    建议在 Go 里面不要使用「引用」这个词,把自己搞晕了。任何地方都是传值。
    Vegetable
        7
    Vegetable  
       2020-05-19 10:55:28 +08:00
    array 是底层设计,Go 也一直在暗示“不要用 Array”。所有你需要所谓引用的地方,都应该用切片。
    nightwitch
        8
    nightwitch  
       2020-05-19 11:02:18 +08:00
    c/c++的数组是值类型
    mikurasa
        9
    mikurasa  
       2020-05-19 11:03:21 +08:00
    slice 和 map 的底层都是 array map 的底层实现是桶+array
    Jirajine
        10
    Jirajine  
       2020-05-19 11:44:09 +08:00 via Android
    slice 就是其他语言的 vector,连续内存,智能动态伸缩,牺牲一点点微不足道的性能和内存占用。
    mightofcode
        11
    mightofcode  
       2020-05-19 11:44:33 +08:00
    go 语言的 slice 和 array 有心智包袱,容易让人混乱
    ica10888
        12
    ica10888  
       2020-05-19 15:19:38 +08:00
    因为没有泛型...
    xhp281
        13
    xhp281  
       2020-05-19 15:27:33 +08:00
    @mornlight 我看有的网课讲传地址的速度更快一些
    janxin
        14
    janxin  
       2020-05-19 15:30:17 +08:00
    因为 slice 不是 array
    keepeye
        15
    keepeye  
       2020-05-19 15:35:11 +08:00   ❤️ 1
    python list:

    a = [1,2,3]
    b = a[:]
    b[0] = 0
    print(a) # [1,2,3]
    print(b) # [0,2,3]

    go slice:
    a := []int{1,2,3}
    b := a[:]
    b[0] = 0
    fmt.Println(a) // [0, 2, 3]

    这就是区别了
    Rwing
        16
    Rwing  
       2020-05-19 16:03:18 +08:00
    我来个 C#的

    var a = new[] { 1, 2, 3 };
    var b = a[..];
    b[0] = 0;
    Console.WriteLine(a); // [1,2,3]
    Console.WriteLine(b); // [0,2,3]
    hahasong
        17
    hahasong  
       2020-05-19 16:17:41 +08:00
    slice 就是引用的,尽量用 slice
    qW7bo2FbzbC0
        18
    qW7bo2FbzbC0  
       2020-05-19 16:27:20 +08:00
    @mornlight 这个怎么理解,很多文章都说是&取地址 *取值
    mornlight
        19
    mornlight  
       2020-05-19 16:51:28 +08:00
    @hjahgdthab750 #18 &取地址 这个描述没问题。取出来之后你赋值过去的就是一个指针类型的值,传参本质上是传递「指针值」,理解清楚了指针的概念这些就都简单了。

    slice 经常被说成「引用类型」是因为它本质上是这样的 struct:
    type slice struct {
    array unsafe.Pointer
    len int
    cap int
    }

    传递 slice 等同于传递这样的一个 struct 值,只是值里面有一个指向底层 array 的指针,所以真正存放内容的 array 部分没有被 copy 。如果直接传一个 array 值,里面存放的内容是会被 copy 一次的。
    Hellert
        20
    Hellert  
       2020-05-19 16:54:00 +08:00
    go 全是值类型
    mornlight
        21
    mornlight  
       2020-05-19 16:57:27 +08:00
    @xhp281 #13 值的体积比较大时,的确传指针会快一些,具体看场景。小值传指针可能会加重 GC 负担。
    传参用 value 还是 pointer,官方有建议的做法: https://github.com/golang/go/wiki/CodeReviewComments#receiver-type
    cmdOptionKana
        22
    cmdOptionKana  
       2020-05-19 17:01:19 +08:00
    主要是因为 Go 有指针。

    没有指针的语言才需要弄出一个什么引用的概念,有指针就不需要引用这个概念了呀。
    Threeinchtime
        23
    Threeinchtime  
       2020-05-19 18:07:21 +08:00   ❤️ 1
    Go 语言只有值传递,channel,slice,map 是引用类型,其余是值类型。
    传值类型拷贝内存,传引用类型拷贝指针。
    jaylee4869
        24
    jaylee4869  
       2020-05-19 18:18:16 +08:00
    巧了我今天刚开始看 Go,碰巧也看到这里了
    CreSim
        25
    CreSim  
       2020-05-19 18:30:24 +08:00 via Android
    主要需要考虑的是栈上分配还是堆上分配吧,array 在栈上,slice 在堆上
    wysnylc
        26
    wysnylc  
       2020-05-19 18:40:38 +08:00
    slice 和 ArrayList 等价,底层都是数组,在数组超出长度的时候创建新的数组把原来的复制过去
    ica10888
        27
    ica10888  
       2020-05-19 18:57:21 +08:00 via Android
    前面没解释清楚,我这里详细说一下
    array 和 slice 是两个完全不一样的东西,slice 的底层实现类似 ArrayList,是用到了 array 数组的拷贝,扩容生成一个新数组
    另外,如果理解了这道经典题的输出的迷惑行为,就大概知道 slice 是什么了
    func main() {
    s := []int{5}
    fmt.Println(cap(s)) //1

    s = append(s, 7)
    fmt.Println(cap(s)) //2

    s = append(s, 9)
    fmt.Println(cap(s)) //4

    x := append(s, 11)
    y := append(s, 12)

    fmt.Println(s, x, y) //[5 7 9] [5 7 9 12] [5 7 9 12]
    }
    x,y 都指向同一个 slice,ops,引用类型
    这里和引用类型和值类型没啥关系,和指针也没啥关系,数组本来就应该是值类型的,而 slice 又和数组长得一模一样
    至于 slice 为什么和数组用一套语法,就在于 golang 语言没法像 java 那样写出 ArrayList<String>这样来约束类型了,不知道是谁的主意,直接在语法层面解决了这一问题,关键是还和数组用同一套,就是因为没有泛型写不出一个 ArrayList 的类出来,同理还有 map
    于是就有一堆这样的迷惑行为产生了,(后面引战话我删了,逃
    qW7bo2FbzbC0
        28
    qW7bo2FbzbC0  
       2020-05-19 19:11:44 +08:00
    @mornlight 谢谢,
    qW7bo2FbzbC0
        29
    qW7bo2FbzbC0  
       2020-05-19 19:12:01 +08:00
    还是不太懂
    SimbaPeng
        30
    SimbaPeng  
       2020-05-19 20:47:29 +08:00
    @mornlight
    @Hellert

    引用类型、值类型 和 引用传递、值传递是完全不同的两个概念,go 只有值传递,但是既有值类型也有引用类型。
    SimbaPeng
        31
    SimbaPeng  
       2020-05-19 20:52:50 +08:00
    @Threeinchtime

    interface 、function 、point 也是引用类型。甚至 string 在实现上也是也是引用类型。
    guonaihong
        32
    guonaihong  
       2020-05-19 21:44:29 +08:00
    go 有指针,实在没必要所有类型都是引用。
    想要引用,无非 type 加个*。指针这个大杀器已经给你,就看你怎么玩了。
    damingxing
        33
    damingxing  
       2020-05-19 22:35:41 +08:00
    Go 语言就是这么设计的. 只能熟悉它的惯用法.

    一般情况下
    arr := [LEN]int{}
    这里 arr 表示一个数组,值类型

    用 arr2 := arr
    这复制了一个数组,相当于重新分配一个相同大小的空间,然后把每个数拷贝进去


    如果用
    arr := &[LEN]int{}
    这个地方就是一个指向数组的指针.

    arr2:=arr
    复制一个指针, arr2 和 arr 指向同一个地址.

    然后有点诡异的地方来了.
    这里你可以用 arr[0]=2, 等同于 (*arr)[0]=2.
    我是不太喜欢前面的写法的. 容易让人以为 arr 是一个数组而不是一个指针.
    相反在 c/c++里就没有这种困惑了. 因为在 c/c++里数组本质就是一个指针.
    在 c#里面也比较好理解,数组是一个引用.

    关于 Slice 相反是比较容易理解的. 因为 Slice 始终是一个引用.
    相比于 c/c++就是 vector.
    相比于 c#就是 ArrayList. 或者一个缺少功能的数组.

    Java 和 c#应该差不多.
    damingxing
        34
    damingxing  
       2020-05-19 22:40:25 +08:00
    如果用数组作为参数,则建议始终传递指针.
    读别人代码时不理解的地方写两行代码测试一下即可.
    Austaras
        35
    Austaras  
       2020-05-20 00:56:36 +08:00   ❤️ 2
    因为 rob pike 是个只会写 c 的民科
    TransAM
        36
    TransAM  
       2020-05-20 01:04:51 +08:00 via Android
    golang 里的 slice 是引用,还是有选择空间的。

    然后 std::array 是值语义,一看你就没好好看 c++。
    TransAM
        37
    TransAM  
       2020-05-20 01:10:04 +08:00 via Android
    你平时是否能用到 std::array,和 std::vector 相比如何?

    golang 也是一样的道理,只不过换了个名字。
    TransAM
        38
    TransAM  
       2020-05-20 01:19:49 +08:00 via Android
    @keepeye a 和 b 都是一个类型,而且 python 中又不止一个数组实现,你随便换个扁平序列,比如 np.array,就是第二种了。
    movistar
        39
    movistar  
       2020-05-20 01:26:56 +08:00
    Go 在设计的时候感觉就有问题,重复应用关键字,就为了省几个关键字...
    结果一个关键字在不同场景有不同的实现,导致看起来很迷
    大道至简.....
    而且 Go 目标就一个更容易开发的 C,有的其他语言做的好的优点也不采纳.所以其他语言转过来的会觉得很奇怪
    至于 slice 和 array 的问题,在 Java 里就是 ArrayList 和 type[]的区别...
    除了特殊场景(定长),绝大部分人用 ArrayList...
    frankhuu
        40
    frankhuu  
    OP
       2020-05-20 10:20:05 +08:00 via iPhone
    @damingxing 为什么 Go 不直接将 array 设计为引用类型,而是值类型呢?然后再设计一个类似 ArrayList 的类来实现动态伸缩列表
    damingxing
        41
    damingxing  
       2020-05-20 18:40:49 +08:00
    @frankhuu

    只能说它就是这样设计的. 掌握惯用法就行了.
    我个人比较喜欢 c/c++的那种方法,数组就是指针,蛮好理解.
    xhp281
        42
    xhp281  
       2020-05-23 19:43:16 +08:00
    @mornlight 明白了,多谢老哥
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1531 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 17:02 · PVG 01:02 · LAX 09:02 · JFK 12:02
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.