概要
変数の値を表示するのにfmt.Println()
やfmt.Printf()
をよく使います。自分で定義した構造体ならその構造は分かるのですが、packageで定義されている構造体だと良くわからないし、インタフェースとして使われていると元がどの構造体か分かりません。そこで、fmt.Printf
の書式の違いによって表示がどう異なるか調べてみました。最後にreflect.Type
インターフェースの構造体を表示しています。
基本型のみの構造体
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
の組み合わせが良さそうです。
複雑な型を含む場合
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
と%f
でfield5
,field6
,field7
はポインタ型ですが、そのポインタが表している型の値も表示しているところです。%s
と%f
以外はポインタの値を表示しているだけです。
さらに、%#v
も基本の型の場合は型表示がなかったですが、今度は型表示がされています。
カスタムの型の場合
最初の基本の型をすべてカスタムの型にして実行します。
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のメソッドを持っている場合
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
,%x
はString()
を呼び出していますので、このときは%#v
,%s
の組み合わせではく%#v
,%f
の組み合わせで表示するのが良さそうです。同様の働きをするのはError() string
です。
インターフェースの例
reflect.TypeOf()
はreflect.Type
というインターフェースを返します。
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.rtype
はString() string
を持っているようです。