覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。twitter: @yyoshikaw

Go言語 - forループによる繰り返し処理

Go言語で繰り返し処理(ループ)を記述するために用意されている構文はforのみとなっています。
whiledo whileといったものは提供されていません。


無限ループ

他の言語であれば、無限ループを記述する場合は、 while(true) { xxx } といったようにwhileを利用する場合が多いと思います。

が、先に書いたようにGo言語にはwhileはありません。forを使って以下のように記述します。

for {
    // ここに無限ループで実行する処理を記述
    fmt.Println("Running...")
}


独特と言えば独特ですが、慣れてしまえばなんてことない気もします。


while的な記述

他言語のwhile文のようにループ条件だけを記述したい場合はforで以下のように書くことができます

coundown := 10
for coundown > 0 {
    fmt.Printf("coundown: %d\n", coundown)
    coundown--
}


典型的なfor

ループ変数を利用して1ずつ値を増加させてある最大値まで処理をさせるといった、基本的なループは以下のように記述します

for i := 0; i < 10; i++ {
    // iの値が0から9まで変化していく
    fmt.Printf("i = %d\n", i)
} 

Go言語の構文の関係で、他言語でみられる 初期値や条件を指定する部分を括弧で囲む という記述はできません


rangeを使ったfor

いわゆる 拡張for文(foreach文)と呼ばれるループを行う場合はrange節を使用します


例えば配列に対するrangeを利用したforループは以下のように記述できます

strArray := [5]string{"taro", "jiro", "hanako", "ken", "tom"}

for i, s := range strArray {
    fmt.Printf("index: %d, name: %s\n", i, s)
}

配列に対するrangeでは、index番号と要素の値が返されます。

例えば、配列に対するrangeで返されるindex番号が処理上不要という場合には以下のように無視することもできます。

strArray := [5]string{"taro", "jiro", "hanako", "ken", "tom"}

for _, s := range strArray {
    fmt.Printf("name: %s\n", s)
}

Go言語 - メソッド

Go言語には、任意の型に関数を紐づけるためのメソッドという機能が存在しています。


メソッドの定義方法

メソッドの定義は以下のように記述します。レシーバーに関する記述をする部分が、関数と異なる箇所になります

func (<レシーバー>) <関数名>([引数]) [戻り値の型] {
    [関数の本体]
}

例えばPersonという構造体を定義して、そのPersonのプロフィールを返すメソッドを定義する場合は以下のような記述をします

type Person struct {
    name string
    age  uint
}

func (p *Person) profile(header string) string {
    return fmt.Sprintf("%s Profile[name: %s, age: %d]", header, p.name, p.age)
}

func (p Person) profile2(header string) string {
    return fmt.Sprintf("%s Profile[name: %s, age: %d]", header, p.name, p.age)
}


定義したメソッドは、 <レシーバ>.<メソッド> という形式で呼び出すことができます

func main() {
    person1 := &Person{name: "KimuraJiro", age: 28}
    fmt.Printf("person1 -> %s\n", person1.profile("!!!"))

    person2 := &Person{name: "KimuraTaro", age: 38}
    fmt.Printf("person2 -> %s\n", person2.profile2("@@@"))
}

■実行結果

person1 -> !!! Profile[name: KimuraJiro, age: 28]
person2 -> @@@ Profile[name: KimuraTaro, age: 38]

定義したメソッドの内容が実行されていることが分かります


メソッドは関数としてどう定義されているか

<レシーバの型>.<メソッド>でメソッドを関数型として参照することができます。

これを利用してPersonに定義したprofileメソッドとprofile2メソッドの関数定義を確認してみます

f1 := (*Person).profile
f2 := (Person).profile2

fmt.Printf("Person.profile -> %T\n", f1)
fmt.Printf("Person.profile2 -> %T\n", f2)

■実行結果

Person.profile -> func(*main.Person, string) string
Person.profile2 -> func(main.Person, string) string

