Go言語入門:言語仕様 -Vol.17-

スポンサーリンク
Go言語入門:言語仕様 -Vol.17- 用語解説
Go言語入門:言語仕様 -Vol.17-
この記事は約18分で読めます。
よっしー
よっしー

こんにちは。よっしーです(^^)

本日は、Go言語の言語仕様について解説しています。

スポンサーリンク

背景

Go言語を学び始めて、公式の「The Go Programming Language Specification(言語仕様書)」を開いてみたものの、「英語で書かれていて読むのが大変…」「専門用語ばかりで何を言っているのかわからない…」と感じたことはありませんか? 実は、多くのGo初心者が同じ壁にぶつかっています。

言語仕様書は、Go言語の「正式な取扱説明書」のような存在です。プログラミング言語がどのように動くのか、どんなルールで書くべきなのかが詳しく書かれていますが、その分、初めて読む人には難しく感じられるのも事実です。

そこでこの記事では、言語仕様書の導入部分を丁寧な日本語訳とともに、初心者の方でも理解しやすい補足説明を加えてお届けします。「強く型付けされている」「ガベージコレクション」「並行プログラミング」といった専門用語も、具体例を交えながらわかりやすく解説していきます。

言語仕様書は難しそうに見えますが、一つひとつの概念を丁寧に読み解いていけば、必ず理解できます。一緒に、Go言語の基礎をしっかり学んでいきましょう!

Variables(変数)

変数は、値を保持するための記憶場所です。許容される値の集合は、変数の型によって決定されます。

変数宣言、または関数のパラメータと戻り値の場合は関数宣言または関数リテラルのシグネチャが、名前付き変数のためのストレージを確保します。組み込み関数newを呼び出すか、複合リテラルのアドレスを取得すると、実行時に変数のストレージが割り当てられます。このような匿名変数は、(場合によっては暗黙的な)ポインタの間接参照を介して参照されます。

配列、スライス、構造体型の構造化された変数には、個別にアドレス指定できる要素とフィールドがあります。これらの各要素は変数のように機能します。

変数の静的型(または単に型)は、その宣言で指定された型、new呼び出しまたは複合リテラルで提供された型、または構造化された変数の要素の型です。インターフェース型の変数には、実行時に変数に割り当てられた値の(非インターフェース)型である、明確な動的型もあります(値が事前宣言された識別子nilでない限り、nilには型がありません)。動的型は実行中に変化する可能性がありますが、インターフェース変数に格納された値は常に変数の静的型に割り当て可能です。

var x interface{}  // xはnilで、静的型はinterface{}
var v *T           // vは値nil、静的型は*T
x = 42             // xは値42、動的型はint
x = v              // xは値(*T)(nil)、動的型は*T

変数の値は、式の中で変数を参照することによって取得されます。これは、変数に割り当てられた最新の値です。変数にまだ値が割り当てられていない場合、その値は型のゼロ値です。


解説

変数とは何か?

変数は、値を入れておく「箱」のようなものです。定数と違い、値を変更できるのが特徴です。

たとえ話: 変数は「ラベル付きの箱」で、その中に色々なものを入れたり、入れ替えたりできます。箱には「型」というルールがあり、「整数しか入れられない箱」「文字列しか入れられない箱」などがあります。

package main

import "fmt"

func main() {
    // 変数の宣言と使用
    var age int = 25       // 整数型の変数
    var name string = "太郎"  // 文字列型の変数
    
    fmt.Println(age, name)  // 25 太郎
    
    // 値を変更できる(定数との違い)
    age = 26
    name = "花子"
    
    fmt.Println(age, name)  // 26 花子
}

1. 変数の宣言方法

基本的な宣言(var)

package main

import "fmt"

func main() {
    // 型を明示的に指定
    var age int = 25
    var name string = "太郎"
    var isStudent bool = true
    
    fmt.Println(age, name, isStudent)
    
    // 型推論(型を省略)
    var score = 95  // int型と推論される
    var message = "こんにちは"  // string型と推論される
    
    fmt.Println(score, message)
}

短縮宣言(:=)

関数内では、varを省略して:=で宣言できます。

package main

import "fmt"

