在Go中,为什么不调用我的Stringer接口方法?使用fmt.Println时

Ral*_*veo 48 go

假设我有以下代码:

package main

import "fmt"

type Car struct{
    year int
    make string
}

func (c *Car)String() string{
    return fmt.Sprintf("{make:%s, year:%d}", c.make, c.year)
}

func main() {
    myCar := Car{year:1996, make:"Toyota"}
    fmt.Println(myCar)
}
Run Code Online (Sandbox Code Playgroud)

当我调用fmt.Println(myCar)并且有问题的对象是指针时,我的String()方法被正确调用.但是,如果对象是一个值,我的输出将使用Go内置的默认格式进行格式化,并且我的代码不会调用格式化所述对象.

有趣的是,无论是哪种情况,如果我手动调用myCar.String(),无论我的对象是指针还是值,它都能正常工作.

如果对象与Println一起使用时,无论对象是基于值还是基于指针,如何以我想要的方式格式化对象?

我不想对String使用值方法,因为这意味着每次调用该对象时都会复制哪个接缝不合理.而且我不想总是手动调用.String(),因为我正试图让鸭子打字系统完成它的工作.

提前致谢!

-Ralph

gue*_*fey 69

在调用时fmt.Println,myCar隐式转换为类型的值,interface{}如您在函数签名中看到的那样.fmt然后包中的代码执行类型切换以找出如何打印此值,看起来像这样:

switch v := v.(type) {
case string:
    os.Stdout.WriteString(v)
case fmt.Stringer:
    os.Stdout.WriteString(v.String())
// ...
}
Run Code Online (Sandbox Code Playgroud)

但是,fmt.Stringer案例失败是因为Car没有实现String(因为它已定义*Car).String手动调用是有效的,因为编译器看到String需要a *Car并因此自动转换myCar.String()(&myCar).String().有关接口的任何内容,您必须手动完成.所以你要么必须实现Stringon,Car要么总是传递指针fmt.Println:

fmt.Println(&myCar)
Run Code Online (Sandbox Code Playgroud)


pet*_*rSO 24

方法

指针与价值观

有关接收器的指针与值的规则是可以在指针和值上调用值方法,但只能在指针上调用指针方法.这是因为指针方法可以修改接收器; 在值的副本上调用它们将导致丢弃这些修改.

因此,对于String在指针和值上调用时的方法,请使用值接收器.例如,

package main

import "fmt"

type Car struct {
    year int
    make string
}

func (c Car) String() string {
    return fmt.Sprintf("{make:%s, year:%d}", c.make, c.year)
}

func main() {
    myCar := Car{year: 1996, make: "Toyota"}
    fmt.Println(myCar)
    fmt.Println(&myCar)
}
Run Code Online (Sandbox Code Playgroud)

输出:

{make:Toyota, year:1996}
{make:Toyota, year:1996}
Run Code Online (Sandbox Code Playgroud)

  • 这是否基本上意味着如果我有一个大的结构,每次它通过Println它将被复制?就像我在帖子中说的那样,这种接缝不合理. (16认同)
  • @RalphCaraveo不,参数类型为`interface {}`,其中包含类型信息和指向该结构的指针。结构本身不会被复制。 (2认同)

zzz*_*zzz 7

在指针接收器上定义fmt.Stringer:

package main

import "fmt"

type Car struct {
        year int
        make string
}

func (c *Car) String() string {
        return fmt.Sprintf("{maker:%s, produced:%d}", c.make, c.year)
}

func main() {
        myCar := Car{year: 1996, make: "Toyota"}
        myOtherCar := &Car{year: 2013, make: "Honda"}
        fmt.Println(&myCar)
        fmt.Println(myOtherCar)
}
Run Code Online (Sandbox Code Playgroud)

操场


输出:

{maker:Toyota, produced:1996}
{maker:Honda, produced:2013}    
Run Code Online (Sandbox Code Playgroud)

然后,始终将指向Car实例的指针传递给fmt.Println.这样,您可以在您的控制下避免使用价格昂贵的价值副本.


小智 6

OP进一步询问:

OP:[当使用值接收器时] “这是否基本上意味着如果我有一个大结构,那么每次它通过 Println 时都会被复制?”

以下实验证明答案是“是”(当使用值接收器时)。请注意,该String()方法在此实验中递增年份,并检查这如何影响打印输出。

type Car struct {
    year int
    make string
}

func (c Car) String() string {
    s := fmt.Sprintf("{ptr:%p, make:%s, year:%d}", c, c.make, c.year)
    // increment the year to prove: is c a copy or a reference?
    c.year += 1
    return s
}

func main() {
    myCar := Car{year: 1996, make: "Toyota"}
    fmt.Println(&myCar)
    fmt.Println(&myCar)
    fmt.Println(myCar)
    fmt.Println(myCar)
}
Run Code Online (Sandbox Code Playgroud)

对于值接收器(c Car),以下打印输出显示 Go 制作了Car结构的值副本,因为年份增量不会反映在对 的后续调用中Println

type Car struct {
    year int
    make string
}

func (c Car) String() string {
    s := fmt.Sprintf("{ptr:%p, make:%s, year:%d}", c, c.make, c.year)
    // increment the year to prove: is c a copy or a reference?
    c.year += 1
    return s
}

func main() {
    myCar := Car{year: 1996, make: "Toyota"}
    fmt.Println(&myCar)
    fmt.Println(&myCar)
    fmt.Println(myCar)
    fmt.Println(myCar)
}
Run Code Online (Sandbox Code Playgroud)

将接收器更改为指针(c *Car)但不更改其他任何内容,打印输出变为:

{ptr:%!p(main.Car={1996 Toyota}), make:Toyota, year:1996}
{ptr:%!p(main.Car={1996 Toyota}), make:Toyota, year:1996}
{ptr:%!p(main.Car={1996 Toyota}), make:Toyota, year:1996}
{ptr:%!p(main.Car={1996 Toyota}), make:Toyota, year:1996}
Run Code Online (Sandbox Code Playgroud)

即使在对 的调用中提供了一个指针作为参数Println,即fmt.Println(&myCar)当使用值接收器时,Go 仍然会制作该Car结构的值副本OP 希望避免进行值复制,我的结论是只有指针接收器才能满足该要求。