Go
golang
Go2Day 7

fmt.Printfなんかこわくない

はじめに

Goのfmtパッケージのprintf系の関数

  • Fprintf
  • Printf
  • Sprintf

のフォーマットの指定方法についてまとめました。

Goでは書式指定子 %... のことを verb と表記しています。

すべての型に使えるverb

%v

値のデフォルトのフォーマットでの表現を出力する。

基本型の場合

verb
論理値(bool) %t
符号付き整数(int, int8など) %d
符号なし整数(uint, uint8など) %d
浮動小数点数(float64など) %g
複素数(complex128など) %g
文字列(string) %s
チャネル(chan) %p
ポインタ(pointer) %p
package main

import (
    "fmt"
)

func main() {
    fmt.Printf("%v\n", true)
    fmt.Printf("%v\n", 42)
    fmt.Printf("%v\n", uint(42))
    fmt.Printf("%v\n", 12.345)
    fmt.Printf("%v\n", 1-2i)
    fmt.Printf("%v\n", "寿司🍣Beer🍺")
    fmt.Printf("%v\n", make(chan bool))
    fmt.Printf("%v\n", new(int))
}
true
42
42
12.345
(1-2i)
寿司🍣Beer🍺
0x434080
0x416028

https://goplay.space/#z9gPcDYzgkV

コンポジット型の場合

以下のようになり、さらに各要素に対して再帰的に %v でのフォーマットをしたものが結果として出力される。

フォーマット
構造体(struct) {フィールド1 フィールド2 ...}
構造体のポインタ &{フィールド1 フィールド2 ...}
配列・スライス(array, slice) [要素1 要素2 ...]
配列・スライスのポインタ &[要素1 要素2 ...]
マップ(map) map[キー1:値1 キー2:値2 ...]
マップのポインタ &map[キー1:値1 キー2:値2 ...]
package main

import (
    "fmt"
    "net/http"
)

func main() {
    fmt.Printf("%v\n", http.Client{})
    fmt.Printf("%v\n", &http.Client{})
    fmt.Printf("%v\n", [...]int{1, 2, 3})
    fmt.Printf("%v\n", &[...]int{1, 2, 3})
    fmt.Printf("%v\n", []int{1, 2, 3})
    fmt.Printf("%v\n", &[]int{1, 2, 3})
    fmt.Printf("%v\n", map[string]int{"寿司": 1000, "ビール": 500})
    fmt.Printf("%v\n", &map[string]int{"寿司": 1000, "ビール": 500})
}
{<nil> <nil> <nil> 0s}
&{<nil> <nil> <nil> 0s}
[1 2 3]
&[1 2 3]
[1 2 3]
&[1 2 3]
map[寿司:1000 ビール:500]
&map[寿司:1000 ビール:500]

https://goplay.space/#aoO0hoy_p6a

%+v

%vと同じだが、構造体の場合にフォールド名を出力する。

package main

import (
    "fmt"
    "net/http"
)

func main() {
    fmt.Printf("%v\n", http.Client{})
    fmt.Printf("%+v\n", http.Client{})
}
{<nil> <nil> <nil> 0s}
{Transport:<nil> CheckRedirect:<nil> Jar:<nil> Timeout:0s}

https://goplay.space/#lWvspeqs7ua

%#v

値のGoの文法での表現を出力する。

package main

import (
    "fmt"
    "net/http"
)

func main() {
    fmt.Printf("%#v\n", true)
    fmt.Printf("%#v\n", 42)
    fmt.Printf("%#v\n", uint(42))
    fmt.Printf("%#v\n", 12.345)
    fmt.Printf("%#v\n", 1-2i)
    fmt.Printf("%#v\n", "寿司🍣Beer🍺")
    fmt.Printf("%#v\n", make(chan bool))
    fmt.Printf("%#v\n", new(int))
    fmt.Printf("\n")
    fmt.Printf("%#v\n", http.Client{})
    fmt.Printf("%#v\n", &http.Client{})
    fmt.Printf("%#v\n", [...]int{1, 2, 3})
    fmt.Printf("%#v\n", &[...]int{1, 2, 3})
    fmt.Printf("%#v\n", []int{1, 2, 3})
    fmt.Printf("%#v\n", &[]int{1, 2, 3})
    fmt.Printf("%#v\n", map[string]int{"寿司": 1000, "ビール": 500})
    fmt.Printf("%#v\n", &map[string]int{"寿司": 1000, "ビール": 500})
}
true
42
0x2a
12.345
(1-2i)
"寿司🍣Beer🍺"
(chan bool)(0x834100)
(*int)(0x816260)