func main() {
    // 短縮宣言(関数内のみ)
    age := 25
    name := "太郎"
    isStudent := true
    
    fmt.Println(age, name, isStudent)
    
    // 複数同時宣言
    x, y, z := 1, 2, 3
    fmt.Println(x, y, z)
}

ゼロ値による初期化

値を指定しないで宣言すると、ゼロ値で初期化されます。

package main

import "fmt"

func main() {
    // ゼロ値で初期化される
    var i int        // 0
    var f float64    // 0.0
    var b bool       // false
    var s string     // "" (空文字列)
    var p *int       // nil
    var slice []int  // nil
    var m map[string]int  // nil
    
    fmt.Printf("int: %v\n", i)
    fmt.Printf("float64: %v\n", f)
    fmt.Printf("bool: %v\n", b)
    fmt.Printf("string: %q\n", s)
    fmt.Printf("pointer: %v\n", p)
    fmt.Printf("slice: %v\n", slice)
    fmt.Printf("map: %v\n", m)
}

ブロック宣言

複数の変数をまとめて宣言できます。

package main

import "fmt"

var (
    AppName    = "MyApp"
    Version    = "1.0.0"
    MaxRetries = 3
)

func main() {
    var (
        name  string = "太郎"
        age   int    = 25
        score float64 = 95.5
    )
    
    fmt.Println(name, age, score)
}

2. ゼロ値(Zero values)

Goでは、変数を宣言すると自動的にゼロ値で初期化されます。これは、未初期化変数によるバグを防ぐ重要な機能です。

各型のゼロ値

ゼロ値説明
boolfalse
数値型0ゼロ
string""空文字列
ポインタnil何も指さない
スライスnil空のスライス
マップnil空のマップ
チャネルnil未初期化のチャネル
インターフェースnil値なし
関数nil関数なし
package main

import "fmt"

func main() {
    var intVal int
    var floatVal float64
    var boolVal bool
    var strVal string
    
    fmt.Printf("int: %d\n", intVal)      // 0
    fmt.Printf("float64: %f\n", floatVal) // 0.000000
    fmt.Printf("bool: %t\n", boolVal)     // false
    fmt.Printf("string: %q\n", strVal)    // ""
}

3. 記憶域の確保

宣言による記憶域確保

変数宣言により、メモリが自動的に確保されます。

package main

func main() {
    // 自動的にメモリが確保される
    var x int = 42
    var name string = "太郎"
    var numbers [5]int  // 配列
}

new関数による確保

new()関数は、ゼロ値で初期化されたメモリを確保し、ポインタを返します。

package main

import "fmt"

func main() {
    // newは指定した型のポインタを返す
    p := new(int)
    fmt.Printf("値: %d, アドレス: %p\n", *p, p)  // 値: 0
    
    // 値を設定
    *p = 42
    fmt.Printf("値: %d, アドレス: %p\n", *p, p)  // 値: 42
    
    // 構造体でも使える
    type Person struct {
        Name string
        Age  int
    }
    
    person := new(Person)
    person.Name = "太郎"
    person.Age = 25
    fmt.Printf("%+v\n", person)  // &{Name:太郎 Age:25}
}

複合リテラルのアドレス

複合リテラルのアドレスを取ると、匿名変数が作成されます。

package main

import "fmt"

type Point struct {
    X, Y int
}

func main() {
    // 複合リテラルのアドレス
    p := &Point{X: 10, Y: 20}
    fmt.Printf("%+v\n", p)  // &{X:10 Y:20}
    
    // スライスでも同様
    slice := &[]int{1, 2, 3}
    fmt.Println(slice)  // &[1 2 3]
}

4. 構造化された変数

配列、スライス、構造体の要素やフィールドは、個別に変数のように扱えます。

配列の要素

package main

import "fmt"

func main() {
    var numbers [5]int
    
    // 各要素に個別にアクセス
    numbers[0] = 10
    numbers[1] = 20
    numbers[2] = 30
    
    fmt.Println(numbers)  // [10 20 30 0 0]
    
    // 要素ごとに変数のように扱える
    numbers[0] = numbers[0] + 5
    fmt.Println(numbers[0])  // 15
}

