LoginSignup
43
18

More than 3 years have passed since last update.

Go言語 構造体をfmt.Printfで表示するときの書式の不思議

Last updated at Posted at 2019-05-09

概要

変数の値を表示するのにfmt.Println()fmt.Printf()をよく使います。自分で定義した構造体ならその構造は分かるのですが、packageで定義されている構造体だと良くわからないし、インタフェースとして使われていると元がどの構造体か分かりません。そこで、fmt.Printfの書式の違いによって表示がどう異なるか調べてみました。最後にreflect.Typeインターフェースの構造体を表示しています。

基本型のみの構造体

main.go
package main

import (
    "fmt"
)

type myStruct struct {
    field1 string
    field2 int
    field3 uint
    field4 *uint
    field5 float64
}

func main() {
    data := uint(987)
    val := &myStruct{"abcd", 123, 456, &data, 7.0}
    fmt.Printf("(%%#v) %#v\n", val)
    fmt.Printf("(%%+v) %+v\n", val)
    fmt.Printf("(%%v)  %v\n", val)
    fmt.Printf("(%%s)  %s\n", val)
    fmt.Printf("(%%x)  %x\n", val)
    fmt.Printf("(%%d)  %d\n", val)
    fmt.Printf("(%%f)  %f\n", val)
}

実行結果