実行結果から、メソッドのレシーバを第1引数にした関数になっていることがわかります


レシーバを値型にしたりポイント型にしたり

Person型をレシーバとするリネームメソッドとPersonのポインタ型をレシーバとするリネームメソッドを以下のように定義します。
引数で与えられた文字列をレシーバPersonnameフィールドにセットしています。

type Person struct {
    name string
    age  uint
}

func (p *Person) rename1(newName string) {
    p.name = newName
}

func (p Person) rename2(newName string) {
    p.name = newName
}


値型に定義したメソッドとポインタに定義したメソッドを実行して結果の違いを確認してみます

fmt.Printf("Person.rename1 -> %T\n", (*Person).rename1)
fmt.Printf("Person.rename2 -> %T\n", (Person).rename2)

personA := &Person{name: "Hanako", age: 19}
oldName := personA.name

personA.rename1("Kumiko")

fmt.Printf("rename1 exec renamed. %s -> %s\n", oldName, personA.name)

personB := &Person{name: "Rika", age: 19}
oldName = personB.name

personB.rename2("Miho")

fmt.Printf("rename2 exec renamed. %s -> %s\n", oldName, personB.name)


■実行結果

Person.rename1 -> func(*main.Person, string)
Person.rename2 -> func(main.Person, string)
rename1 exec renamed. Hanako -> Kumiko
rename2 exec renamed. Rika -> Rika

実行結果から、
ポインタ型に対するメソッド呼び出しの場合はレシーバ(構造体)の値が変更されていますが、
値型に対するメソッド呼び出しの場合はレシーバ(構造体)の値が変更されていないことが分かります。

これは、以下の事を勘案すれば納得いく結果です

  • メソッドがレシーバを第1引数に取る関数であること
  • 値渡しした引数はコピーされて関数に渡される



関連エントリ

Go言語 - 無名関数

前回のエントリでGo言語の関数の基本的な部分について書きました。

本エントリでは、前回のエントリでは扱わなかった無名関数について書きます。


Go言語では関数を値として扱うことができて、変数へ格納することもできます。
まずその辺りの動作を以下確認しておきます。

例えば以下のような、引数で与えられた文字列を装飾して戻り値で返す関数があったとします

func decorate(src string) string {
    return "<<" + src + ">>"
}

この場合には、decorate関数を変数に格納してその変数を経由して実行することができます

func main() {
    f := decorate   // 関数を変数に格納
    ret := f("golang")  // 変数fに対して引数を渡して関数を実行

    fmt.Println("decorete result ->", ret)  // ⇒ decorete result -> <<golang>>
}


無名関数を扱う

先の例では明確に関数名を持った関数を定義していますが、
関数名を与えずに無名関数として関数を定義して変数にセットすることができます

func main() {
    f := func(src string) string { return "<<" + src + ">>" }   // 無名関数を定義
    ret := f("clang")  // 変数fに対して引数を渡して関数を実行

    fmt.Println("decorete result ->", ret)  // ⇒ decorete result -> <<clang>>
    fmt.Printf("function type -> %T\n", f)
}

上記は変数fに対して、"string型の引数をとりstring型の戻り値を変えす無名関数" を代入しています。
変数にセットする関数の内容を記載している右辺部分は関数リテラルとなります。関数リテラルは以下のように記述します

func([引数]) [戻り値の型] { [関数の本体] }


ちなみに上記プログラムでは、今回定義した無名関数の型についてもチェックしていますが結果は以下のようになっています

function type -> func(string) string


戻り値として関数を返す関数

Go言語では 関数を戻り値として返す関数 を定義することができます。
例えば、文字列を装飾する関数を返す関数(returnDecoreteFunc)を以下のように記述することができます

func returnDecoreteFunc() func(string) string {
    return func(src string) string { return "||" + src + "||" }
}

returnDecoreteFuncの定義から、戻り値の型は「引数がstringで戻り値もstring の関数」となっています。