スライスの要素

package main

import "fmt"

func main() {
    slice := []int{1, 2, 3, 4, 5}
    
    // 要素への個別アクセス
    slice[0] = 10
    slice[2] = 30
    
    fmt.Println(slice)  // [10 2 30 4 5]
    
    // スライス操作
    slice = append(slice, 6, 7)
    fmt.Println(slice)  // [10 2 30 4 5 6 7]
}

構造体のフィールド

package main

import "fmt"

type Person struct {
    Name string
    Age  int
    City string
}

func main() {
    var p Person
    
    // フィールドへの個別アクセス
    p.Name = "太郎"
    p.Age = 25
    p.City = "東京"
    
    fmt.Printf("%+v\n", p)  // {Name:太郎 Age:25 City:東京}
    
    // フィールドを変数のように操作
    p.Age = p.Age + 1
    fmt.Println(p.Age)  // 26
}

5. 静的型と動的型

静的型(Static type)

静的型は、変数宣言時に決まる型です。コンパイル時に確定します。

package main

import "fmt"

func main() {
    // 静的型が決まっている
    var x int = 42         // 静的型: int
    var name string = "太郎"  // 静的型: string
    var p *int = &x        // 静的型: *int
    
    fmt.Printf("%T %T %T\n", x, name, p)
}

動的型(Dynamic type)

インターフェース型の変数は、実行時に動的型を持ちます。

package main

import "fmt"

func main() {
    // 空のインターフェース
    var x interface{}
    
    fmt.Printf("型: %T, 値: %v\n", x, x)  // 型: <nil>, 値: <nil>
    
    // 整数を代入(動的型が int になる)
    x = 42
    fmt.Printf("型: %T, 値: %v\n", x, x)  // 型: int, 値: 42
    
    // 文字列を代入(動的型が string になる)
    x = "こんにちは"
    fmt.Printf("型: %T, 値: %v\n", x, x)  // 型: string, 値: こんにちは
    
    // スライスを代入(動的型が []int になる)
    x = []int{1, 2, 3}
    fmt.Printf("型: %T, 値: %v\n", x, x)  // 型: []int, 値: [1 2 3]
}

静的型と動的型の例

package main

import "fmt"

func main() {
    var x interface{}  // 静的型: interface{}, 動的型: nil
    var v *int         // 静的型: *int, 値: nil
    
    fmt.Printf("x: 型=%T, 値=%v\n", x, x)  // 型=<nil>, 値=<nil>
    fmt.Printf("v: 型=%T, 値=%v\n", v, v)  // 型=*int, 値=<nil>
    
    x = 42  // 動的型が int になる
    fmt.Printf("x: 型=%T, 値=%v\n", x, x)  // 型=int, 値=42
    
    x = v  // 動的型が *int になる
    fmt.Printf("x: 型=%T, 値=%v\n", x, x)  // 型=*int, 値=<nil>
}

型アサーション

インターフェース型から具体的な型を取り出すには、型アサーションを使います。

package main

import "fmt"

func main() {
    var x interface{} = 42
    
    // 型アサーション
    i := x.(int)
    fmt.Printf("i: %d (型: %T)\n", i, i)  // i: 42 (型: int)
    
    // 安全な型アサーション
    if value, ok := x.(int); ok {
        fmt.Println("intです:", value)
    }
    
    if value, ok := x.(string); ok {
        fmt.Println("stringです:", value)
    } else {
        fmt.Println("stringではありません")
    }
}

6. 変数のスコープ

パッケージレベル変数

package main

import "fmt"

// パッケージレベル(グローバル)変数
var globalVar = "グローバル変数"

func main() {
    fmt.Println(globalVar)  // どこからでもアクセス可能
    
    foo()
}

func foo() {
    fmt.Println(globalVar)  // 他の関数からもアクセス可能
}

関数レベル変数

package main

import "fmt"

func main() {
    // 関数内でのみ有効
    localVar := "ローカル変数"
    fmt.Println(localVar)
    
    // foo()からはlocalVarにアクセスできない
}

func foo() {
    // fmt.Println(localVar)  // エラー!
}

ブロックレベル変数

