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

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

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

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

スポンサーリンク

背景

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

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

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

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

Pointer types(ポインタ型)

ポインタ型は、指定された型の変数へのすべてのポインタの集合を示します。この型はポインタの基底型と呼ばれます。初期化されていないポインタの値はnilです。

PointerType = "*" BaseType .
BaseType    = Type .
*Point
*[4]int

解説

ポインタとは何か?

ポインタ(pointer) は、変数の**メモリアドレス(住所)**を保持する型です。値そのものではなく、「値がどこにあるか」を指し示します。

たとえ話: ポインタは「住所」のようなものです。家(変数)そのものではなく、「〇〇市△△町1-2-3」という住所を持っています。住所を知っていれば、その家を訪ねて中身を見たり、変更したりできます。

package main

import "fmt"

func main() {
    // 通常の変数
    x := 42
    
    // ポインタ: xのアドレスを取得
    ptr := &x
    
    fmt.Println("xの値:", x)           // 42
    fmt.Println("xのアドレス:", ptr)    // 0xc0000... (メモリアドレス)
    fmt.Println("ポインタ経由の値:", *ptr) // 42
    
    // ポインタ経由で値を変更
    *ptr = 100
    fmt.Println("変更後のx:", x)       // 100
}

1. ポインタの基本

ポインタの宣言

package main

import "fmt"

func main() {
    // int型のポインタ
    var p1 *int
    fmt.Println(p1) // <nil> (初期化されていない)
    
    // string型のポインタ
    var p2 *string
    fmt.Println(p2) // <nil>
    
    // 構造体のポインタ
    type Person struct {
        Name string
        Age  int
    }
    var p3 *Person
    fmt.Println(p3) // <nil>
}

ゼロ値はnil

初期化されていないポインタの値はnilです。

package main

import "fmt"

func main() {
    var ptr *int
    
    // nilチェック
    if ptr == nil {
        fmt.Println("ポインタはnilです")
    }
    
    // nilポインタを参照外しするとパニック
    // fmt.Println(*ptr) // panic: runtime error
}

2. アドレス演算子(&)と参照外し(*)

&演算子: アドレスを取得

package main

import "fmt"

func main() {
    x := 42
    
    // &演算子でアドレスを取得
    ptr := &x
    
    fmt.Printf("xの値: %d\n", x)
    fmt.Printf("xのアドレス: %p\n", ptr)
    fmt.Printf("xのアドレス(別の表記): %p\n", &x)
}

*演算子: 参照外し(dereference)

package main

import "fmt"

func main() {
    x := 42
    ptr := &x
    
    // *演算子でポインタの指す値にアクセス
    fmt.Println("ポインタ経由の値:", *ptr) // 42
    
    // ポインタ経由で値を変更
    *ptr = 100
    
    fmt.Println("変更後のx:", x)      // 100
    fmt.Println("変更後の*ptr:", *ptr) // 100
}

3. 様々な型のポインタ

基本型のポインタ

package main

import "fmt"

func main() {
    // int型
    var i int = 42
    var pi *int = &i
    
    // string型
    var s string = "Hello"
    var ps *string = &s
    
    // bool型
    var b bool = true
    var pb *bool = &b
    
    fmt.Println(*pi, *ps, *pb)
}

配列のポインタ

package main

import "fmt"

func main() {
    arr := [4]int{1, 2, 3, 4}
    
    // 配列のポインタ
    ptr := &arr
    
    // ポインタ経由で要素にアクセス
    fmt.Println((*ptr)[0]) // 1
    
    // Goでは自動的に参照外ししてくれる
    fmt.Println(ptr[0]) // 1
    
    // 要素の変更
    ptr[0] = 10
    fmt.Println(arr) // [10 2 3 4]
}

構造体のポインタ

package main

import "fmt"

type Point struct {
    X, Y int
}

func main() {
    p := Point{X: 10, Y: 20}
    
    // 構造体のポインタ
    ptr := &p
    
    // Goでは (*ptr).X と書かなくても良い
    fmt.Println(ptr.X) // 10 (自動的に参照外し)
    
    // フィールドの変更
    ptr.X = 30
    fmt.Println(p.X) // 30
}

4. newによるポインタの作成

new()関数は、指定した型のゼロ値を持つ変数を作成し、そのポインタを返します。

package main

import "fmt"

func main() {
    // newでポインタを作成
    ptr := new(int)
    
    fmt.Println(ptr)  // アドレス
    fmt.Println(*ptr) // 0 (ゼロ値)
    
    // 値を設定
    *ptr = 42
    fmt.Println(*ptr) // 42
}

newと&の比較

package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

func main() {
    // 方法1: new
    p1 := new(Person)
    p1.Name = "太郎"
    p1.Age = 25
    
    // 方法2: &と複合リテラル
    p2 := &Person{
        Name: "花子",
        Age:  30,
    }
    
    fmt.Printf("%+v\n", p1)
    fmt.Printf("%+v\n", p2)
}