returnDecoreteFuncの戻り値で返された関数を実行するには以下のようになります。
一度変数に受けてその変数経由実行することも、変数を経由せずに実行することもできます。

// 一度変数に受ける場合
f := returnDecoreteFunc()
ret1 := f("Java")
fmt.Println("decorete result ->", ret1)  // ⇒ decorete result -> ||Java||

// 変数を経由しない場合
ret2 := returnDecoreteFunc()("C#")
fmt.Println("decorete result ->", ret2)  // ⇒ decorete result -> ||C#||


関数を引数にとる関数

Go言語では 関数を引数にとる関数 を定義することもできます。

例えば以下は、第1引数に文字列、第2引数に装飾した文字列を返す関数 をとる関数(decorateAndPrint)で、
第1引数の文字列を第2引数の関数で装飾して、その結果を標準出力に書きだす処理をします。

func decorateAndPrint(str string, decoreteStrategy func(string) string) {
    decoratedStr := decoreteStrategy(str)
    fmt.Printf("original string: %s -> decorated string: %s\n", str, decoratedStr)
}


以下は本関数を呼び出す例となります。
decorateAndPrintを2回呼び出していますが、2回とも第1引数は同じですが、第2引数では各々異なる処理(文字列の装飾方法)

func main() {
    decorateAndPrint("Taro", func(src string) string {
        return "///" + src + "///"
    })

    decorateAndPrint("Taro", func(src string) string {
        return "<h2>" + src + "</h2>"
    })
}

実行結果は以下の通りです。呼び出し毎に事なる装飾関数が適用されていることが分かります

original string: Taro -> decorated string: ///Taro///
original string: Taro -> decorated string: <h2>Taro</h2>


クロージャ

Goの関数は クロージャ(closure)です。
関数と関数の処理に関連する関数の外の環境をセットにして閉じ込めた状態で取り扱うことができます。

以下の例ではsumCalc関数は、「引数にintを1つとり戻り値で2つのint値を返す関数」を戻り値にする関数となっています。 戻り値で返される関数は、引数で与えられた整数値を加算していくもので、合計値をsum変数に格納しています

package main

import "fmt"

func main() {
    f := sumCalc()

    for i := 1; i < 5; i++ {
        sumVal, srcVal := f(i)
        fmt.Printf("add value: %d -> sum: %d\n", srcVal, sumVal)
    }
}

func sumCalc() func(int) (int, int) {
    sum := 0 // 足し算の合計値を格納する変数
    return func(x int) (int, int) {
        sum = sum + x
        return sum, x
    }
}

実行すると以下の通りとなります

add value: 1 -> sum: 1
add value: 2 -> sum: 3
add value: 3 -> sum: 6
add value: 4 -> sum: 10

sum変数は見かけ上は関数内のローカル変数ですが、実際にはクロージャに属する変数として利用されます。
クロージャによって束縛された変数の領域は、何らかの形でクロージャが参照され続ける限りは破棄されることがありません。
そのため、本例では関数呼び出し毎の合計値をsum変数に保持することができています



関連エントリ

Go言語 - 関数

オブジェクト指向ではないGo言語において、関数を定義することがプログラミングにおいてかなり重要なものとなるようです。
ここでは、Go言語の関数について基本的な部分を確認します。
Go言語の関数はJavaのメソッドやC言語の関数と比べても独特な部分がいくつも見受けられます。


基本的な関数の定義

Go言語の関数定義は基本的に以下の構文になります

func <関数名>([引数]) [戻り値の型] {
    [関数の本体]
}


以下は2つの文字列(string)を引数に取り、それらの文字列を結合した文字列の文字数を戻り値(int)で返す関数になります

func concatCount(str1 string, str2 string) int {
    concatStr := str1 + str2
    return utf8.RuneCountInString(concatStr)
}

定義した関数を呼び出すには以下の通りとなります

count := concatCount("Hello", "World")
fmt.Println("count:", count)   // ⇒ count: 10


