t和*t之间的区别是什么?

huangapple go评论102阅读模式
英文:

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

huangapple
  • 本文由 发表于 2017年3月28日 17:33:09
  • 转载请务必保留本文链接:https://go.coder-hub.com/43065856.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定