5. ポインタと関数

値渡し vs ポインタ渡し

package main

import "fmt"

// 値渡し(コピーが渡される)
func incrementByValue(x int) {
    x = x + 1
    fmt.Println("関数内:", x)
}

// ポインタ渡し(アドレスが渡される)
func incrementByPointer(x *int) {
    *x = *x + 1
    fmt.Println("関数内:", *x)
}

func main() {
    a := 10
    
    incrementByValue(a)
    fmt.Println("値渡し後:", a) // 10 (変わらない)
    
    incrementByPointer(&a)
    fmt.Println("ポインタ渡し後:", a) // 11 (変わる)
}

構造体とポインタ

package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

// 値レシーバ(コピー)
func (p Person) HaveBirthdayValue() {
    p.Age++
    fmt.Println("関数内:", p.Age)
}

// ポインタレシーバ(元の値を変更)
func (p *Person) HaveBirthdayPointer() {
    p.Age++
    fmt.Println("関数内:", p.Age)
}

func main() {
    person := Person{Name: "太郎", Age: 25}
    
    person.HaveBirthdayValue()
    fmt.Println("値レシーバ後:", person.Age) // 25 (変わらない)
    
    person.HaveBirthdayPointer()
    fmt.Println("ポインタレシーバ後:", person.Age) // 26 (変わる)
}

6. ポインタのポインタ

ポインタのアドレスを保持するポインタも作れます。

package main

import "fmt"

func main() {
    x := 42
    
    // xのポインタ
    p := &x
    
    // ポインタのポインタ
    pp := &p
    
    fmt.Println("x:", x)      // 42
    fmt.Println("*p:", *p)    // 42
    fmt.Println("**pp:", **pp) // 42
    
    // ポインタのポインタ経由で値を変更
    **pp = 100
    fmt.Println("x:", x) // 100
}

7. nilポインタ

nilチェック

package main

import "fmt"

func processValue(ptr *int) {
    if ptr == nil {
        fmt.Println("nilポインタです")
        return
    }
    
    fmt.Println("値:", *ptr)
}

func main() {
    var p1 *int
    processValue(p1) // nilポインタです
    
    x := 42
    p2 := &x
    processValue(p2) // 値: 42
}

nilポインタのメソッド呼び出し

package main

import "fmt"

type Person struct {
    Name string
}

func (p *Person) Greet() {
    if p == nil {
        fmt.Println("nilです")
        return
    }
    fmt.Println("こんにちは、", p.Name)
}

func main() {
    var p *Person
    p.Greet() // nilです (nilレシーバでも呼び出せる)
    
    p2 := &Person{Name: "太郎"}
    p2.Greet() // こんにちは、 太郎
}

8. 実用例

例1: オプショナルな値

package main

import "fmt"

type Config struct {
    Host    string
    Port    *int  // nilならデフォルト値を使う
    Timeout *int
}

func main() {
    port := 8080
    
    config := Config{
        Host: "localhost",
        Port: &port,
        // Timeoutは指定しない(nil)
    }
    
    // ポート
    if config.Port != nil {
        fmt.Println("ポート:", *config.Port)
    } else {
        fmt.Println("ポート: デフォルト")
    }
    
    // タイムアウト
    if config.Timeout != nil {
        fmt.Println("タイムアウト:", *config.Timeout)
    } else {
        fmt.Println("タイムアウト: デフォルト")
    }
}

例2: 大きな構造体の効率的な処理

package main

import "fmt"

type LargeStruct struct {
    Data [1000000]int
}

// 値渡しだとコピーが発生(非効率)
func processByValue(s LargeStruct) {
    // ...
}

// ポインタ渡しはコピー不要(効率的)
func processByPointer(s *LargeStruct) {
    s.Data[0] = 100
}

func main() {
    large := LargeStruct{}
    
    // processByValue(large) // 遅い
    processByPointer(&large) // 速い
    
    fmt.Println(large.Data[0])
}

例3: リンクリスト

package main

import "fmt"

type Node struct {
    Value int
    Next  *Node  // 次のノードへのポインタ
}

func main() {
    // リンクリストの作成
    node1 := &Node{Value: 1}
    node2 := &Node{Value: 2}
    node3 := &Node{Value: 3}
    
    node1.Next = node2
    node2.Next = node3
    
    // リストの走査
    current := node1
    for current != nil {
        fmt.Print(current.Value, " -> ")
        current = current.Next
    }
    fmt.Println("nil")
    // 出力: 1 -> 2 -> 3 -> nil
}

例4: 二分木

package main

import "fmt"

type TreeNode struct {
    Value int
    Left  *TreeNode
    Right *TreeNode
}