http.Client{Transport:http.RoundTripper(nil), CheckRedirect:(func(*http.Request, []*http.Request) error)(nil), Jar:http.CookieJar(nil), Timeout:0}
&http.Client{Transport:http.RoundTripper(nil), CheckRedirect:(func(*http.Request, []*http.Request) error)(nil), Jar:http.CookieJar(nil), Timeout:0}
[3]int{1, 2, 3}
&[3]int{1, 2, 3}
[]int{1, 2, 3}
&[]int{1, 2, 3}
map[string]int{"寿司":1000, "ビール":500}
&map[string]int{"寿司":1000, "ビール":500}

https://goplay.space/#fxNuoQ2vBUj

%T

値の型のGoの文法での表現を出力する。

package main

import (
    "fmt"
    "net/http"
)

func main() {
    fmt.Printf("%T\n", true)
    fmt.Printf("%T\n", 42)
    fmt.Printf("%T\n", uint(42))
    fmt.Printf("%T\n", 12.345)
    fmt.Printf("%T\n", 1-2i)
    fmt.Printf("%T\n", "寿司🍣Beer🍺")
    fmt.Printf("%T\n", make(chan bool))
    fmt.Printf("%T\n", new(int))
    fmt.Printf("\n")
    fmt.Printf("%T\n", http.Client{})
    fmt.Printf("%T\n", &http.Client{})
    fmt.Printf("%T\n", [...]int{1, 2, 3})
    fmt.Printf("%T\n", &[...]int{1, 2, 3})
    fmt.Printf("%T\n", []int{1, 2, 3})
    fmt.Printf("%T\n", &[]int{1, 2, 3})
    fmt.Printf("%T\n", map[string]int{"寿司": 1000, "ビール": 500})
    fmt.Printf("%T\n", &map[string]int{"寿司": 1000, "ビール": 500})
}
bool
int
uint
float64
complex128
string
chan bool
*int

http.Client
*http.Client
[3]int
*[3]int
[]int
*[]int
map[string]int
*map[string]int

https://goplay.space/#mpPV8cOoc0n

%%

%そのものを出力したい場合に使う。

論理値に使えるverb

%t

truefalse

package main

import (
    "fmt"
)

func main() {
    fmt.Printf("%t\n", true)
    fmt.Printf("%t\n", false)
}
true
false

https://goplay.space/#dPg7ImEakE3

整数に使えるverb

%d

10進数での表現

%b

2進数での表現

%o

8進数での表現

%x

16進数での表現(a-fは小文字)

%X

16進数での表現(A-Fは大文字)

%c

Unicodeコードポイントに対応する文字

%q

対応する文字をシングルクォート'で囲んだ文字列

package main

import (
    "fmt"
)

func main() {
    answer := 42
    fmt.Printf("%b\n", answer)
    fmt.Printf("%c\n", answer)
    fmt.Printf("%d\n", answer)
    fmt.Printf("%o\n", answer)
    fmt.Printf("%q\n", answer)
    fmt.Printf("%x\n", answer)
    fmt.Printf("%X\n", answer)
    fmt.Printf("%U\n", answer)
}
101010
*
42
52
'*'
2a
2A
U+002A

https://goplay.space/#uOjc6CIP2Cc

浮動小数点数・複素数に使えるverb

%b

小数点なしの指数表記 指数は2の累乗

%e

指数表記

%E

%eeEで表記される

%f, %F

指数表記なし

%g

指数が大きい場合は%eそうでなければ%f

%G

指数が大きい場合は%Eそうでなければ%F

package main

import (
    "fmt"
)

func main() {
    f := 12.345
    fmt.Printf("%b\n", f)
    fmt.Printf("%e\n", f)
    fmt.Printf("%E\n", f)
    fmt.Printf("%f\n", f)
    fmt.Printf("%F\n", f)
    fmt.Printf("%g\n", f)
    fmt.Printf("%G\n", f)
    fmt.Printf("%g\n", 12345678.9)
    fmt.Printf("%G\n", 12345678.9)
}
6949617174986097p-49
1.234500e+01
1.234500E+01
12.345000
12.345000
12.345
12.345
1.23456789e+07
1.23456789E+07

