美文网首页
Go切片内容丢失问题

Go切片内容丢失问题

作者: lwhile | 来源:发表于2017-10-24 23:10 被阅读46次

title: "Go切片内容丢失问题"
date: 2017-10-24
draft: false
categories: ["Golang"]
tags: [""]


感觉这篇文章有点标题党的嫌疑, 文中要说的其实还是Go的值传递的问题, 不是发现了Go的某些惊天大bug,误入的大神可以不用看下面的内容啦 !

起因是在编写公司一个项目的过程中, 发现如果在结构体的不同方法之间调用其包含的切片,会出现切片内容不一致的情况.代码类似这样:

type T struct {
    slice []int
}

func (t T) Append() {
    t.slice = append(t.slice, 1)
    fmt.Println("#1 slice:",t.slice)
}

func (t T) Show() {
    fmt.Println("#2 slice:", t.slice)
}

func main() {
    t := T{}
    t.slice = make([]int, 0, 1)
    t.Append()
    t.Show()
}

执行上面的代码会输出:

#1 slice: [1]
#2 slice: []

按照正常的思维, 切片应该都是输出[1]才对,但其实因为Go的值传递模型, 导致了结果与预料的出现了偏差.之前以为自己对这个问题彻底搞懂了,没想到还是会纠结了一下, 下面记录下解决问题的过程,希望自己彻底理解Go的值传递模型,也希望能对你有帮助.

为上面的代码添加一些打印输出:

type T struct {
    slice []int
}

func (t T) Append() {
    fmt.Printf("Append: %p\n", &t)
    t.slice = append(t.slice, 1)
}

func (t T) Show() {
        fmt.Printf("Show: %p\n", &t)
}

func main() {
    t := T{}
    fmt.Printf("main: %p\n", &t)
    t.slice = make([]int, 0, 1)
    t.Append()
    t.Show()
}

输出为

main: 0x1040a0b0
Append: 0x1040a0c0
Show: 0x1040a0d0

可以看到, 三个变量t的地址都不相同, 所以在调用某个类型的方法的时候, Go是会拷贝一个新的值,确实是使用值传递.

接下来打印slice的地址出来看看(我在这里犯了一个错误)

type T struct {
    slice []int
}

func (t T) Append() {
    t.slice = append(t.slice, 1)
    fmt.Printf("Append slice: %p\n", t.slice)
}

func (t T) Show() {
         fmt.Printf("Show slice: %p\n", t.slice)
}

func main() {
    t := T{}
    t.slice = make([]int, 0, 1)
    fmt.Printf("main slice: %p\n", t.slice)
    t.Append()
    t.Show()
}

输出为:

main slice: 0x10410020
Append slice: 0x10410020
Show slice: 0x10410020

看到这里输出的地址相同, 我误以为调用类型方法的时候,对切片没有使用值传递.但其实不然

Go的切片本质是也是一个结构体, 在源码中可以看到它的定义:

// src/runtime/slice.go
type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}

我们上面输出的3个地址都是这个结构体的地址.但不是说Go是值传递的吗,为什么这里又传了引用?
其实Go在这里还是使用了值传递, 但对于slice, map, channel这三种应用类型, Go又不会做全部的复制处理, 如果这样做显然开销太大.
Go默默得为我们新建了一个变量, 然后将这个变量的值指向了切片.说起来有点绕, 用一张图来表示会更直白:

image.png

我们打印看下图中 t ' 对应的slice的地址看看:

type T struct {
    slice []int
}

func (t T) Append() {
    t.slice = append(t.slice, 1)
    fmt.Printf("Append slice: %p\n", &t.slice)
}

func (t T) Show() {
         fmt.Printf("Show slice: %p\n", &t.slice)
}

func main() {
    t := T{}
    t.slice = make([]int, 0, 1)
    fmt.Printf("main slice: %p\n", &t.slice)
    t.Append()
    t.Show()
}

输出的3个地址确实都不同:

main slice: 0x1040a0b0
Append slice: 0x1040a0c0
Show slice: 0x1040a0d0

总结:
Go里面的参数传递其实都是按照值传递来进行的, 理解这点可以解决很多开发中遇到的问题.至于如何解决上面代码中切片元素不一致的问题 ? 很简单, 把类型方法中的T改为指针类型就可以了.

相关文章

  • Go切片内容丢失问题

    title: "Go切片内容丢失问题"date: 2017-10-24draft: falsecategories...

  • Go语言切片

    // //Go语言切片 // /* // go语言切片是对数组的抽象 // Go 数组的长度不可改变,在特定场景中...

  • leetcode的Combination Sum题补充切片的传递

    Combination Sum以go 切片的传递 内容:回溯法。切片切为形参,为值传递。如果需要引用传递,要加指针...

  • Golang之数组和切片

    引用 数组、字符串和切片 Go数组中的索引问题 深入解析 Go 中 Slice 底层实现 Golang 入门 : ...

  • 第03天(复合类型)_03

    13_数组做函数参数.go 14_数组指针做函数参数.go 15_切片的长度和容量.go 16_切片的创建.go ...

  • Go语言第3天 - 常用数据类型

    以下内容部分参考自Go语言基础数据类型Go语言中的数组切片:特立独行的可变数组Go语言数据类型-数组Go标准容器之...

  • 15.Go_Slice(切片)

    Go 切片 定义切片 切片初始化 len()和cap()函数 空(nil)切片 切片拦截 append() 和co...

  • 七、Go切片

    七、Go语言切片(Slice) Go 语言切片是对数组的抽象。 Go 数组的长度不可改变,在特定场景中这样的集合就...

  • vue中返回上一页go()和back()的区别

    go(-1): 返回上一页, 原页面表单中的内容会丢失; history.go(-1):后退+刷新; histor...

  • golang系列教程

    Go包管理 Go开发工具 Go Doc 文档 Go 数组 Go 切片 Go Map Go 类型 Go 函数方法 G...

网友评论

      本文标题:Go切片内容丢失问题

      本文链接:https://www.haomeiwen.com/subject/tmlfpxtx.html