■引数無しの関数定義

関数定義において引数を記述しなければ引数なしの関数となります。

func number() int {
    return 10
}


■戻り値無しの関数定義

関数定義において戻り値の型を記述しなければ戻り値なしの関数となります。
JavaやC言語では必要になるvoidの記述はGo言語では不要となります

func printHelloWorld() {
    fmt.Println("Hello, World")
}


■引数の型をまとめて記述できる

同じ型の引数を複数持つ関数を定義する場合、

func concatCount(str1 string, str2 string) int {
    concatStr := str1 + str2
    return utf8.RuneCountInString(concatStr)
}

以下のように、引数の型をまとめて記述することができます。

func concatCount(str1, str2 string) int {
    concatStr := str1 + str2
    return utf8.RuneCountInString(concatStr)
}


可変個引数関数

可変個の引数を渡すことができる関数を定義することもできます。そのような場合は引数の型の前に"..."を付与します。
(ちなみに、可変個引数にできるのは、関数の最後のパラメータのみです)

例として、可変個の整数を引数にとってその合計を返す関数を定義すると以下のようになります

func sumAll1(values ...int) int {
    length := len(values)

    sum := 0
    for _, value := range values {
        sum += value
    }

    fmt.Printf("func sumAll [param: len=%d, value=%#v, result -> %d]\n", length, values, sum)

    return sum
}


呼び出し側のコードは以下のようになります。

var sumVal int
sumVal = sumAll1()         // ⇒ 0
sumVal = sumAll1(5)       // ⇒ 5
sumVal = sumAll1(5, 6, 7, 8, 9) // ⇒ 35

// sumVal = sumAll1([]int{1, 2, 3, 4}) // ← コンパイルエラー。スライスを渡すことはできない

■実行結果

func sumAll1 [param: len=0, value=[]int(nil), result -> 0]
func sumAll1 [param: len=1, value=[]int{5}, result -> 5]
func sumAll1 [param: len=5, value=[]int{5, 6, 7, 8, 9}, result -> 35]

関数呼び出し側のコードと実行結果から以下が分かります。

  • 引数は複数でなくても良い。1個でも0個でも良い
  • 関数に渡された引数の値はスライスとして扱われる。ただし、呼び出す際の引数にスライスを渡すことはできない


引数がスライスになっている関数を定義して、その関数と可変個引数関数の型を各々チェックしてみます

func sumAll2(values []int) int {
    sum := 0
    for _, value := range values {
        sum += value
    }
    return sum
}

func main() {
    fmt.Printf("sumAll1 -> %T\n", sumAll1)
    fmt.Printf("sumAll2 -> %T\n", sumAll2)
}

■実行結果

sumAll1 -> func(...int) int
sumAll2 -> func([]int) int

結果から、可変個引数関数とスライスを引数に取る関数の型が明確に違うことが分かります


複数の戻り値を返す

Go言語では戻り値を複数返すことができます。
JavaやC言語からするとかなり不思議な感じがします。

以下のような定義をします。複数の戻り値の型の前後に記述している括弧は省略できません

func <関数名>([引数]) (戻り値1の型, 戻り値2の型 [, 戻り値3の型, 戻り値4の型・・・]) {
    [関数の本体]
}


例えば、2つの文字列(string)を引数に取り、それらの文字列を結合した文字列(第1戻り値)とその文字列の文字数(第2戻り値)を返す関数の定義は以下のようになります

func concatCount(str1 string, str2 string) (string, int) {
    concatStr := str1 + str2
    count := utf8.RuneCountInString(concatStr)
    return concatStr, count
}

呼び出し方法は以下のようになり、変数をカンマ区切りで並べて関数が返す複数の戻り値を各変数に格納します

str1 := "Hello"
str2 := "World"

str, count := concatCount(str1, str2)  // str: 結合した文字列, count: 文字数

fmt.Printf("str=%s -> count: %d", str, count)   // ⇒ str=HelloWorld -> count: 10