https://goplay.space/#x97S-f6fLcn

文字列([]byteも同じ)に使えるverb

%s

そのままの出力

%q

Goの文法上のエスケープをした文字列

%x

1バイトにつき2文字の16進数での表現(a-fは小文字)

%X

%xと同じだが、A-Fが大文字

package main

import (
    "fmt"
)

func main() {
    s := "寿司🍣Beer🍺"
    fmt.Printf("%s\n", s)
    fmt.Printf("%q\n", s)
    fmt.Printf("%x\n", s)
    fmt.Printf("%X\n", s)
}
寿司🍣Beer🍺
"寿司🍣Beer🍺"
e5afbfe58fb8f09f8da342656572f09f8dba
E5AFBFE58FB8F09F8DA342656572F09F8DBA

https://goplay.space/#4z7ki7m7BjD

width, precision

verbの直前に整数でwidthを指定することができる。
widthはrune単位で数えられる出力する文字列の長さ。
指定しない場合、値を表現するのに必要な長さになる。

precisionはwidthの直後に.を付け、その後に整数で指定できる。
.がない場合、デフォルトのprecisionになる。
.があるが数値の指定がない場合、precisionは0になる。

width, precisionには整数の代わりに*を指定することもでき、その場合次の引数の値を指定したことになる。
その場合、その引数の値は整数である必要がある。

  • 文字列([]byteでも同じ)の場合: precisionはフォーマット対象にする文字列の長さの上限。文字列が長すぎる場合は途中で切られる。長さはrune単位だが、%x%Xでフォーマットされる場合はバイト単位。
  • 浮動小数点数の場合:

    • width: 文字列で表現される際の最小の文字数
    • precision:
      • %e, %f, 小数点以下の桁数
      • %g, %G有効桁数の最大値
      • デフォルト:
        • %e, %f, %#g: 6
        • %g: 数値を表すのに必要な桁数
  • 複素数の場合: widthとprecisionの値は実数部と虚数部にそれぞれ適用され、結果は()で囲われる。

package main

import (
    "fmt"
)

func main() {
    f := 12.345
    fmt.Printf("%f\n", f)
    fmt.Printf("%12f\n", f)
    fmt.Printf("%12.2f\n", f)
    fmt.Printf("%.2f\n", f)
    fmt.Printf("%12.f\n", f)
    fmt.Printf("%e\n", f)
    fmt.Printf("%#g\n", f)
    fmt.Printf("%g\n", f)

    fmt.Printf("%f", 1-2i)
}
12.345000
   12.345000
       12.35
12.35
          12
1.234500e+01
12.3450
12.345
(1.000000-2.000000i)

https://goplay.space/#qbrm8OIcZ0V

flag

width, precisionの他にもverbの直前に置くことでフォーマットを変えられるflagがあります。

+

  • 数値の場合: 正でも符号(+)を出力する
  • %qの場合: ASCII文字だけで出力する
package main

import (
    "fmt"
)

func main() {
    fmt.Printf("%d\n", 42)
    fmt.Printf("%+d\n", 42)
    fmt.Printf("%q\n", 945)
    fmt.Printf("%+q\n", 945)
    fmt.Printf("%q\n", "寿司🍣Beer🍺")
    fmt.Printf("%+q\n", "寿司🍣Beer🍺")
}
42
+42
'α'
'\u03b1'
"寿司🍣Beer🍺"
"\u5bff\u53f8\U0001f363Beer\U0001f37a"

https://goplay.space/#Mqe5nGa0Af_E

-

左詰めにする

package main

import (
    "fmt"
)

func main() {
    fmt.Printf("%5d\n", 42)
    fmt.Printf("%-5d\n", 42)
    fmt.Printf("%10s\n", "寿司🍣Beer🍺")
    fmt.Printf("%-10s\n", "寿司🍣Beer🍺")
}
   42
42   
  寿司🍣Beer🍺
寿司🍣Beer🍺

https://goplay.space/#0eC0HaeMk-B

#

