はじめてのGo―シンプルな言語仕様,型システム,並行処理

第2章 基本文法―覚えやすいコンパクトな言語仕様

この記事を読むのに必要な時間:およそ 9.5 分

マップ

マップは,値をKey-Valueの対応で保存するデータ構造です。

宣言と初期化

たとえばintのキーにstringの値を格納するマップは次のように宣言します。

var month map[int]string = map[int]string{}

次のようにキーを指定して値を保存します。

month[1] = "January"
month[2] = "February"
fmt.Println(month) // map[1:January 2:February]

宣言と初期化を一緒に行う場合は次のように書きます。

month := map[int]string{
    1: "January",
    2: "February",
}
fmt.Println(month) // map[1:January 2:February]

マップの操作

マップから値を取り出す場合は,次のようにキーを指定し,戻り値として受け取ります。

jan := month[1]
fmt.Println(jan) // January

このとき2つ目の戻り値も受け取ると,指定したキーがこのマップに格納されているかをboolで返します。マップ内のキーの存在を調べるような場合には,値を無視して次のようにします。

_, ok := month[1]
if ok {
    // データがあった場合
}

マップからデータを消す場合は組込み関数のdelete()を使用します。

delete(month, 1)
fmt.Println(month) // map[1:January]

スライス同様,rangeを用いるとfor文でKey-Valueをそれぞれ受け取りながら処理を進めることができます。ただし,マップの場合は取り出される順番は保証されない点に注意してください。

for key, value := range month {
    fmt.Printf("%d %s\n", key, value)
}

ポインタ

Goはポインタを扱うことができます。ポインタ型の変数は,型の前に*を付けます。アドレスは変数の前に&を付けて取得できるため,Cと似たような形で表現できます。

func callByValue(i int) {
    i = 20 // 値を上書きする
}

func callByRef(i *int) {
    *i = 20 // 参照先を上書きする
}

func main() {
    var i int = 10
    callByValue(i) // 値を渡す
    fmt.Println(i) // 10
    callByRef(&i) // アドレスを渡す
    fmt.Println(i) // 20
}

しかし,Cなどと違い,Goはポインタ演算を認めていません。ポインタをデータサイズ分ずつずらして,メモリ上からデータを読み込むといったことは基本的にはできません。

defer

ファイル操作などを行う場合,使用後のファイルは必ず閉じる必要があります。次の例では関数の最後にファイルのクローズ処理を記述していますが,その前に関数を抜ける処理があったり,後述するパニックが起こってしまうと,Close()まで到達しない場合が発生してしまいます。

func main() {
    file, err := os.Open("./error.go")
    if err != nil {
        // エラー処理
    }
    // 正常処理
    file.Close()
}

こうした処理はdeferを用いて記述できます。先の例ではfile.Close()の関数呼び出しをdeferの後ろに記述すると,この処理がmain()を抜ける直前に必ず実行されるようになります。

func main() {
    file, err := os.Open("./error.go")
    if err != nil {
        // エラー処理
    }
    // 関数を抜ける前に必ず実行される
    defer file.Close()
    // 正常処理
}

ファイルのClose()などは,deferを用いて記述するほうが安全です。

パニック

エラーは戻り値によって表現するのが基本ですが,そうではない場合もあります。たとえば配列やスライスの範囲外にアクセスした場合や,ゼロ除算をしてしまった場合などです。こうした処理はエラーを返すことができないため,代わりにパニックという方法でエラーが発生します。

このパニックで発生したエラーはrecover()という組込み関数で取得し,そこでエラー処理を実施できます。recover()deferの中に書くことで,パニックで発生したエラーの処理を実施してから,関数を抜けることができます。

func main() {
    defer func() {
        err := recover()
        if err != nil {
            // runtime error: index out of range
            log.Fatal(err)
        }
    }()

    a := []int{1, 2, 3}
    fmt.Println(a[10]) // パニックが発生
}

panic()

パニックは組込み関数panic()を用いて自分で発生させることもできます。先ほどの例を自分でパニックにする場合は次のように書けます。

a := []int{1, 2, 3}
for i := 0; i < 10; i++ {
    if i >= len(a) {
        panic(errors.New("index out of range"))
    }
    fmt.Println(a[i])
}

ただしパニックを用いるのは,エラーを戻り値として表現できない場合や,回復が不可能なシステムエラー,やむを得ず大域脱出が必要な場合などであり,基本的にエラーは関数の戻り値として呼び出し側に返すようにしましょう。

まとめ

この章ではGoの基本的な文法を解説しました。これらの知識は次章以降にも使用しますので,しっかり把握しておきましょう。

COLUMN:A Tour of Go

A Tour of Goは,Goの一通りの基本文法を解説する,初心者向けのチュートリアルです。ブラウザ上で実際にコードを動かしながら学ぶことができるため,初めてGoを触る際に参考になります。

ブラウザ上でGoのコードを実行するしくみは,Go Playgroundとして公開されています。ここでは記述したGoのコードに対して一意なURLを取得できるため,サンプルコードをGo Playgroundに書けば,メーリングリストやTwitterなどに投稿して共有できます。

著者プロフィール

コメント

コメントの記入