👉

Goのポインタについて

に公開

はじめに

Go言語では、ポインタは重要な概念の一つです。
ポインタを理解し適切に使用することで効率的なプログラムを書くことができます。
この記事では、Goにおけるポインタの使用方法と基本概念について解説します。

Goのポインタについて

Goにおけるポインタは、メモリアドレスを格納する変数です。
変数のメモリ位置に直接アクセスして、その値を変更する方法を提供します。

値渡しと参照渡しについて

Goにおけるポインタを適切に使い分けるためには、値渡し(Pass by Value)と参照渡し(Pass by Reference)の概念を理解する必要があります。

値渡しとは、変数の値そのものをコピーして渡す方法です。この場合、元の変数と渡された変数は別々のメモリ領域に存在します。一方、参照渡しはポインタを使用して変数のメモリアドレスを渡す方法で、関数内で同じメモリ領域を参照することができます。

Goでは、デフォルトですべての引数が値渡しとなります。ポインタを使用することで、明示的に参照渡しを行うことができます。これにより、大きなデータ構造の不必要なコピーを避けたり、関数内での変更を元の変数に反映させたりすることが可能になります。

値渡し(ポインタなし)

  • 引数として渡された値のコピーが作成される
  • 関数内で変更しても元の変数には影響しない
  • 大きな構造体の場合、メモリ効率が悪い場合がある

参照渡し(ポインタあり)

  • 引数として渡された変数のメモリアドレスが渡される
  • 関数内で変更すると元の変数も変更される
  • 大きな構造体の場合、メモリ効率が良い

ポインタで利用される基本的な演算子

  • &:変数のメモリアドレスを取得
  • *:ポインタが指す値を取得(デリファレンス)

使用例

type User struct {
    Name string
    Age  int
}

// 値渡し(ポインタなし)
func ValueFunction(user User) User {
    user.Age = 31  // 元の変数には影響しない
    return user
}

// 参照渡し(ポインタあり)
func ReferenceFunction(user *User) *User {
    user.Age = 31  // 元の変数も変更される
    return user
}

// 実行例
func main() {
    // ユーザーの作成
    user := User{
        Name: "yamada",
        Age:  30,
    }
    
    // 値渡しの場合
    newUser1 := ValueFunction(user)
    fmt.Println("値渡し後の元の値:", user) // 値渡し後の元の値: {yamada 30}
    fmt.Println("値渡し後の戻り値:", newUser1)  // 値渡し後の戻り値: {yamada 31}
    
    // 参照渡しの場合
    newUser2 := ReferenceFunction(&user)
    fmt.Println("参照渡し後の元の値:", user) // 参照渡し後の元の値: {yamada 31}
    fmt.Println("参照渡し後の戻り値:", *newUser2) // 参照渡し後の戻り値: {yamada 31}
}

ポインタを使うべき場合

大きな構造体を扱う場合

  • 構造体のコピーを避けることでメモリ使用量を削減できます
  • 多くのフィールドを持つ構造体や、スライスやマップなどの大きなデータ構造を含む構造体では特に効果的です
  • パフォーマンスクリティカルな部分では、不要なメモリコピーを避けるべきです

関数内で引数を変更したい場合

  • 元の変数に変更を反映させたい場合にポインタは必須です
  • 複数の値を変更して返したい場合に便利です
  • 例えば、ユーザー情報の更新や状態変更を行う関数で活用できます

nilを許容したい場合

  • ポインタはnilを許容するため、「値が存在しない」状態を表現できます
  • 値型はnilを許容せず、常にゼロ値(int型なら0、string型なら空文字列など)を持ちます。例えば *stringはnilを許容するため、「未設定」と「空の文字列」を区別できますが、stringは常に少なくとも空文字列になります

ポインタを使わないほうがよい場合

小さな基本型の場合

  • intfloat64stringboolなどの基本型はサイズが小さいため、値渡しでも効率的です
  • これらの型のコピーのコストは非常に低く、ポインタ操作のオーバーヘッドの方が大きい場合があります
  • 特に並行処理の場合、値型の方が安全に扱えることがあります

変更不要の読み取り専用データ場合

  • 関数内で変更しない場合は値渡しで十分です
  • 値渡しは意図しない変更から保護する効果があります
  • イミュータブル(不変)なデータ構造を設計する場合に適しています

並行処理で共有データを避けたい場合

  • 値型はコピーされるため、並行処理での予期しない変更を防げます
  • ポインタを使うと複数のゴルーチンから同じメモリにアクセスする可能性があり、競合状態を引き起こす恐れがあります

まとめ

  • ポインタを使うと、大きなデータ構造を効率的に扱え、関数内での変更を元の変数に反映できます
  • 小さな基本型や変更不要のデータには値渡しが適しています
GitHubで編集を提案

Discussion