🦦

Go の fmt.Print 系関数は上手に使い分けたい

2022/04/16に公開

Go の標準パッケージである fmt パッケージは、主にフォーマット処理を含めた入出力のための機能がまとめられたパッケージです。

import "fmt"

fmt パッケージに用意された9つの Print 系関数(名前に「print」を持つ関数)は、初学者から上級者まで全 Go プログラマがデバッグやエラーハンドリングなどで使用するかと思いますが、それら Print 系関数はどれも微妙に異なる動作をします。
この記事では、それぞれの関数の違いとそれらがどのような場面で最適であるかを明確にします。

Print 系関数は3つのグループに分類できる

まず、fmtパッケージの Print 系関数は以下の3つのグループに分類することができます。

グループ 関数 用途
名前が「P」から始まる関数グループ fmt.Print, fmt.Printf, fmt.Println 標準出力への出力
名前が「S」から始まる関数グループ fmt.Sprint, fmt.Sprintf, fmt.Sprintln 文字列生成
名前が「F」から始まる関数グループ fmt.Fprint, fmt.Fprintf, fmt.Fprintln io.Writer インターフェースを満たす任意の出力ストリームに出力

状況に応じた最適な出力方法の選定にはこの分類の理解が重要です
それぞれ解説していきます。

まずはじめに

全グループに共通して存在する名前の末尾が「f」で終わる関数は、フォーマットを使用することを表しています。
長い文字列を使用したい場面では、その文字列そのものを使うよりもフォーマットを使いその文字列を生成することで可読性や再利用性が高まり、メンテナンスしやすくなります。

さて、フォーマット文字列の中では「%d」などの書式指定子と呼ばれる記号を使用して、データの埋め込む場所とその書式(型)を指定します。

// 標準出力へフォーマットした文字列を出力(名前が「P」から始まる関数グループ)
name := Tom
fmt.Printf("My name is %s.", name)
// 出力
"My name is Tom."


// フォーマットした文字列を生成(名前が「S」から始まる関数グループ)
word := "Go言語"
s := fmt.Spintf("I like %s.\n", word)
// s == "I like Go言語."


// io.Writer インターフェースを満たす任意の出力ストリームへフォーマットした文字列を出力(名前が「F」から始まる関数グループ)
f, err := os.Create("foo.text")  //「foo.text」というファイルを作成し変数に格納
if err != nil {
	log.Fatal(err)
}
defer f.Close()

fmt.Fprintf(f, "%s!\n", "hello")

// foo.textの内容
hello!

また、文字だけでなく数値や浮動小数点数などのデータを整形して出力することも可能です

// 標準出力へフォーマットした文字列を出力(名前が「P」から始まる関数グループ)
number := 4
fmt.Printf("%d * %d = %d\n", number, number, number*number)
// 出力
"4 * 4 = 16"


// フォーマットした文字列を生成(名前が「S」から始まる関数グループ)
s := fmt.Spintf("%.2f\n", 1.23456)
// s == "1.23"


// io.Writer インターフェースを満たす任意の出力ストリームへフォーマットした文字列を出力(名前が「F」から始まる関数グループ)
f, err := os.Create("foo.text")  //「foo.text」というファイルを作成し変数に格納
if err != nil {
	log.Fatal(err)
}
defer f.Close()

fmt.Fprintf(f, "%05d|%05d\n", 121, 33)

// foo.textの内容
00121|00033

書式指定子一覧

それぞれの書式指定子の意味は以下の通りです。

文字列型

書式指定子 出力 補足
%s "Go言語" "Go言語"
%10s "Go言語" "          Go言語" 文字数10になるように右詰め
%-10s "Go言語" "Go言語          " 文字数10になるように左詰め
%q "Go言語" ""Go言語"" ダブルクオート付き
%x "Go言語" "476fe8a880e8aa9e" 16進数(a-f 小文字)
%X "Go言語" "476FE8A880E8AA9E" 16進数(A-F 大文字)

整数型・浮動小数点型