func (n *TreeNode) Insert(value int) {
    if value < n.Value {
        if n.Left == nil {
            n.Left = &TreeNode{Value: value}
        } else {
            n.Left.Insert(value)
        }
    } else {
        if n.Right == nil {
            n.Right = &TreeNode{Value: value}
        } else {
            n.Right.Insert(value)
        }
    }
}

func (n *TreeNode) InOrder() {
    if n == nil {
        return
    }
    n.Left.InOrder()
    fmt.Print(n.Value, " ")
    n.Right.InOrder()
}

func main() {
    root := &TreeNode{Value: 5}
    root.Insert(3)
    root.Insert(7)
    root.Insert(1)
    root.Insert(9)
    
    root.InOrder()
    // 出力: 1 3 5 7 9
}

9. ポインタの比較

ポインタ同士の比較

package main

import "fmt"

func main() {
    x := 42
    y := 42
    
    p1 := &x
    p2 := &x  // 同じ変数を指す
    p3 := &y  // 別の変数を指す
    
    fmt.Println(p1 == p2) // true (同じアドレス)
    fmt.Println(p1 == p3) // false (異なるアドレス)
    
    // 値が同じでもアドレスが違えば異なる
    fmt.Println(*p1 == *p3) // true (値は同じ)
}

nilとの比較

package main

import "fmt"

func main() {
    var p1 *int
    x := 42
    p2 := &x
    
    fmt.Println(p1 == nil) // true
    fmt.Println(p2 == nil) // false
    
    // nilポインタ同士
    var p3 *int
    fmt.Println(p1 == p3) // true
}

10. よくある間違いと注意点

間違い1: nilポインタの参照外し

package main

func main() {
    var ptr *int
    
    // これはパニック!
    // fmt.Println(*ptr) // panic: runtime error
    
    // 正しい書き方
    if ptr != nil {
        fmt.Println(*ptr)
    }
}

間違い2: ローカル変数のアドレスを返す

package main

func createPointer() *int {
    x := 42
    // xは関数終了後も有効(Goは自動的にヒープに確保)
    return &x // OK (ガベージコレクションがある)
}

func main() {
    ptr := createPointer()
    fmt.Println(*ptr) // 42 (正しく動作する)
}

注意: Goはガベージコレクションがあるので、C言語のようなダングリングポインタの問題は起きません。

間違い3: スライスとマップの混同

package main

func main() {
    // スライスは参照型(内部的にポインタを持つ)
    slice := []int{1, 2, 3}
    modifySlice(slice)
    fmt.Println(slice) // [100 2 3] (変更される)
    
    // 配列は値型
    arr := [3]int{1, 2, 3}
    modifyArray(arr)
    fmt.Println(arr) // [1 2 3] (変更されない)
}

func modifySlice(s []int) {
    s[0] = 100
}

func modifyArray(a [3]int) {
    a[0] = 100
}

まとめ: ポインタ型で覚えておくべきこと

ポインタの基本

  1. アドレスを保持: 変数の「住所」を持つ
  2. ゼロ値はnil: 初期化されていないポインタはnil
  3. &演算子: アドレスを取得
  4. *演算子: 参照外し(値にアクセス)

宣言と使用

// 宣言
var ptr *int

// アドレス取得
x := 42
ptr = &x

// 参照外し
value := *ptr

// 値の変更
*ptr = 100

関数との使用

// 値渡し(コピー)
func byValue(x int) {
    x = 100 // 元の変数は変わらない
}

// ポインタ渡し(参照)
func byPointer(x *int) {
    *x = 100 // 元の変数が変わる
}

newとの比較

// new
ptr1 := new(int)
*ptr1 = 42

// &
x := 42
ptr2 := &x

実用的なアドバイス

package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

func main() {
    // 1. nilチェックを忘れずに
    var ptr *int
    if ptr != nil {
        fmt.Println(*ptr)
    }
    
    // 2. 大きな構造体はポインタで渡す
    p := &Person{Name: "太郎", Age: 25}
    updatePerson(p)
    
    // 3. スライス・マップは既に参照型
    slice := []int{1, 2, 3}
    modifySlice(slice) // ポインタ不要
    
    // 4. メソッドはポインタレシーバを使うことが多い
    p.HaveBirthday()
    
    fmt.Println(p.Age)
}

func updatePerson(p *Person) {
    p.Age++
}

func modifySlice(s []int) {
    s[0] = 100
}

func (p *Person) HaveBirthday() {
    p.Age++
}

ポインタを使うべき場合

  1. 関数で元の値を変更したい
  2. 大きな構造体を効率的に渡したい
  3. オプショナルな値を表現したい(nilが使える)
  4. データ構造(リンクリスト、木構造など)

ポインタは、効率的で柔軟なプログラミングを可能にする重要な機能です。適切に使うことで、メモリ効率が良く、保守性の高いコードが書けます!

おわりに 

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

よっしー
よっしー

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

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

コメント

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