$ go run main.go
(%#v) &main.myStruct{field1:"abcd", field2:123, field3:0x1c8, field4:(*uint)(0xc000070080), field5:7}
(%+v) &{field1:abcd field2:123 field3:456 field4:0xc000070080 field5:7}
(%v)  &{abcd 123 456 0xc000070080 7}
(%s)  &{abcd %!s(int=123) %!s(uint=456) %!s(*uint=0xc000070080) %!s(float64=7)}
(%x)  &{61626364 7b 1c8 c000070080 %!x(float64=7)}
(%d)  &{%!d(string=abcd) 123 456 824634179712 %!d(float64=7)}
(%f)  &{%!f(string=abcd) %!f(int=123) %!f(uint=456) %!f(*uint=0xc000070080) 7.000000}
  • %#v
    • 構造体名が分かる
    • 項目名が分かる
    • 符号なしのときは16進表示
    • 符号ありのときは10進表示
    • ポインタのときはポインタの型が分かる
    • 値が整数のときはint系かfloat系か分からない
  • %+v
    • 項目名が分かる
    • 符号付きか符号なしか分からない
    • 値が整数のときはint系かfloat系か分からない
    • ポインタのときは16進表示
    • 値が整数のときはint系かfloat系か分からない
  • %v
    • 項目名が分からい以外は%+vと同じ
  • %s
    • 項目名が分からない
    • すべての型が分かる。ただし、型の記述が無いものをstringとみなすとする
  • %x
    • 項目名が分からない
    • float系以外は型が分からない
  • %d
    • 項目名が分からない
    • string,float系以外は型が分からない
  • %f
    • 項目名が分からない
    • すべての型が分かる。ただし、float64,float32の区別がつかない

これらを見ると構造体を調べるには、%#v%sの組み合わせが良さそうです。

複雑な型を含む場合

main.go
package main

import (
    "fmt"
)

type type1 struct {
    f1 string
    f2 int
}

type myStruct struct {
    field1 map[string]int
    field2 [2]int
    field3 []uint
    field4 type1
    field5 *type1
    field6 *[2]int
    field7 *[2]*int
}

func main() {
    data1 := make(map[string]int)
    data1["ab"] = 12
    data1["cd"] = 34
    data2 := [2]int{1, 2}
    data3 := type1{"abcd", 789}
    data41 := 5
    data42 := 6
    data4 := [2]*int{&data41, &data42}
    val := &myStruct{data1, data2, []uint{3, 4}, data3, &data3, &data2, &data4}
    fmt.Printf("(%%#v) %#v\n", val)
    fmt.Printf("(%%+v) %+v\n", val)
    fmt.Printf("(%%v)  %v\n", val)
    fmt.Printf("(%%s)  %s\n", val)
    fmt.Printf("(%%x)  %x\n", val)
    fmt.Printf("(%%d)  %d\n", val)
    fmt.Printf("(%%f)  %f\n", val)
}
$ go run main.go
(%#v) &main.myStruct{field1:map[string]int{"ab":12, "cd":34}, field2:[2]int{1, 2}, field3:[]uint{0x3, 0x4}, field4:main.type1{f1:"abcd", f2:789}, field5:(*main.type1)(0xc000066400), field6:(*[2]int)(0xc000070080), field7:(*[2]*int)(0xc00005c1c0)}
(%+v) &{field1:map[ab:12 cd:34] field2:[1 2] field3:[3 4] field4:{f1:abcd f2:789} field5:0xc000066400 field6:0xc000070080 field7:0xc00005c1c0}
(%v)  &{map[ab:12 cd:34] [1 2] [3 4] {abcd 789} 0xc000066400 0xc000070080 0xc00005c1c0}
(%s)  &{map[ab:%!s(int=12) cd:%!s(int=34)] [%!s(int=1) %!s(int=2)] [%!s(uint=3) %!s(uint=4)] {abcd %!s(int=789)} %!s(*main.type1=&{abcd 789}) %!s(*[2]int=&[1 2]) %!s(*[2]*int=&[0xc000070090 0xc000070098])}
(%x)  &{map[6162:c 6364:22] [1 2] [3 4] {61626364 315} c000066400 c000070080 c00005c1c0}
(%d)  &{map[%!d(string=ab):12 %!d(string=cd):34] [1 2] [3 4] {%!d(string=abcd) 789} 824634139648 824634179712 824634098112}
(%f)  &{map[%!f(string=ab):%!f(int=12) %!f(string=cd):%!f(int=34)] [%!f(int=1) %!f(int=2)] [%!f(uint=3) %!f(uint=4)] {%!f(string=abcd) %!f(int=789)} %!f(*main.type1=&{abcd 789}) %!f(*[2]int=&[1 2]) %!f(*[2]*int=&[0xc000070090 0xc000070098])}

ここで面白いのは%s%ffield5,field6,field7はポインタ型ですが、そのポインタが表している型の値も表示しているところです。%s%f以外はポインタの値を表示しているだけです。
さらに、%#vも基本の型の場合は型表示がなかったですが、今度は型表示がされています。

カスタムの型の場合

最初の基本の型をすべてカスタムの型にして実行します。

main.go
package main

import (
    "fmt"
)

type (
    type1 string
    type2 int
    type3 uint
    type4 *uint
    type5 float64
)

type myStruct struct {
    field1 type1
    field2 type2
    field3 type3
    field4 type4
    field5 type5
}

func main() {
    data := uint(987)
    val := &myStruct{"abcd", 123, 456, &data, 7.0}
    fmt.Printf("(%%#v) %#v\n", val)
    fmt.Printf("(%%+v) %+v\n", val)
    fmt.Printf("(%%v)  %v\n", val)
    fmt.Printf("(%%s)  %s\n", val)
    fmt.Printf("(%%x)  %x\n", val)
    fmt.Printf("(%%d)  %d\n", val)
    fmt.Printf("(%%f)  %f\n", val)
}
$ go run main.go
(%#v) &main.myStruct{field1:"abcd", field2:123, field3:0x1c8, field4:(main.type4)(0xc000066080), field5:7}
(%+v) &{field1:abcd field2:123 field3:456 field4:0xc000066080 field5:7}
(%v)  &{abcd 123 456 0xc000066080 7}
(%s)  &{abcd %!s(main.type2=123) %!s(main.type3=456) %!s(main.type4=0xc000066080) %!s(main.type5=7)}
(%x)  &{61626364 7b 1c8 c000066080 %!x(main.type5=7)}
(%d)  &{%!d(main.type1=abcd) 123 456 824634138752 %!d(main.type5=7)}
(%f)  &{%!f(main.type1=abcd) %!f(main.type2=123) %!f(main.type3=456) %!f(main.type4=0xc000066080) 7.000000}

すべてカスタムの型の名称で表示されるため、数値系やポインタや文字列などの区別は付きますが、数値系で値が小さいとビット数までは分かりません。符号の有無とfloat系ということは分かりそうです。

構造体がString()stringのメソッドを持っている場合

main.go
package main

import (
    "fmt"
)

type myStruct struct {
    field1 string
    field2 int
    field3 uint
    field4 *uint
    field5 float64
}

func (m *myStruct) String() string {
    return "called String()"
}

func main() {
    data := uint(987)
    val := &myStruct{"abcd", 123, 456, &data, 7.0}
    fmt.Printf("(%%#v) %#v\n", val)
    fmt.Printf("(%%+v) %+v\n", val)
    fmt.Printf("(%%v)  %v\n", val)
    fmt.Printf("(%%s)  %s\n", val)
    fmt.Printf("(%%x)  %x\n", val)
    fmt.Printf("(%%d)  %d\n", val)
    fmt.Printf("(%%f)  %f\n", val)
}
$ go run main.go
(%#v) &main.myStruct{field1:"abcd", field2:123, field3:0x1c8, field4:(*uint)(0xc000070080), field5:7}
(%+v) called String()
(%v)  called String()
(%s)  called String()
(%x)  63616c6c656420537472696e672829
(%d)  &{%!d(string=abcd) 123 456 824634179712 %!d(float64=7)}
(%f)  &{%!f(string=abcd) %!f(int=123) %!f(uint=456) %!f(*uint=0xc000070080) 7.000000}

%+v,%v,%s,%xString()を呼び出していますので、このときは%#v,%sの組み合わせではく%#v,%fの組み合わせで表示するのが良さそうです。同様の働きをするのはError() stringです。

インターフェースの例

reflect.TypeOf()reflect.Typeというインターフェースを返します。

main.go
package main

import (
    "fmt"
    "reflect"
)

func main() {
    val := reflect.TypeOf(1)
    fmt.Printf("(%%#v) %#v\n", val)
    fmt.Printf("(%%+v) %+v\n", val)
    fmt.Printf("(%%v)  %v\n", val)
    fmt.Printf("(%%s)  %s\n", val)
    fmt.Printf("(%%x)  %x\n", val)
    fmt.Printf("(%%d)  %d\n", val)
    fmt.Printf("(%%f)  %f\n", val)
}
$ go run main.go
(%#v) &reflect.rtype{size:0x8, ptrdata:0x0, hash:0xf75371fa, tflag:0x7, align:0x8, fieldAlign:0x8, kind:0x82, alg:(*reflect.typeAlg)(0x566d30), gcdata:(*uint8)(0x4db70c), str:1059, ptrToThis:46976}
(%+v) int
(%v)  int
(%s)  int
(%x)  696e74
(%d)  &{8 0 4149441018 7 8 8 130 5664048 5093132 1059 46976}
(%f)  &{%!f(uintptr=8) %!f(uintptr=0) %!f(uint32=4149441018) %!f(reflect.tflag=7) %!f(uint8=8) %!f(uint8=8) %!f(uint8=130) %!f(*reflect.typeAlg=&{0x451100 0x402ef0}) %!f(*uint8=0x4db70c) %!f(reflect.nameOff=1059) %!f(reflect.typeOff=46976)}

reflect.Typeの元はreflect.rtypeという構造体です。%fで構造体の各項目の型について多くの情報が得られます。また、reflect.rtypeString() stringを持っているようです。

43
18
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
43
18