Golang 字节。缓冲区 - 传递值问题

Golang bytes.Buffer - Passby value issue

提问人:Dinu Mathai 提问时间:7/19/2018 更新时间:7/19/2018 访问量:5814

问:

下面的 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 asdasdasd”。谁能解释一下。

值传递

评论

0赞 leaf bebop 7/19/2018
该问题已在 Go 的 github 上发布为一个问题,您可以在此处观看:github.com/golang/go/issues/26462
2赞 icza 7/19/2018
所以你所经历的答案就在这里:github.com/golang/go/issues/26462#issuecomment-406256821。 从一开始就不应该按原样传递,始终传递指向它的指针。bytes.Buffer

答:

-1赞 Himanshu 7/19/2018 #1

在 Golang FAQ 部分中提到它为:

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

即使在编译器可以将值的地址取为 传递给方法,如果方法修改了更改的值,则更改将 迷失在来电者中。例如,如果 字节。缓冲区使用值接收器而不是指针,此代码:

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

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

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

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 示例

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

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 示例

评论

0赞 leaf bebop 7/19/2018
但他确实写对了吗?所以这个值被重写成,不是吗?b = ...b
0赞 Himanshu 7/19/2018
@leafbebop检查编辑后的答案,OP 正在将缓冲区的副本传递给函数。
0赞 leaf bebop 7/19/2018
然后他返回一个修改后的缓冲区的副本,该副本被重新分配给原始变量。
0赞 Himanshu 7/19/2018
@leafbebop是的,该值更新了缓冲区的状态,但在返回时它丢弃了更改
0赞 Himanshu 7/19/2018
@leafbebop 没有什么新东西,如果你仔细检查它,你仍然没有写入主缓冲区。如果您确实想将某些内容附加到缓冲区,请使用此 play.golang.org/p/ukqEyZaaCLI
1赞 Uvelichitel 7/19/2018 #2

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