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

Golang 结构体切片,大家的习惯是切片元素是结构体值还是指针(不考虑性能,仅考虑代码优雅)

  •  
  •   einsdisp · 2020-10-10 09:29:11 +08:00 · 2554 次点击
    这是一个创建于 378 天前的主题,其中的信息可能已经有所发展或是发生改变。
    type Foobar struct {
    }
    

    切片元素是值

    var s []Foobar
    

    Or:

    切片元素是指针

    var s []*Foobar
    

    不考虑性能,仅考考代码优雅性。

    前一种切片(元素为值)在循环赋值时有不便之处:

    for _, i := range s {
    	i.xxx = yyy
    }
    

    上面的代码实际上是无效的,必须使用索引,于是代码很不优雅:

    for i, _ := range s {
    	s[i].xxx = yyy
    }
    

    但后一种切片(元素为指针),在很多使用场景下同样不方便

    20 条回复    2020-10-14 17:47:37 +08:00
    einsdisp
        1
    einsdisp   2020-10-10 09:42:39 +08:00
    后一种切片(元素为指针),在很多使用场景下同样不方便。

    在使用切片中的某元素时,常常要先解引用(就是说需要写成类似 `*s[i]` 的形式而非 `s[i]`),感觉同样很不优雅
    zarte
        2
    zarte   2020-10-10 09:48:42 +08:00
    ```
    for _, i := range s {
    i.xxx = yyy
    }
    ```
    其他语言这样也不行呀。
    我是用这种。
    JeromeCui
        3
    JeromeCui   2020-10-10 09:53:28 +08:00
    我比较习惯指针,可能是因为我之前写了好几年的 C++吧
    cmdOptionKana
        4
    cmdOptionKana   2020-10-10 09:54:55 +08:00
    一律优先用指针,遇到特殊情况才直接用结构体。

    另外在 Go 里,给一个结构体添加方法,官方也是提倡优先对其指针添加方法,像这样 func (foobar *Foobar) Method(){}
    richzhu
        5
    richzhu   2020-10-10 09:56:32 +08:00
    我是用上面那种
    labulaka521
        6
    labulaka521   2020-10-10 09:58:16 +08:00 via iPhone
    range 是复制到一个新的对象去了,你再修改也改不到原始的
    simenet
        7
    simenet   2020-10-10 09:58:51 +08:00
    别问,问就是指针
    dodoa
        8
    dodoa   2020-10-10 10:02:27 +08:00
    喜欢用值类型,虽然指针和值类型在传值修改的影响完全不一样,根据使用场景的不同,各自有各自的优势或者说弊端吧。值类型完全不用考虑内存管理的问题
    coderxy
        9
    coderxy   2020-10-10 10:10:09 +08:00
    用指针,性能即优雅。 就像我声明切片的时候尽量指定容量,看着更舒服。
    keepeye
        10
    keepeye   2020-10-10 10:22:10 +08:00
    指针
    damngood
        11
    damngood   2020-10-10 10:31:33 +08:00 via iPhone
    指针性能未必更好. 要考虑 gc 和内存分配这些问题.
    Sasasu
        12
    Sasasu   2020-10-10 10:40:53 +08:00
    在有 gc 的语言中一切常识都要重新考虑。

    一个指针数组填充时需要 N 次内存分配,创建 N + 1 个对象。

    写起来奔放的话 gc 导致的 cpu 使用会比程序真正用到的还要多。

    STW 延迟还好,但是吞吐量就没有了。
    vvmint233
        13
    vvmint233   2020-10-10 10:54:32 +08:00
    所以为啥要加个_, for i := range s {s[i].xxx = yyyy} 也挺好看的啊
    index90
        14
    index90   2020-10-10 10:54:43 +08:00
    同样的问题: https://v2ex.com/t/664610
    snowwalf
        15
    snowwalf   2020-10-10 10:56:32 +08:00
    for i := range s {
    s[i].xxx = yyy
    }
    useben
        16
    useben   2020-10-10 11:16:06 +08:00
    看情况, 小对象直接存值; 大对象一般存指针; 若大对象且频繁创建销毁看情况, 若内存足够直接存值, 若内存紧张存指针, 总之就是 trade-off

    因为切片底层的扩容是根据切片元素分情况的, 值 or 指针. 若是值直接扩容追加到旧内存; 若是指针, 需要判断是否写屏障, 还有 gc 判断啥的, 因此对增加开销.
    lewinlan
        17
    lewinlan   2020-10-10 11:32:40 +08:00 via Android
    无脑指针即可
    mengzhuo
        18
    mengzhuo   2020-10-10 11:40:57 +08:00
    99%指针
    1%为了性能才用 struct
    gamexg
        19
    gamexg   2020-10-10 11:52:52 +08:00
    尽量指针,
    极个别为了性能考虑保存为值,但是使用时也尽量使用指针:

    ```
    list1 := make([]Struct1, 1)

    for i, _ := range list1 {
    v := &list1[i]

    v.A = "11"
    }
    ```
    bluetroy
        20
    bluetroy   2020-10-14 17:47:37 +08:00
    是否考虑过为什么 range 获取到的元素是元素的一个复制?为什么 go 语言要如此设计?人家特地复制出来给你用就怕你瞎改。老老实实写 s[i].xxx = yyy 吧。
    另外:slice[1] = a;slice[1]被放入的是 a 的一个复制,但是使用 slice[1]获取值时获取的是底层值,因此可以直接进行 slice[1].xx =b 。 而 map[1]=a,而获取的时候总是返回 map[1]底层值的一个复制,因此无法 map[1].xx=b 。
    关于   ·   帮助文档   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   1297 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 18:09 · PVG 02:09 · LAX 11:09 · JFK 14:09
    ♥ Do have faith in what you're doing.