デフォルトとは異なるフォーマットにする

  • 8進数の場合(%#o): 先頭に0を付ける
  • 16進数の場合(%#x): 先頭に0xを付ける
  • 16進数(大文字)の場合(%#X): 先頭に0Xを付ける
  • ポインタの場合(%#p): 先頭の0xを付けない
  • %qの場合: strconv.CanBackquoteがtrueを返すならraw文字列を出力する
  • %e, %E, %f, %F, %g, %Gの場合: 必ず小数点を付ける
  • %g, %Gの場合: 末尾の0を省略しない
  • %Uの場合: U+0078 'x'の形式で出力する
package main

import (
    "fmt"
)

func main() {
    answer := 42
    fmt.Printf("%o\n", answer)
    fmt.Printf("%#o\n", answer)
    fmt.Printf("%x\n", answer)
    fmt.Printf("%#x\n", answer)
    fmt.Printf("%X\n", answer)
    fmt.Printf("%#X\n", answer)
    fmt.Printf("%p\n", &answer)
    fmt.Printf("%#p\n", &answer)
    fmt.Printf("%q\n", "go")
    fmt.Printf("%#q\n", "go")
    fmt.Printf("%q\n", "`go`")
    fmt.Printf("%#q\n", "`go`")
    fmt.Printf("%.f\n", 12.345)
    fmt.Printf("%#.f\n", 12.345)
    fmt.Printf("%g\n", 12.345)
    fmt.Printf("%#g\n", 12.345)
    fmt.Printf("%U\n", answer)
    fmt.Printf("%#U\n", answer)
}
52
052
2a
0x2a
2A
0X2A
0x416020
416020
"go"
`go`
"`go`"
"`go`"
12
12.
12.345
12.3450
U+002A
U+002A '*'

https://goplay.space/#2YTrvJY308B

(スペース)

  • 数値の場合: 符号のためのスペースを空ける
  • 文字列をバイト単位で表現する場合: それぞれのバイトの間にスペースを空ける
package main

import (
    "fmt"
)

func main() {
    fmt.Printf("%d\n", 42)
    fmt.Printf("% d\n", 42)
    fmt.Printf("%x\n", "寿司🍣Beer🍺")
    fmt.Printf("% x\n", "寿司🍣Beer🍺")
    fmt.Printf("%X\n", "寿司🍣Beer🍺")
    fmt.Printf("% X\n", "寿司🍣Beer🍺")
}
42
 42
e5afbfe58fb8f09f8da342656572f09f8dba
e5 af bf e5 8f b8 f0 9f 8d a3 42 65 65 72 f0 9f 8d ba
E5AFBFE58FB8F09F8DA342656572F09F8DBA
E5 AF BF E5 8F B8 F0 9F 8D A3 42 65 65 72 F0 9F 8D BA

https://goplay.space/#_KM5ZDuGfM2

0

スペースではなく、0で埋める

数値の場合: 0埋めは符号のあと

package main

import (
    "fmt"
)

func main() {
    fmt.Printf("%10s\n", "寿司🍣Beer🍺")
    fmt.Printf("%010s\n", "寿司🍣Beer🍺")
    fmt.Printf("%10.3f\n", -12.345)
    fmt.Printf("%010.3f\n", -12.345)
}
  寿司🍣Beer🍺
00寿司🍣Beer🍺
   -12.345
-00012.345

https://goplay.space/#tGs_5FUYnVy

[n]引数のインデックス指定

  • verbの直前に[n]の表記でインデックスを指定することで、フォーマットする引数を指定できる
  • *に対しても使える
  • [n]の指定をした以降はn+1番目, n+2番目, ... となる
package main

import (
    "fmt"
)

func main() {
    fmt.Printf("%[2]s %[1]s\n", "寿司🍣", "Beer🍺")
    fmt.Printf("%[3]*.[2]*[1]f\n", 12.345, 2, 8)
    fmt.Printf("%*.*f\n", 8, 2, 12.345)
    fmt.Printf("%s %s %[1]q %q", "寿司🍣", "Beer🍺")
}
Beer🍺 寿司🍣
   12.35
   12.35
寿司🍣 Beer🍺 "寿司🍣" "Beer🍺"

https://goplay.space/#dztaNduIqv0

参考

fmt - The Go Programming Language