見出し画像

golang ポインタとは 使いどころと注意点について

今回はGo言語のポインタについて解説していきます。

ポインタとは

ポインタは変数のメモリアドレスを保持するためのデータ型です。

変数の実際の値ではなく、その値が格納されているメモリ上の位置であるため、直接変数の値を操作するのではなく、その値への参照を通じて操作を行います。

値渡しか参照渡しか

ポインタの理解を深めるには値渡しと参照渡しという概念について理解をしておく必要があります。

値渡しとは変数のコピーを作成して関数に渡します。そのため、関数内で変数の値を変更しても、元の変数の値は変更されません。

func main() {
    x := 10
    modify(x)
    fmt.Println(x)
}

func modify(y int) {
    y = 20
}

上記コードではmodify()に変数xを渡してyとして新たに20を代入しています。こちらは値渡しのため元の変数xの値は変更されず、10と出力されます。

逆に参照渡しは実際の変数への参照が関数に渡されます。そのため、関数内でその参照を通じて値を変更すると、呼び出し元の変数が変更されます。

func main() {
    x := 10
    modify(&x)
    fmt.Println(x)
}

func modify(y *int) {
    *y = 20
}

上記コードではmodifyに変数xの参照を渡してポインタを通じて値を変更しています。そのため出力結果は20と出力されます。

関数内で変数を更新して、元の変数に影響を与えるかどうかが値渡しか参照渡しの違いとなります。


ポインタの使い方

上記の説明でも少し触れましたが、ポインタは参照渡しの際に使われるデータ型となります。

package main

import "fmt"

func main() {
    x := 10
    p := &x

    fmt.Println("xの値:", x)
    fmt.Println("xのアドレス:", p)

    *p = 20
    fmt.Println("新しいxの値:", x)
}

ポインタは型の前にアスタリスク(*)を付けることで定義できます。例えば、int型の変数のポインタは*intとなります。

変数のアドレスを取得するには、アンパサンド(&)を使用します。例えば、var x intのアドレスは&xで取得できます。

ポインタを通じて値を参照するには、再びアスタリスク(*)を使用します。例えば、ポインタpが指す値にアクセスするには*pと記述します。


ポインタの使いどころ

ではポインタは実際にどのような場面で使用されるのか紹介していきます。

大きい構造体を操作する場合

package main

import "fmt"

type BigStruct struct {
    Data [100]int
}

func modifyWithPointer(s *BigStruct) {
    s.Data[0] = 100
}

func main() {
    big := BigStruct{}
    modifyWithPointer(&big)
    fmt.Println(big.Data[0])
}

大きな構造体や配列は、値として渡すとその全内容がコピーされてしまうためメモリ使用量やパフォーマンスに影響を与えてしまいます。
このような大きいデータ構造をポインタを通じて渡すことで、データのコピーを避けることができます。


関数によるデータの変更の共有

package main

import "fmt"

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

func main() {
    value := 5
    increment(&value)
    fmt.Println(value)
}

関数内で変数の値を変更して、それを呼び出し元と共有したい場合にポインタは有効です。


メソッドレシーバーにおける状態の変更

package main

import "fmt"

type Counter struct {
    Value int
}

func (c *Counter) Increment() {
    c.Value++
}

func main() {
    counter := Counter{}
    counter.Increment()
    fmt.Println(counter.Value)
}

構造体にメソッドを定義する場合、そのメソッドが構造体の状態を変更する必要がある場合があります。その際にレシーバーをポインタとして定義することで、メソッド内での変更がその構造体のインスタンスに反映されます。


インターフェースの実装

package main

import "fmt"

type Printer interface {
    Print()
}

type MyData struct {
    Value string
}

func (d *MyData) Print() {
    fmt.Println(d.Value)
}

func main() {
    var p Printer = &MyData{"Hello, World!"}
    p.Print()
}

ある型がインターフェースを実装する際、メソッドレシーバーがポインタ型か値型かによってその型の扱い方が変わります。

ポインタレシーバーを使うと、そのメソッドはその型のポインタのみがインターフェースを実装することになります。


ポインタの注意点

ヌルポインタのデリファレンス

var p *int
if p != nil {
    fmt.Println(*p)
}

ポインタがnilの場合にでデリファレンスすると、実行時にパニックが発生します。 ポインタがnilでないことを確認するか、初期化されていることを確認する必要があります。


ゴルーチン間でのポインタの共有

var mu sync.Mutex
var sharedVar int

func safeIncrement() {
    mu.Lock()
    sharedVar++
    mu.Unlock()
}

複数のゴルーチンが同じデータを指すポインタにアクセスすると、データ競合が発生する可能性があります。
ミューテックスなどを使用してアクセスを制御する必要があります。


不要なポインタの使用

ポインタは適切に使用すると効果的ですが、常に必要というわけではありません。
特にintやbool型など小さな組み込み型やインターフェース型は値として渡す方が効率的です。パフォーマンスや可読性のバランスを考えてポインタの使用を検討する必要があります。


golangをもっと詳しくなりたい方に

初めてのGo言語

Go言語の入門から応用まではこの一冊で網羅されています。説明も順序立てて説明されており完成度の高い参考書となっています。


詳解Go言語Webアプリケーション開発

こちらはGo言語の基礎知識を得たあとに読むことをすすめる本となっています。ハンズオン形式で手を動かしながら実装をしていくことができるため、実際の開発を意識しながらGo言語を学ぶことができます。

以下の記事では他にも筆者が実際に読んでおすすめしたい本をまとめています。


【Go入門】Golang基礎入門 + 各種ライブラリ + 簡単なTodoWebアプリケーション開発(Go言語)

まだGo言語の勉強を始めていない方、全くの初心者の方にはこちらのUdemy教材が最もおすすめです。

この教材ではgolangの基礎について網羅的に学ぶことができます。ただ見て学ぶだけでなく、講座の中でアプリ作成まで行うことで学んだことの理解をさらに深めることができます。


現役シリコンバレーエンジニアが教えるGo入門 + 応用でビットコインのシストレFintechアプリの開発

個人的に最も勉強になったのは「現役シリコンバレーエンジニアが教えるGo入門 + 応用でビットコインのシストレFintechアプリの開発」という教材です。
本格的なレクチャーに入る前に、なぜGoなのか、なぜFintechなのかについて説明されていることで、Goの概念や意味について大枠から理解することができます。

以下の記事では筆者が実際に受講したおすすめUdemy教材をまとめています。

※本ページではアフィリエイトリンク(PR)が含まれています


この記事が参加している募集

この記事が気に入ったらサポートをしてみませんか?