小编典典

t和* t之间的差异

go

package main

import "fmt"

type TT struct {
    a int
    b float32
    c string
}

func (t *TT) String() string {
    return fmt.Sprintf("%+v", *t)
}

func main() {
    tt := &TT{3, 4, "5"}
    fmt.Printf(tt.String())
}

该代码可以很好地工作。但是,如果String按如下所示更改方法,则会导致死循环。区别在于将*t替换为t。为什么?

func (t *TT) String() string {
    return fmt.Sprintf("%+v", t)
}

阅读 467

收藏
2020-07-02

共1个答案

小编典典

因为fmt程序包检查要打印的值是否具有String() string方法(或换句话说:是否实现fmt.Stringer接口),如果是,则将调用它以获取string值的表示形式。

fmt软件包doc中对此进行了说明:

[…]如果操作数实现String()字符串方法,则将调用该方法将对象转换为字符串,然后根据动词的要求对其进行格式化(如果有)。

这里:

return fmt.Sprintf("%+v", *t)

您正在将*t类型的值传递TTfmt包。如果该TT.String()方法具有指针接收器,则该类型的方法集TT
不包含String()方法,因此fmt程序包将不会调用它(仅*TT包含它的方法集)。

如果更改接收到非指针类型,那么该类型的方法集TT 将包括
String()方法,所以fmt包将调用,但这是我们目前正处在的方法,所以这是一个无休止的“间接递归”。

预防/保护

如果由于某种原因您确实需要使用与传递给fmt包的值的类型相同的接收器类型,避免这种情况/避免这种情况发生的一种简单而通用的方法是type使用关键字创建一个新类型,并使用type
传递的值的转换

func (t TT) String() string {
    type TT2 TT
    return fmt.Sprintf("%+v", TT2(t))
}

Go Playground上尝试一下。

但是为什么这样做呢?因为type关键字创建了一个新类型,并且该新类型将具有 零个 方法(它不会“继承”基础类型的方法)。

这会产生一些运行时开销吗?编号。引用自规范:类型声明

特定规则适用于数字类型之间或字符串类型之间的(非恒定)转换。这些转换可能会更改的表示形式x并产生运行时成本。
所有其他转换只会更改类型,而不会更改的表示形式x

2020-07-02