英文:
The difference between t and *t
问题
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)
}
问题出在String
方法的实现上。在第一个版本中,使用*t
解引用指针,将指针指向的值格式化为字符串。而在第二个版本中,直接使用了指针t
,这样会导致在格式化字符串时,又调用了String
方法,形成了递归调用,从而导致死循环。
因此,正确的做法是使用*t
来解引用指针,将指针指向的值格式化为字符串。这样可以避免死循环的问题。
英文:
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())
}
The code can work well. But if I change the String
method as in the following, it will cause dead loop. The difference is that the *t
is replaced with t
. Why?
func (t *TT) String() string {
return fmt.Sprintf("%+v", t)
}
答案1
得分: 6
因为fmt
包会检查要打印的值是否具有String() string
方法(或者换句话说:是否实现了fmt.Stringer
接口),如果是的话,就会调用该方法来获取值的string
表示。
这在fmt
包的文档中有说明:
> [...] 如果操作数实现了String() string
方法,该方法将被调用以将对象转换为字符串,然后根据所需的动词进行格式化。
在这里:
return fmt.Sprintf("%+v", *t)
你将类型为TT
的值*t
传递给了fmt
包。如果TT.String()
方法具有指针接收器,那么类型TT
的方法集 不包括String()
方法,因此fmt
包不会调用它(只有*TT
的方法集包括它)。
如果你将接收器更改为非指针类型,那么类型TT
的方法集将包括String()
方法,因此fmt
包将调用它,但这就是我们当前所在的方法,所以这是一个无限的“间接递归”。
预防/保护
如果由于某种原因你确实需要使用与传递给fmt
包的值类型相同的接收器类型,一种简单常见的方法是使用type
关键字创建一个新类型,并对传递的值使用类型转换:
func (t TT) String() string {
type TT2 TT
return fmt.Sprintf("%+v", TT2(t))
}
在Go Playground上尝试一下。
但是为什么这样能行呢?因为type
关键字创建了一个新类型,而新类型将没有任何方法(它不会“继承”基础类型的方法)。
这会产生一些运行时开销吗?不会。引用自规范:类型声明:
> 特定规则适用于数值类型之间或与字符串类型之间的(非常量)转换。这些转换可能会改变x
的表示并产生运行时开销。所有其他转换只会改变类型而不会改变x
的表示。
在这里阅读更多信息:https://stackoverflow.com/questions/32253768/does-convertion-between-alias-types-in-go-create-copies/32253871#32253871
英文:
Because the fmt
package checks if the value being printed has a String() string
method (or in other words: if it implements the fmt.Stringer
interface), and if so, it will be called to get the string
representation of the value.
This is documented in the fmt
package doc:
> [...] If an operand implements method String() string, that method will be invoked to convert the object to a string, which will then be formatted as required by the verb (if any).
Here:
return fmt.Sprintf("%+v", *t)
You are passing a value *t
of type TT
to the fmt
package. If the TT.String()
method has a pointer receiver, then the method set of the type TT
does not include the String()
method, so the fmt
package will not call it (only the method set of *TT
includes it).
If you change the receiver to non-pointer type, then the method set of the type TT
will include the String()
method, so the fmt
package will call that, but this is the method we're currently in, so that's an endless "indirect recursion".
Prevention / protection
If for some reason you do need to use the same receiver type as the type of the value you pass to the fmt
package, an easy and common way to avoid this / protect from it is to create a new type with the type
keyword, and use type conversion on the value being passed:
func (t TT) String() string {
type TT2 TT
return fmt.Sprintf("%+v", TT2(t))
}
Try this on the Go Playground.
But why does this work? Because the type
keyword creates a new type, and the new type will have zero methods (it does not "inherit" the methods of the underlying type).
Does this incur some run-time overhead? No. Quoting from Spec: Type declarations:
> Specific rules apply to (non-constant) conversions between numeric types or to and from a string type. These conversions may change the representation of x
and incur a run-time cost. All other conversions only change the type but not the representation of x
.
Read more about this here: https://stackoverflow.com/questions/32253768/does-convertion-between-alias-types-in-go-create-copies/32253871#32253871
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论