はじめに
こんにちは、なかじです!
今回は、ポインタの疑問です!
『Goにポインタていつ使うの?』と思ってしまい、調査しましたので、記事にします〜
❓ ポインタとは何か?
この技術書の説明が自分的にわかりやすかった
プログラムが動作するためにはメモリ上に保存したデータを読み書きすることが欠かせません。
あるデータを読むためには、そのデータが書き込まれている場所とどこまで書かれているかという範囲を知る必要があります。
ポインタはメモリ上のアドレス位置を格納した変数であり、変数を型とともに利用することでデータが保存されている範囲も知ることができます。
つまりポインタによって、メモリ上のどのアドレスを起点にどれだけの大きさのメモリを読み取るかということがわかります。
ポインタ型の変数から実体を取得する操作(デリファレンス)や逆に実体からその参照を取得する操作は、メモリ上に書かれているデータ自体を取得したいのか、データが書かれている範囲の先頭アドレスを取得したいかによって使い分けます。
つまり、、、
プログラムが動作するために
- プログラムは、メモリ上に保存されたデータを読み書きすることで動作する
- データを扱うためには、そのデータがメモリ上のどこにあるか(アドレス)、それがどの範囲にわたっているかを知る必要がある
ポインタとは
- ポインタは、メモリ上の特定のアドレスを指し示す変数
- 変数の型を指定することで、そのポインタが示すメモリ領域の大きさや種類を知ることができる
- ポインタを使用することで、プログラムはメモリ上のどこからどれだけのデータを読み取るかを把握できるようになる
ポインタ型の変数から実体を取得する操作
- ポインタ型の変数から実際のデータを取得
- ポインタが指し示すアドレスにあるデータの値を読み出す
実体から参照を取得する操作
- 特定のデータからそのデータのメモリアドレス(参照)を取得する
- 変数をポインタを通して参照できるようにする
❓ ポインタが他の言語とGo言語でどのように異なるか
Go言語のポインタについて、C言語のポインタと比較した点をここでは軽く紹介します!
Goの言語の特徴
- 読みやすい & 書きやすい
- 複雑な記述をしにくい(愚直に書く)
- 冗長な記述が不要(型推論など)- 曖昧さの排除
- 曖昧な記述はできない- 考えられたシンプルさ
- 各言語機能のベクトルが直交している
- 機能を増やすことで言語を拡張していくことはしない
Go言語の特徴としては、複数のプログラマーが携わる開発においてコードに大きな差が生まれず、ミスを引き起きにくい特徴があります!
ガベージコレクションについて
Go:
Goには、ガベージコレクション(不要になったメモリを解放する機能)があり、メモリリーク(使用していないメモリを開放することなく確保し続けてしまうこと)を防ぐことができる。
Goでは、ポインタを使ってもガベージコレクタが不要になったメモリを回収する
C/C++:
C/C++では、ポインタを通じて動的に確保したメモリは、適切に解放する必要がある。
メモリ領域を確保するためにmalloc()
関数、使わなくなったメモリ領域を解放するためにはfree()
関数があり、メモリ解放し忘れると、メモリの使用領域が増加し、次のメモリ領域が確保できなくなり、エラーやプログラム終了を起こす。
C言語が登場した時期はそもそもメモリ容量が少なく、厳格なメモリ管理が必要不可欠だったためらしい。
ポインタ演算の制限
Go:
Goには、ポインタ演算の機能なし。
これは、Goの安全性とシンプルさの特徴からなのかなと思います。
ポインタを使用する場合でも、ポインタ演算による複雑さや危険性を排除しています。
C/C++:
C/C++には、ポインタ演算が可能。
ポインタ演算によってメモリ上の特定の位置に対する制御ができるようになる。
型安全性
Go:
静的型付けで、型安全性が高い。
C/C++:
型キャストで、異なる型のポインタ間での変換が可能。
❓ ポインタの基本的な使い方
基本構文の紹介をします!
基本的な使い方
演算子 | 説明 | 利用例 |
---|---|---|
& |
ポインタを取得 | &x |
* |
ポインタが指す値を取得 | *p |
func main() {
x := 1
var p *int = &x
fmt.Println(p) // xのメモリアドレス
fmt.Println(*p) // xの値
}
// 0xc000012028
// 10
関数とポインタ
func swap(a, b *int) {
*a, *b = *b, *a // aとbが指す値を入れ替え
}
func main() {
x, y := 100, 200
swap(&x, &y)
println(x, y)
}
// 200 100
ちなみに、ポインタを使わずに書くとこうなります!
func main() {
x, y := 100, 200
x, y = y, x // 直接的な値の交換
println(x, y)
}
上下で比較すると、ポインタの使い所というのがだんだん見えてきた気がします!
💡 Go言語におけるポインタの使用する場面
Go言語におけるポインタの使用する場面やポイントについてを考えてみました!
Go言語のガベージコレクションとポインタ
Goのガベージコレクションは、不要になったメモリを自動的に解放し、メモリリークを防ぎます。
ガベージコレクタは、アクティブなポインタを通じてアクセス可能なオブジェクトを特定します。
ポインタがオブジェクトを参照している限り、そのオブジェクトは「使用中」と見なされ、ガベージコレクタによって回収されません!
ガベージコレクションの使用例
例えば、TodoAppでは、ユーザーがタスクを追加して、追加されたタスクを管理します。タスクは動的に追加され、完了または削除されると、必要なくなります。
type Todo struct {
Task string
Next *Todo
}
func main() {
// Todoリストの初期化
todoList := &Todo{Task: "牛乳を買う"}
todoList.Next = &Todo{Task: "レポートを書く"}
...
}
// main関数が終了すると、todoListとそれに関連するタスクは使用されなくなり、
// ガベージコレクタはこれらのオブジェクトをメモリから解放します。
💡 ポインタを使うべきでない時
本記事の結論の部分ですが、以下の場合が、ポインタを使用すべきでない時だと思いました。
- 小さい構造体や不変のデータには値渡しを使うとき
- ポインタを使うと複雑さが増すとき
- ポインタを適切に管理できず、ガベージコレクションの効率が低下するとき
「ポインタっていつ使うん?」に対しての回答ではないですが、使うべきではない時を紹介させていただきました🙇
まとめ
ポインタは難しいですが、Goを理解するために必要なので、頑張って理解して使いましょう!
書いていて、改めて勉強になったので技術系の記事も続けていこうかな🚀
ここ違うよ!など、あればコメントお待ちしております〜
参考
PR
HRBrainでは一緒に働いてくれる仲間を募集しています!😁
アドベントカレンダー1記事目
アドベントカレンダー2記事目
アドベントカレンダー3記事目