Goのポインタについて1から学んでみた

ポインタは、Go言語で効率的で柔軟なコードを書くために不可欠な機能です。

今回は、具体的なコード例を交えながら、ポインタのより深い理解を目指します。

値渡しと参照渡し

Go言語では、関数の引数は基本的に「値渡し」されます。つまり、引数の値がコピーされて関数に渡されます。

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

func main() {
    a := 10
    increment(a)
    fmt.Println(a) // 10
}

このコードでは、increment関数内でxの値を変更しても、呼び出し元のaの値は変わりません。これは、increment関数にaの値のコピーが渡されているためです。

ポインタを使うと、「参照渡し」のように振る舞わせることができます。

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

func main() {
    a := 10
    increment(&a)
    fmt.Println(a) // 11
}

このコードでは、increment関数にaのアドレスが渡されています。関数内で*xの値を変更すると、aの値も変更されます。

構造体とポインタ

構造体を関数に渡す場合も、ポインタを使うことでメモリ効率を向上させることができます。

go
type Person struct {
    Name string
    Age  int
}

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

func main() {
    john := Person{Name: "John", Age: 30}
    celebrateBirthday(&john)
    fmt.Println(john.Age) // 31
}

このコードでは、celebrateBirthday関数にPerson構造体へのポインタを渡しています。関数内でp.Ageを変更すると、呼び出し元のjohn.Ageも変更されます。

ポインタとスライス

スライスは、内部的にポインタを使って実装されています。スライスを関数に渡すと、スライスの要素自体はコピーされず、スライスのヘッダ情報(ポインタ、長さ、容量)のみがコピーされます。

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

func main() {
    nums := []int{1, 2, 3}
    modifySlice(nums)
    fmt.Println(nums) // [100 2 3]
}

このコードでは、modifySlice関数内でスライスの要素を変更すると、呼び出し元のスライスの要素も変更されます。

ポインタのポインタ

ポインタのポインタとは、ポインタ変数のアドレスを格納するポインタです。

go
a := 10
p := &a
pp := &p

fmt.Println(*p) // 10
fmt.Println(**pp) // 10

ポインタのポインタは、複雑なデータ構造を扱う場合や、関数の引数としてポインタ自体を変更したい場合などに使われます。

nilポインタ

ポインタ変数は、初期化されていない状態や、何も指していない状態ではnilという値を持ちます。

go
var p *int
fmt.Println(p) // nil

nilポインタに対してデリファレンス(*p)を行うと、実行時パニックが発生します。ポインタを使う前に、nilチェックを行うことが重要です。

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

まとめ

ポインタは、Go言語で効率的で柔軟なコードを書くために不可欠な機能です。関数間でのデータの受け渡し、構造体の操作、スライスの理解、nilポインタの扱いなど、様々な場面でポインタが出てきます。