書式指定子 出力 補足
%d -1 "-1" 整数を表示
%+d 1 "+1" 符号を表示
%5d 123 "  123" 指定した桁数で右詰め
%-5d 123 "123  " 指定した桁数で左詰め
%05d 123 "00123" 指定した桁数で右詰めし、0で埋める
%c 65 "A" Unicode文字
%q 36938 '遊' シングルクオートで囲われたUnicode文字
%o 100 "144" 8進数
%#o 100 "0144" 0付き8進数
%x 10203 "27db" 16進数(a-f 小文字)
%X 10203 "27DB" 16進数(A-F 大文字)
%#x 10203 "0x27db" 0x付き16進数(a-f 小文字)
%#X 10203 "0x27DB" 0x付き16進数(A-F 大文字)
%U 36938 "U+904A" Unicodeコードポイント
%#U 36938 "U+904A '遊'" Unicodeコードポイントと文字
%b 250 "11111010" 2進数
%f 123.456 "123.456000" 実数表現
%.2f 123.456 "123.45" 小数点以下を2桁に丸める

その他

書式指定子 出力 補足
%t true true bool 型専用
%p 任意のポインタ型 "0xc82000a380 ポインタのアドレス16進数
%T 任意の型 "int" Go の型を表示

%v

%v は Go のさまざまな型の値を柔軟に出力できる、トランプで言う Joker みたいな書式指定子です
マップや配列、スライスやチャネルなどについても %v で表せます。

書式指定子 出力 補足
%v interface{} 何でも受け付けます

標準出力への出力関数(名前が「P」から始まる関数グループ)

名前が「P」から始まる関数グループの関数は通常の標準出力を行います

fmt.Print(a ...interface{}) (n int, err error)
fmt.Printf(format string, a ...interface{}) (n int, err error)
fmt.Println(a ...interface{}) (n int, err error)

fmt.Print

任意の数の interface{} 型の引数をとり、さまざまなデータ型を末尾の改行無しで出力します
データ同士を「,(カンマ)」で区切って複数出力することも可能ですが、文字列型と隣接するデータは間にスペースが付きません

fmt.Print("アイウエオ")  // アイウエオ
fmt.Print(struct{ X, Y int }{ 1, 2 }) // { 1 2 }
fmt.Print(1, 123, "Go言語", struct{ X, Y int }{ 1, 2 })  // 1 123Go言語{ 1 2 }

fmt.Printf

第1引数に文字列でフォーマットを指定して、第2引数以降に埋め込む任意のデータを並べて出力します

fmt.Printf("My name is %s, %s\n", "Tom", "Hello")  // "My name is Tom, Hello"

fmt.Println

任意の数の interface{} 型の引数をとり、さまざまなデータ型を末尾に改行を付けて出力します
fmt.Print 同様、文字同士を「,(カンマ)」で区切って複数出力することも可能ですが、文字列型と隣接するデータは間にスペースが付きません

fmt.Println("アイウエオ")  // アイウエオ(末尾に改行付き)
fmt.Println(struct{ X, Y int }{ 1, 2 })  // { 1 2 }(末尾に改行付き)
fmt.Println(1, 123, "Go言語", struct{ X, Y int }{ 1, 2 })  // 1 123Go言語{ 1 2 }(末尾に改行付き)

文字列生成関数(名前が「S」から始まる関数グループ)

名前が「S」から始まる関数グループの関数は文字列を生成します。
この関数単体では標準出力などはできないため、通常のコード内では変数などにデータを格納するためなどに使います

fmt.Sprint(a ...interface{}) string
fmt.Sprintf(format string, a ...interface{}) string
fmt.Sprintln(a ...interface{}) string

fmt.Sprint

任意の数の interface{} 型の引数をとり、文字列型を生成します
データ同士を「,(カンマ)」で区切って複数指定することも可能ですが、文字列型と隣接するデータは間にスペースが付きません

s := fmt.Sprint("アイウエオ")  // s = アイウエオ
s := fmt.Sprint(struct{ X, Y int }{ 1, 2 })  // s = { 1 2 }
s := fmt.Sprint(1, 123, "Go言語", struct{ X, Y int }{ 1, 2 })  // s = 1 123Go言語{1 2}

