为什么在 Go 数组(array)被设计成值,而不跟 C/C++或 Java 一样,设计为一个引用? - V2EX
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 6926 次点击
    这是一个创建于 1978 天前的主题,其中的信息可能已经有所发展或是发生改变。
    最近在入门学习 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 是引用类型,其余是值类型。
    传值类型拷贝内存,传引用类型拷贝指针。
    lavvrence
        24
    lavvrence  
       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     Solana     875 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 38ms UTC 22:36 PVG 06:36 LAX 15:36 JFK 18:36
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86