Golang如何打印带指针的结构体值

18
package main

import "fmt"

type A struct {
    a int32
    B *B
}
type B struct {
    b int32
}

func main() {
    a := &A{
        a: 1,
        B: &B{
            b: 2,
        },
    }
    fmt.Printf("v ==== %+v \n", a)
}


//ret: v ==== &{a:1 B:0xc42000e204}
//??? how to print B's content but not pointer

1
顺便说一下,在这个网站(以及其他网站)中,仅仅粘贴代码而不加任何解释是不好的做法。(顺便问一下@JimB,你是如何在问题仍然只有代码的情况下编辑它的?我无法编辑。这是你的声望特权吗?) - RayfenWindspear
@RayfenWindspear:https://stackoverflow.com/help/privileges - JimB
5个回答

17

基本上,你必须自己完成。有两种方法可以做到这一点。要么按你想要的方式打印它,要么通过为结构体添加一个func String() string来实现Stringer接口,当你使用格式化符号%v时会调用它。您还可以在格式中引用作为结构体的每个值。

实现Stringer接口是始终获得所需结果的最可靠方法。而且每个结构体只需要执行一次,而不是每次打印时都需要执行。

https://play.golang.org/p/PKLcPFCqOe

package main

import "fmt"

type A struct {
    a int32
    B *B
}

type B struct{ b int32 }

func (aa *A) String() string {
    return fmt.Sprintf("A{a:%d, B:%v}",aa.a,aa.B)
}

func (bb *B) String() string {
    return fmt.Sprintf("B{b:%d}",bb.b)
}

func main() {
    a := &A{a: 1, B: &B{b: 2}}

    // using the Stringer interface
    fmt.Printf("v ==== %v \n", a)

    // or just print it yourself however you want.
    fmt.Printf("v ==== A{a:%d, B:B{b:%d}}\n", a.a, a.B.b)

    // or just reference the values in the struct that are structs themselves
    // but this can get really deep
    fmt.Printf("v ==== A{a:%d, B:%v}", a.a, a.B)
}

2
请注意,第二个选项在 B 为空时会出现 panic 错误,而第一个和第三个选项将打印 <nil> - ain
@ain 又是 Stringer 实现更优秀的另一个原因。 - RayfenWindspear

11
当你涉及到更大的结构体时,编写一堆自定义字符串函数会变得很麻烦。Goconvey目前使用以下项目来显示任意大小的结构体的差异和预期/实际输出: https://github.com/luci/go-render/blob/master/render/render.go#L51。它包括显示指针值。
如果你需要将输出作为代码重复使用(比如fmt.Printf("%#v", a)但包含指针值),我有一个以上项目的分支版本,它将呈现完整嵌套指针作为可重用代码:
package main

import (
    "fmt"
    "github.com/gdexlab/go-render/render"
)

type A struct {
    a int32
    B *B
}
type B struct {
    b int32
}

func main() {
    a := &A{
        a: 1,
        B: &B{
            b: 2,
        },
    }
    output := render.AsCode(a)
    fmt.Println(output)

}
// outputs: "&A{a:1, B:&B{b:2}}" compared to initial version of "&{a:1 B:0xc42000e204}"

Go Playground 示例: https://play.golang.org/p/tcfJYb0NnVf


8
另一个简单的解决方案是使用编组打印结构体。这仅适用于在结构体内部将首字母大写的导出(公共)变量。
package main

import (
    "fmt"
    "gopkg.in/yaml.v2"
    "encoding/json"
)

type A struct {
    Aa int32
    B *B
}
type B struct {
    Bb int32
}
func main() {
    a := &A{
        Aa: 1,
        B: &B{
            Bb: 2,
        },
    }
    aJSON, _ := json.Marshal(a)
    fmt.Printf("JSON Print - \n%s\n", string(aJSON))


    aYAML, _ := yaml.Marshal(a)
    fmt.Printf("YAML Print - \n%s\n", string(aYAML))
}

输出:-

JSON Print - 
{"Aa":1,"B":{"Bb":2}}
YAML Print - 
aa: 1
b:
  bb: 2

如果您需要多次打印结构体,请按照以下方式实现Stringer接口:
package main

import (
    "fmt"
    "gopkg.in/yaml.v2"
)

type A struct {
    Aa int32
    B *B
}

func (a A) String() string {
    bytes, _ := yaml.Marshal(a)
    return string(bytes)
}

type B struct {
    Bb int32
}

func main() {
    a := &A{
        Aa: 1,
        B: &B{
            Bb: 2,
        },
    }

    fmt.Printf("YAML Print - \n%+v\n", a)
}

输出 -

YAML Print - 
aa: 1
b:
  bb: 2

3
这对像我这样的初学者来说是一个简单得多的解决方案...非常感谢!!! - vijayakumarpsg587
1
这太棒了。非常感谢,当调试时很容易找到。 - LFMekz

3
使用 fmt 和 reflect。
package main

import (
    "fmt"
    "reflect"
)

type A struct {
    a int32
    B *B
}
type B struct {
    b int32
}

func main() {
    a := &A{
        a: 1,
        B: &B{
            b: 2,
        },
    }
    fmt.Printf("%s\n", GetGoString(a)) // output: &A{a: 1, B: &B{b: 2}}
}

func GetGoString(v interface{}) string {
    return getGoString(reflect.ValueOf(v))
}

func getGoString(v reflect.Value) string {
    switch v.Kind() {
    case reflect.Invalid:
        return "nil"
    case reflect.Struct:
        t := v.Type()
        out := getTypeString(t) + "{"
        for i := 0; i < v.NumField(); i++ {
            if i > 0 {
                out += ", "
            }
            fieldValue := v.Field(i)
            field := t.Field(i)
            out += fmt.Sprintf("%s: %s", field.Name, getGoString(fieldValue))
        }
        out += "}"
        return out
    case reflect.Interface, reflect.Ptr:
        if v.IsZero() {
            return fmt.Sprintf("(%s)(nil)", getTypeString(v.Type()))
        }
        return "&" + getGoString(v.Elem())
    case reflect.Slice:
        out := getTypeString(v.Type())
        if v.IsZero() {
            out += "(nil)"
        } else {
            out += "{"
            for i := 0; i < v.Len(); i++ {
                if i > 0 {
                    out += ", "
                }
                out += getGoString(v.Index(i))
            }
            out += "}"
        }
        return out
    default:
        return fmt.Sprintf("%#v", v)
    }
}

func getTypeString(t reflect.Type) string {
    if t.PkgPath() == "main" {
        return t.Name()
    }
    return t.String()
}

关于映射(case reflect.Map),也缺乏关于断言的说明。 - lupguo

2

实现Stringer接口以自定义type

  • 不需要外部包
  • 不需要将类型包装在其他类型中

示例:

package main

import "fmt"

type A struct {
    B *B `json:"b"`
}

type B int

func (b *B) String() string {
    if b == nil {
        return "nil"
    }

    return fmt.Sprintf("%d", *b)
}

func main() {
    var a A
    fmt.Printf("a: %+v\n", a)
    a.B = B(3)
    fmt.Printf("a: %+v\n", a)
}

输出:

a: {B:nil}
a: {B:3}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接