package main

import "fmt"

func main() {
    x := 10
    
    if true {
        // ブロック内でのみ有効
        y := 20
        fmt.Println(x, y)  // 10 20
    }
    
    fmt.Println(x)  // 10
    // fmt.Println(y)  // エラー! yはif文の外では使えない
    
    for i := 0; i < 3; i++ {
        // iはforループ内でのみ有効
        fmt.Println(i)
    }
    
    // fmt.Println(i)  // エラー!
}

シャドーイング

内側のスコープで同じ名前の変数を宣言できます(推奨されません)。

package main

import "fmt"

var x = "パッケージレベル"

func main() {
    fmt.Println(x)  // パッケージレベル
    
    // 同じ名前の変数を宣言(シャドーイング)
    x := "関数レベル"
    fmt.Println(x)  // 関数レベル
    
    if true {
        // さらに内側でシャドーイング
        x := "ブロックレベル"
        fmt.Println(x)  // ブロックレベル
    }
    
    fmt.Println(x)  // 関数レベル
}

7. 実用例

例1: カウンター

package main

import "fmt"

func main() {
    count := 0
    
    // カウントアップ
    count++
    count++
    count++
    
    fmt.Println("カウント:", count)  // 3
    
    // リセット
    count = 0
    fmt.Println("カウント:", count)  // 0
}

例2: スワップ(値の交換)

package main

import "fmt"

func main() {
    a := 10
    b := 20
    
    fmt.Println("交換前:", a, b)  // 10 20
    
    // Goの多重代入を使った交換
    a, b = b, a
    
    fmt.Println("交換後:", a, b)  // 20 10
}

例3: 累積計算

package main

import "fmt"

func main() {
    numbers := []int{10, 20, 30, 40, 50}
    
    sum := 0
    for _, num := range numbers {
        sum += num
    }
    
    fmt.Println("合計:", sum)  // 150
    
    average := float64(sum) / float64(len(numbers))
    fmt.Printf("平均: %.2f\n", average)  // 30.00
}

例4: 状態管理

package main

import "fmt"

type State int

const (
    StateInit State = iota
    StateRunning
    StateStopped
)

func main() {
    state := StateInit
    fmt.Println("状態:", state)  // 0
    
    state = StateRunning
    fmt.Println("状態:", state)  // 1
    
    if state == StateRunning {
        fmt.Println("実行中です")
    }
}

例5: ポインタを使った関数

package main

import "fmt"

func increment(x *int) {
    *x = *x + 1
}

func main() {
    count := 0
    fmt.Println("初期値:", count)  // 0
    
    increment(&count)
    fmt.Println("1回目:", count)  // 1
    
    increment(&count)
    fmt.Println("2回目:", count)  // 2
}

まとめ: 変数で覚えておくべきこと

変数宣言の3つの方法

// 1. var (型指定)
var age int = 25

// 2. var (型推論)
var name = "太郎"

// 3. 短縮宣言(関数内のみ)
score := 95

ゼロ値の重要性

var x int     // 0 (自動初期化)
var s string  // "" (空文字列)
var b bool    // false
var p *int    // nil

変数 vs 定数

特徴変数定数
値の変更可能不可
宣言var, :=const
用途変化する値固定値

実用的なアドバイス

package main

import "fmt"

func main() {
    // 1. 適切な命名
    userName := "太郎"  // OK
    u := "太郎"         // 短すぎ(避ける)
    
    // 2. ゼロ値を活用
    var count int  // 0で初期化される
    
    // 3. スコープを最小限に
    if x := getValue(); x > 0 {
        // xはこのif文内でのみ有効
        fmt.Println(x)
    }
    
    // 4. 未使用変数はエラー
    // unused := 123  // コンパイルエラー!
    _ = 123  // 空白識別子で無視
}

func getValue() int {
    return 42
}

変数は、プログラムの状態を保持する基本的な仕組みです。適切に使うことで、読みやすく保守しやすいコードが書けます!

おわりに 

本日は、Go言語の言語仕様について解説しました。

よっしー
よっしー

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。

それでは、また明日お会いしましょう(^^)

コメント

タイトルとURLをコピーしました