戻り値を破棄する

関数が複数の戻り値を返す場合でも、そのすべての戻り値が関数呼び出し後の処理で必要ではないケースもあります。
そのような場合には、戻り値を破棄することができます。
具体的には戻り値用の変数の代わりに"_"を利用して戻り値を破棄します。

例えば先の例で定義したconcatCount関数において、
結合した文字列(第1戻り値)が不要という場合は以下のように呼び出します

str1 := "Hello"
str2 := "World"

_, count := concatCount(str1, str2)  // 第1戻り値を破棄
fmt.Printf("str1=%s, str2=%s -> concat count: %d", str1, str2, count)  // ⇒ str1=Hello, str2=World -> concat count: 10

上記とは逆に、結合した文字列の文字数(第2戻り値)が不要という場合は以下のように呼び出します

str1 := "Hello"
str2 := "World"


str, _ := concatCount(str1, str2)  // 第2戻り値を破棄
fmt.Printf("str1=%s, str2=%s -> concat str: %s\n", str1, str2, str)  // ⇒ str1=Hello, str2=World -> concat str: HelloWorld


関数の戻り値を利用したエラー処理

Go言語には例外の機構は基本的にはありません。Go言語のエラー処理は関数の戻りを利用して行うスタイルが基本になります。
この辺はC言語と似ていますが、Cと違うのは関数の戻り値が複数返せるということです。
複数戻り値が返せることを利用して、普通に返したい値とエラー判定用の値の両方を返して呼び出し側でエラー判定用の(error型等の)戻り値をチェックしてエラー処理を行います。

エラー判定用の戻り値は基本的に、複数戻り値の最後の戻り値として返します。
また呼び出し側では、エラー判定用のerror型の値を受ける変数の変数名にはerrというのを使うのが慣例の様です


例えば、エラーを返す可能性のある以下のような関数があった場合、

// 戻り値は、返したい値とerror
func concatCount(str1 string, str2 string) (int, error) {
    if len(str1) == 0 || len(str2) == 0 {
        return 0, errors.New("param is empty")
    }

    concatStr := str1 + str2
    count := utf8.RuneCountInString(concatStr)
    return count, nil
}

呼び出し側は以下のようになります

str1 := "Hello"
str2 := ""

count, err := concatCount(str1, str2)
if err != nil {
    fmt.Printf("Failed concatAndPrint") // 何らかのエラー処理
    return
}

fmt.Printf("str1=%s, str2=%s -> concat count: %d", str1, str2, count)

第2戻り値をerrで受けて、そのerr変数がnilかどうかでエラー判定をします(nilでなければエラー) errに対するnilチェックが基本的にGo言語におけるエラー判定処理となります。


また、戻り値がerror型のみとなっている以下のような関数があった場合

func concatAndPrint(str1 string, str2 string) error {
    if len(str1) == 0 || len(str2) == 0 {
        return errors.New("param is empty")
    }

    concatStr := str1 + str2
    fmt.Println(concatStr)
    return nil
}

呼び出し側は以下のようになり、err変数でのエラー結果の受け取りとエラー判定を一気にやるような記述をします

str1 := "Hello"
str2 := ""

if err := concatAndPrint(str1, str2); err != nil {
    fmt.Printf("Failed concatAndPrint") // 何らかのエラー処理
}


戻り値に変数を割り当てる

これもまたGo言語独特な気がしますが、関数定義において戻り値の型だけではなく戻り値の変数名も指定することができます。

たとえば以下のような関数がそれに当たり、resStrとstrCountが戻り値用の変数となります。

// 戻り値に変数を割り当てる
func concatCount(str1 string, str2 string) (retStr string, strCount int) {
    retStr = str1 + str2
    strCount = utf8.RuneCountInString(retStr)
    return
}

戻り値の変数名を指定している場合は、同名のローカル変数に格納した値が戻り値となります。
return文では何も返す必要はありません。(自動的にローカル変数の値が戻り値として返されます)


