【问题标题】:Golang bytes.Buffer - Passby value issueGolang bytes.Buffer - 传递值问题
【发布时间】:2018-12-27 09:11:14
【问题描述】:

下面的 golang(go1.10.2) 代码会给出意外的输出

package main

import (
    "bytes"
    "fmt"
)

func main() {
    var b bytes.Buffer
    //Commenting the below line will fix the problem
    b.WriteString("aas-")
    fmt.Printf("Before Calling - \"%s\"\n", b.String())
    b = makeMeMad(b)
    fmt.Printf("FinalValue - \"%s\"\n", b.String())
}

func makeMeMad(b bytes.Buffer) bytes.Buffer {
    b.WriteString("xcxxcx asdasdas dasdsd asdasdasdasd")
    fmt.Printf("Write More - \"%s\"\n", b.String())

    /*
        //This will fix the problem
        var newBuffer bytes.Buffer
        newBuffer.WriteString(b.String())
        return newBuffer
    */
    return b
}

输出

Before Calling - "aas-"
Write More - "aas-xcxxcx asdasdas dasdsd asdasdasdasd"
FinalValue - "aas-                                   "

我期待在输出的最后一行出现“aas-xcxxcx asdasdas dasdsd asdasdasdasd”。谁能解释一下。

【问题讨论】:

标签: go pass-by-value


【解决方案1】:

在后台bytes.Buffer 包含其他未导出的字段bootstrap 数组和buf 切片。虽然缓冲区内容是指向内部数组的小切片以避免分配。当您将 bytes.Buffer 参数作为值传递时,函数会收到一份副本。切片是引用类型,所以这个副本的切片继续指向原始缓冲区的数组。当您写入此副本的切片时,您实际上写入了原始的引导数组,副本的数组保持不变(在我们的例子中为“aas-”)。然后你返回这个副本,你可以打印它。但是当你将它分配给包含原始的变量时,引导数组首先被分配(“aas-”)然后buf slice 指向它。 引导数组是 [64] 字节,因此您可以在代码 sn-p 和 see 中使用 >64 的长字符串文字,当缓冲区分配 buf 切片时,事情会按预期工作。 还有here 的简化示例试图说明为什么这一切看起来如此简洁。

【讨论】:

    【解决方案2】:

    Golang FAQ 部分提到:

    如果接口值包含指针*T,则方法调用可以获得 通过取消引用指针来获得一个值,但如果是一个接口值 包含一个值 T,方法调用没有有用的方法来获取 一个指针。

    即使编译器可以将值的地址 传递给方法,如果方法修改了值,则更改将 迷失在来电者中。例如,如果 Write 方法的 bytes.Buffer 使用了值接收器而不是指针,这段代码:

    var buf bytes.Buffer
    io.Copy(buf, os.Stdin)
    

    将标准输入复制到 buf 的副本中,而不是 buf 本身

    错误是因为您没有在 makeMeMad 函数中传递缓冲区的地址。这就是为什么它没有覆盖 main 函数中的原始缓冲区。 将地址传递给创建的缓冲区以将字符串附加到现有缓冲区值。

    package main
    
    import (
        "bytes"
        "fmt"
    )
    
    func main() {
        var b bytes.Buffer
        //Commenting the below line will fix the problem
        b.WriteString("aas-")
        fmt.Printf("Before Calling - \"%s\"\n", b.String())
        makeMeMad(&b)
        fmt.Printf("FinalValue - \"%s\"\n", b.String())
    }
    
    func makeMeMad(b *bytes.Buffer) {
        b.WriteString("xcxxcx asdasdas dasdsd asdasdasdasd")
        fmt.Printf("Write More - \"%s\"\n", b.String())
    
        /*
            //This will fix the problem
            var newBuffer bytes.Buffer
            newBuffer.WriteString(b.String())
            return newBuffer
        */
    }
    

    Playground Example

    或者您可以将返回的缓冲区值分配给一个新变量,您将获得更新后的缓冲区值。

    package main
    
    import (
        "bytes"
        "fmt"
    )
    
    func main() {
        var b bytes.Buffer
        //Commenting the below line will fix the problem
        b.WriteString("aas-")
        fmt.Printf("Before Calling - \"%s\"\n", b.String())
        ab := makeMeMad(b)
        fmt.Printf("FinalValue - \"%s\"\n", ab.String())
    }
    
    func makeMeMad(b bytes.Buffer) bytes.Buffer {
        b.WriteString("xcxxcx asdasdas dasdsd asdasdasdasd")
        fmt.Printf("Write More - \"%s\"\n", b.String())
    
        /*
            //This will fix the problem
            var newBuffer bytes.Buffer
            newBuffer.WriteString(b.String())
            return newBuffer
        */
        return b
    }
    

    Go Playground 上的工作代码

    或者您可以创建一个全局缓冲区,以便在任何函数写入缓冲区时更改缓冲区内的值。

    package main
    
    import (
        "bytes"
        "fmt"
    )
    
    var b bytes.Buffer
    
    func main() {
        //Commenting the below line will fix the problem
        b.WriteString("aas-")
        fmt.Printf("Before Calling - \"%s\"\n", b.String())
        b := makeMeMad(b)
        fmt.Printf("FinalValue - \"%s\"\n", b.String())
    }
    
    func makeMeMad(b bytes.Buffer) bytes.Buffer {
        b.WriteString("xcxxcx asdasdas dasdsd asdasdasdasd")
        fmt.Printf("Write More - \"%s\"\n", b.String())
    
        /*
            //This will fix the problem
            var newBuffer bytes.Buffer
            newBuffer.WriteString(b.String())
            return newBuffer
        */
        return b
    }
    

    Playground Example

    【讨论】:

    • 但他确实写了b = ... 对吗?所以值被重新写入b,不是吗?
    • @leafbebop 检查编辑后的答案 OP 正在将缓冲区的副本传递给函数。
    • 然后他返回一个修改过的缓冲区的副本,该缓冲区被重新分配给原始变量。
    • @leafbebop 是的,该值更新了缓冲区的状态,但返回时它丢弃了更改
    • @leafbebop 如果您仔细检查,您仍然没有写入主缓冲区,这并没有什么新鲜事。如果您真的想在缓冲区中添加一些内容,请使用 play.golang.org/p/ukqEyZaaCLI
    猜你喜欢
    • 2014-12-22
    • 2019-06-25
    • 1970-01-01
    • 2020-05-26
    • 2017-01-21
    • 2021-11-07
    • 1970-01-01
    • 1970-01-01
    • 2019-01-20
    相关资源
    最近更新 更多