fmt.Sprintf

第1引数に文字列でフォーマットを指定して、第2引数以降に埋め込む任意のデータを並べて文字列型を生成します

s := fmt.Sprintf("My name is %s\n", "Tom")  // s = "My name is Tom"

fmt.Sprintln

任意の数の interface{} 型の引数をとり、文字列型を生成します
文字同士を「,(カンマ)」で区切って複数指定することも可能ですが、文字列型と隣接するデータは間にスペースが付きません

s := fmt.Sprintln("アイウエオ")  // s = アイウエオ(末尾に改行付き)
s := fmt.Sprintln(123)  // s = 123(末尾に改行付き)
s := fmt.Sprintln(1, 123, "Go言語", struct{ X, Y int }{ 1, 2 })  // s = 1 123Go言語{ 1 2 }(末尾に改行付き)

ファイル(File)への出力関数(名前が「F」から始まる関数グループ)

名前が「F」から始まる関数グループの関数は io.Writer インターフェースを満たす任意の出力ストリーム(例えば標準出力やファイル、HTTPレスポンスボディ)への出力を行います。

fmt.Fprint(w io.Writer, a ...interface{}) (n int, err error)
fmt.Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)
fmt.Fprintln(w io.Writer, a ...interface{}) (n int, err error)

io.Writer の内容は以下の通りです。

type Writer interface {
  Write(p []byte) (n int, err error)
}

fmt.Fprint

第1引数に io.Writer型 を満たす型をとり、第2引数で書き出したい文字列などを指定して出力します。この時末尾の改行は無しです。
データ同士を「,(カンマ)」で区切って複数書き出すことも可能ですが、文字列型と隣接するデータは間にスペースが付きません

// os.Stdout (標準出力)へ書き出す
fmt.Fprint(os.Stdout, "アイウエオ")  // アイウエオ
fmt.Fprint(os.Stdout, struct{ X, Y int }{ 1, 2 })  // { 1 2 }
fmt.Fprint(os.Stdout, 1, 123, "Go言語", struct{ X, Y int }{ 1, 2 })  // 1 123Go言語{ 1 2 }


// ファイルへ書き出す
f, err := os.Create("foo.text")  //「foo.text」というファイルを作成し変数に格納
if err != nil {
	log.Fatal(err)
}
defer f.Close()

fmt.Fprint(f, "hello")

// foo.textの内容
hello!

fmt.Fprintf

第1引数に io.Writer型 を満たす型をとり、第2引数に文字列でフォーマットを指定、第3引数以降は埋め込む任意のデータを並べて書き出します

// os.Stdout (標準出力)へ書き出す
fmt.Fprintf(os.Stdout, "My name is %s\n", "Tom")  // "My name is Tom"


// ファイルへ書き出す
f, err := os.Create("foo.text")  //「foo.text」というファイルを作成し変数に格納
if err != nil {
    log.Fatal(err
}
defer f.Close()

fmt.Fprintf(f, "%s!\n", "hello")

// foo.textの内容
hello!

fmt.Fprintln

第1引数に io.Writer型 を満たす型をとり、第2引数で書き出したい文字列などを指定して出力します。この時末尾に改行が入ります。
文字同士を「,(カンマ)」で区切って複数出力することも可能ですが、「文字列型と隣接するデータ」は間にスペースが付きません

// os.Stdout (標準出力)へ書き出す
fmt.Fprintln(os.Stdout, "アイウエオ")  // アイウエオ(末尾に改行付き)
fmt.Fprintln(os.Stdout, struct{ X, Y int }{ 1, 2 })  // { 1 2 }(末尾に改行付き)
fmt.Fprintln(os.Stdout, 1, 123, "Go言語", struct{ X, Y int }{ 1, 2 })  // 1 123Go言語{ 1 2 }(末尾に改行付き)


// ファイルへ書き出す
f, err := os.Create("foo.text")  //「foo.text」というファイルを作成し変数に格納
if err != nil {
    log.Fatal(err)
}
defer f.Close()

fmt.Fprintln(f, "hello!")

// foo.textの内容
hello!

Discussion