まとめ

Go言語における基本的な関数の定義や扱いについて書いてみました。
エントリ内に書いた通りですが、関数に関しては他言語であまり見かけない結構独特な仕様をGo言語は持っています。

本エントリでは触れなかった無名関数については以下エントリを参照下さい。



関連エントリ

Go言語 - %Tや%vの書式で出力される文字列

Go言語のfmtパッケージに存在するPrintf関数は書式を指定して標準出力に書き込みを行います。
C言語のprintfに良く似ています。が、C言語には存在しない書式がいくつか加わっています。
その中の代表格として以下のような書式が存在しています。

  • %T
    • 対象データの型情報を埋め込む
  • %v
    • デフォルトフォーマットで対象データの情報を埋め込む
  • %+v
    • 構造体を出力する際に、%vの内容にフィールド名も加わる
  • %#v
    • Go言語のリテラル表現で対象データの情報を埋め込む

これらの書式は、デバッグの時などに利用すると力を発揮すると思われます。

具体的に上記の書式にいくつかデータをセットしてみて、どんな値が出力されるのかを確認してみます。
(最初のサンプル以外は、pakage, import等の記述は省略します)


整数値

package main

import (
    "fmt"
)

func main() {
    valInt := 123

    fmt.Printf("valInt[%%T] -> %T\n", valInt)
    fmt.Printf("valInt[%%v] -> %v\n", valInt)
    fmt.Printf("valInt[%%+v] -> %+v\n", valInt)
    fmt.Printf("valInt[%%#v] -> %#v\n", valInt)
}

■実行結果

