假设我有以下代码:
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)
在指针接收器上定义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 希望避免进行值复制,我的结论是只有指针接收器才能满足该要求。