01介绍
在 Golang 语言开发中,我们经常会使用结构体类型,如果我们使用的结构体类型的变量包含指针类型的字段,我们在记录日志的时候,指针类型的字段的值是指针地址,将会给我们 debug 代码造成不便。
实际上,Golang 为我们提供了一个接口类型 Stringer ,它是一个支持以字符串形式描述自己的类型,它只提供了一个方法,应该是 Golang 中最简单和最常用的接口之一,它由 fmt 包定义。
type Stringer interface {
String() string
}
- 1.
- 2.
- 3.
fmt 包的打印函数会检查你的类型是否实现该接口,以便知道怎么打印你的变量。所以,我们在记录日志的时候,如果需要记录的变量是具有指针类型字段的结构体,我们不妨也为该结构体类型定义 String 方法,用来实现可以记录指针字段的实际值的目的。
本文我们介绍怎么通过实现 Stringer 接口,让我们的代码更优雅。
02打印指针类型的值
读者朋友们在 Golang 程序开发中,一定也会使用到包含指针类型字段的结构体,你是否在记录日志的时候,发现记录的值是指针地址,给你 debug 代码造成不便呢?
func main() {
name := "frank"
user := User{
Id: 1,
Name: &name,
}
fmt.Println(user)
}
type User struct {
Id int
Name *string
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
输出结果:
{1 0xc000096210}
- 1.
阅读上面这段代码,我们构造了一个包含指针类型字段的结构体,然后打印该结构体类型的变量,输出结果中指针类型的字段 Name 的值是指针地址,而不是我们想要的字段值 frank。
试想一下,如果我们记录的日志中,变量的值是指针地址,将会对我们 debug 代码造成不变,所以我们需要使用 Golang 提供的接口 Stringer 解决该问题。
func (u User) String() string {
return fmt.Sprintf("{Id: %v, Name: %v}", u.Id, *u.Name)
}
- 1.
- 2.
- 3.
输出结果:
{Id: 1, Name: frank}
- 1.
阅读上面这段代码,我们给类型 User 定义了 String 方法,通过实现 Golang 的 Stringer 接口,来实现打印指针类型变量的实际值的目的。
03避“坑”
读者朋友们阅读完以上内容,应该已经学会了怎么使用接口 Stringer 实现打印指针类型变量的值。不过,我还是想列举一个异常情况,帮助 Golang 新手读者朋友避“坑”。如果你已经是 Golang 老手,本节内容可以跳过。
func (u *User) String() string {
return fmt.Sprintf("{Id: %v, Name: %v}", u.Id, *u.Name)
}
- 1.
- 2.
- 3.
阅读上面这段代码,我们将类型方法的接收者改为指针类型,我相信大多数读者朋友们会使用指针类型的接收者。此时,读者朋友会发现输出结果没有使用我们定义的 String 方法,而是输出的指针类型字段的指针地址。
想要解决这个问题也很简单,我们只需要在定义结构体类型变量的时候,使用指针类型,这样 fmt 包的打印函数就可以自动执行我们定义的 String 方法了。
func main() {
name := "frank"
user := &User{
Id: 1,
Name: &name,
}
fmt.Println(user)
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
04总结
本文我们介绍了怎么打印包含指针类型变量的结构体类型变量的值,在我们需要记录日志的时候,不用再因为记录的是指针地址,从而给我们 debug 代码造成不便。
参考资料:
https://go.dev/doc/effective_go#pointers_vs_values
https://go.dev/tour/methods/17