valInt[%T] -> int
valInt[%v] -> 123
valInt[%+v] -> 123
valInt[%#v] -> 123

値そのものが出力されます


文字列

func main() {
    valStr := "hello golang"

    fmt.Printf("valStr[%%T] -> %T\n", valStr)
    fmt.Printf("valStr[%%v] -> %v\n", valStr)
    fmt.Printf("valStr[%%+v] -> %+v\n", valStr)
    fmt.Printf("valStr[%%#v] -> %#v\n", valStr)
}

■実行結果

valStr[%T] -> string
valStr[%v] -> hello golang
valStr[%+v] -> hello golang
valStr[%#v] -> "hello golang"

%#vの時だけ、"(ダブルクォート)で囲った値が出力されるので、スペースを含む文字列の場合などに分かりやすいかもしれません


構造体

type Person struct {
    id   int
    name string
}

func main() {
    person := Person{id: 1, name: "taro"}

    fmt.Printf("person[%%T] -> %T\n", person)
    fmt.Printf("person[%%v] -> %v\n", person)
    fmt.Printf("person[%%+v] -> %+v\n", person)
    fmt.Printf("person[%%#v] -> %#v\n", person)
}

■実行結果

person[%T] -> main.Person
person[%v] -> {1 taro}
person[%+v] -> {id:1 name:taro}
person[%#v] -> main.Person{id:1, name:"taro"}

フィールド名が出る書式(%+v, %#v)の方が分かりやすく感じます


構造体のポインタ

type Person struct {
    id   int
    name string
}

func main() {
    ptrPerson := &Person{id: 1, name: "taro"}

    fmt.Printf("ptrPerson[%%T] -> %T\n", ptrPerson)
    fmt.Printf("ptrPerson[%%v] -> %v\n", ptrPerson)
    fmt.Printf("ptrPerson[%%+v] -> %+v\n", ptrPerson)
    fmt.Printf("ptrPerson[%%#v] -> %#v\n", ptrPerson)
}

■実行結果

ptrPerson[%T]  -> *main.Person
ptrPerson[%v] -> &{1 taro}
ptrPerson[%+v] -> &{id:1 name:taro}
ptrPerson[%#v] -> &main.Person{id:1, name:"taro"}

対象がポインタであってもアドレスではなく実際の値が表示されます


配列

func main() {
    strArray := [3]string{"tokyo", "osaka", "fukuoka"}

    fmt.Printf("strArray[%%T] -> %T\n", strArray)
    fmt.Printf("strArray[%%v] -> %v\n", strArray)
    fmt.Printf("strArray[%%+v] -> %+v\n", strArray)
    fmt.Printf("strArray[%%#v] -> %#v\n", strArray)
}

■実行結果

strArray[%T] -> [3]string
strArray[%v] -> [tokyo osaka fukuoka]
strArray[%+v] -> [tokyo osaka fukuoka]
strArray[%#v] -> [3]string{"tokyo", "osaka", "fukuoka"}

配列内の全要素の内容が出力されています


スライス

func main() {
    strSlice := []string{"monkey", "cat", "dog", "man"}

    fmt.Printf("strSlice[%%T] -> %T\n", strSlice)
    fmt.Printf("strSlice[%%v] -> %v\n", strSlice)
    fmt.Printf("strSlice[%%+v] -> %+v\n", strSlice)
    fmt.Printf("strSlice[%%#v] -> %#v\n", strSlice)
}

■実行結果

strSlice[%T] -> []string
strSlice[%v] -> [monkey cat dog man]
strSlice[%+v] -> [monkey cat dog man]
strSlice[%#v] -> []string{"monkey", "cat", "dog", "man"}

配列とほぼ同様の出力内容になっています


マップ

type Person struct {
    id   int
    name string
}

func main() {
    personMap := map[int]Person{
        1: Person{id: 1, name: "taro"},
        2: Person{id: 2, name: "jiro"},
        3: Person{id: 2, name: "hanako"},
    }

    fmt.Printf("personMap[%%T] -> %T\n", personMap)
    fmt.Printf("personMap[%%v] -> %v\n", personMap)
    fmt.Printf("personMap[%%+v] -> %+v\n", personMap)
    fmt.Printf("personMap[%%#v] -> %#v\n", personMap)
}

■実行結果

personMap[%T] -> map[int]main.Person
personMap[%v] -> map[1:{1 taro} 2:{2 jiro} 3:{2 hanako}]
personMap[%+v] -> map[2:{id:2 name:jiro} 3:{id:2 name:hanako} 1:{id:1 name:taro}]
personMap[%#v] -> map[int]main.Person{3:main.Person{id:2, name:"hanako"}, 1:main.Person{id:1, name:"taro"}, 2:main.Person{id:2, name:"jiro"}}

マップ内の全要素の内容が出力されています


channel

func main() {
    ch := make(chan string, 5)
    ch <- "start"

    fmt.Printf("ch[%%T] -> %T\n", ch)
    fmt.Printf("ch[%%v] -> %v\n", ch)
    fmt.Printf("ch[%%+v] -> %+v\n", ch)
    fmt.Printf("ch[%%#v] -> %#v\n", ch)
}

■実行結果

ch[%T] -> chan string
ch[%v] -> 0x1219c3c0
ch[%+v] -> 0x1219c3c0
ch[%#v] -> (chan string)(0x1219c3c0)

channelに関してはあまり有用な情報は出力されないようです


関数

func main() {
    function := func(src int) string {
        return fmt.Sprintf("param is %d", src)
    }

    fmt.Printf("function[%%T] -> %T\n", function)
    fmt.Printf("function[%%v] -> %v\n", function)
    fmt.Printf("function[%%+v] -> %+v\n", function)
    fmt.Printf("function[%%#v] -> %#v\n", function)
}

■実行結果

function[%T] -> func(int) string
function[%v] -> 0x476e20
function[%+v] -> 0x476e20
function[%#v] -> (func(int) string)(0x476e20)

関数に関しても出力することはできますが、%vでは有用な情報は出力されません


補足

本エントリでは全てPrintf関数を用いているので結果が標準出力に書き込まれますが、Sprintf関数を使用すればフォーマットした結果を戻り値で受け取ることができます