Golang中默认String()函数的使用问题

Golang中默认String()函数的使用问题 我定义了一个结构体,我们称之为 S。仅仅通过这样做,我就创建了无限的数据类型:S、指向 S 的指针、指向 S 指针的指针,等等。

在这一系列无限的数据类型中,只有其中一个已经实现了默认的 String() 函数,那就是 S。<- 这是真的吗? (1)

因为 S 已经有一个默认的 String() 方法,如果我为 *S(指向 S 类型的指针)定义一个 String() 方法,这将有一个缺点:当为 *S 调用时它可以工作,但不会为 S(非指针)调用。当为 S(常规类型,非指针)调用时,默认的 String() 方法仍然会被调用。<- 这是真的吗?

这就是为什么,在创建自定义的 String() 函数时,将其接收器设为类型 S 而不是指向 S 的指针是有益的。因为 *S 没有默认的 String() 函数,当在指针接收器上调用 String() 时,我的自定义 String()(为 S 定义,非指针)会被调用。<- 这是真的吗?

对于类型 **S(指向 S 指针的指针),我为 S 编写的自定义 String() 函数不会被调用。<- 为什么会这样,这与陈述 (1) 相矛盾。

请帮我弄清楚真相,希望这个真相能让我摆脱疑虑 🙂 也可以看看这个例子:https://play.golang.org/p/tQ_nfRHk8w4


更多关于Golang中默认String()函数的使用问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

JOhn_Stuart:

在这一系列无限的数据类型中,只有一种类型已经默认实现了 String() 函数,那就是 S。← 这是真的吗?(1)

不是。String 函数不会被自动实现。"打印系列"函数对内置类型有默认的处理方式,然后会通过类型断言检查该类型是否实现了 fmt.Formatterfmt.GoStringerfmt.Stringer 接口,如果类型没有定义这些接口,最终会回退到使用 reflect 包来构建其表示形式。

JOhn_Stuart:

因为 S 已经有一个默认的 String() 方法,如果我为 *S(指向 S 类型的指针)定义一个 String() 方法,这将有一个缺点:当为 *S 调用时它会工作,但为 S(不带指针)调用时则不会被调用。当为 S(常规类型,非指针)调用时,默认的 String() 方法仍然会被调用。← 这是真的吗?

我不太确定如何回答这个问题。你不能同时在 T*T 上定义 String 方法:Go Playground - The Go Programming Language

func main() {
    fmt.Println("hello world")
}

你可以在类型为 T 的变量或变量上调用 (*T).String 函数;编译器会隐式地获取变量的地址(或结构体字段、数组/切片元素等)并使用它:Go Playground - The Go Programming Language。然而,如果值被封装到接口中,例如当值被传递给任何"打印系列"函数时,这种情况就不会发生。

JOhn_Stuart:

这就是为什么,在创建自定义的 String() 函数时,将其接收者定义为类型 S 而不是指向 S 的指针是有益的。因为 *S 没有默认的 String() 函数,当在指针接收者上调用 String() 时,我自定义的 String()(为 S 定义,不带指针)就会被调用。← 这是真的吗?

"有益"是一个非常主观的术语,但我在这里同意你的观点 🙂

JOhn_Stuart:

对于类型 **S(指向 S 指针的指针),我为 S 编写的自定义 String() 函数不会被调用。← 为什么会这样,这与陈述 (1) 相矛盾

我不知道为什么会这样,但 Go 不允许在双重指针上定义方法,即使指针是定义的类型:Go Playground - The Go Programming Language,所以没有从 (**T).StringT.String 的隐式调用。

更多关于Golang中默认String()函数的使用问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go语言中,String()方法遵循接收者类型的方法集规则。让我们逐一分析你的问题:

  1. 关于默认String()方法的存在: 只有S类型有默认的String()方法(通过实现fmt.Stringer接口),而*S**S等指针类型没有默认实现。这是正确的。

  2. *S定义String()方法的影响: 如果你只为*S定义String()方法,那么:

    • *S类型会调用你定义的方法
    • S类型仍然调用默认的String()方法 这是因为方法集规则:非指针类型S的方法集只包含值接收者的方法,而指针类型*S的方法集包含所有值接收者和指针接收者的方法。
  3. S定义String()方法的好处: 如果你为S(值接收者)定义String()方法:

    • S类型会调用你的自定义方法
    • *S类型也会调用你的自定义方法(通过Go的自动解引用) 这是正确的,因为指针类型可以自动调用值接收者的方法。
  4. 关于**S类型: 对于**S类型,你为S定义的String()方法不会被调用,这与第一条陈述并不矛盾。原因如下:

package main

import "fmt"

type S struct {
    value int
}

// 为S定义String()方法(值接收者)
func (s S) String() string {
    return fmt.Sprintf("S{value: %d}", s.value)
}

func main() {
    s := S{value: 42}
    ps := &s
    pps := &ps
    
    fmt.Println("s:", s)           // 调用S.String()
    fmt.Println("ps:", ps)         // 调用S.String()(自动解引用)
    fmt.Println("pps:", pps)       // 不会调用S.String()
    
    // 明确调用String()方法
    fmt.Println("s.String():", s.String())
    fmt.Println("ps.String():", ps.String())
    // fmt.Println("pps.String():", pps.String()) // 编译错误:pps没有String()方法
}

关键点解释

  • **S类型的方法集是空的,因为它不是S*S
  • Go的方法调用只自动解引用一层指针
  • **S无法自动转换为*S来调用方法

更完整的示例

package main

import "fmt"

type S struct {
    value int
}

// 为*S定义String()方法
func (s *S) String() string {
    if s == nil {
        return "nil"
    }
    return fmt.Sprintf("*S{value: %d}", s.value)
}

func main() {
    s := S{value: 42}
    ps := &s
    pps := &ps
    
    fmt.Println("s:", s)           // 使用默认String()方法
    fmt.Println("ps:", ps)         // 调用*S.String()
    fmt.Println("pps:", pps)       // 不会调用任何String()方法
    
    // 类型断言示例
    var stringer fmt.Stringer
    stringer = ps                  // 成功:*S实现了Stringer
    // stringer = pps               // 失败:**S没有实现Stringer
    fmt.Println("stringer:", stringer)
}

总结

  • 只有S类型有默认的String()方法
  • S定义String()方法是最佳实践,因为它对S*S都有效
  • **S及更深层的指针不会自动调用S的方法,因为它们的方法集不同
  • 方法调用只自动解引用一层指针
回到顶部