はじめに
【Go入門】Golang基礎入門+αの備忘録#2の続き。
この記事は以下のUdemyコースの学習記録です。
重要だと思った部分や感想、復習用に何を学んだかを書いていきます。
セクション10:ポインタ
ポインタ
ポインタの基本を学んだ。
簡単にまとめる
Goの値型と参照型とポインタ型についてまとめる。
- 値型
値型は代入する度に新しいメモリ領域(スタック上のメモリ領域)が確保される。(int,float,bool,stringなど。ただし、stringは内部的には参照型なような挙動をする)。
関数に渡すと新たな変数としてコピーされ、その関数内のローカル変数となる。このローカル変数のデータをどれだけ変えても関数外の元の変数は変更されない。 - 参照型
各参照型は構造体として定義されており、構造体内にはポインタが含まれているため、ポインタ型のような動作をする。 - ポインタ型
メモリアドレスのみ直接もたせることができる。
ポインタ型のような動作とは?
参照型はGoの内部実装でポインタを含む構造体
になっている。そのため、参照型を宣言するとその参照型の内部(構造体)の設計によって同時にヒープ上のメモリアドレスも確保されるため、ポインタのような動作をする(ポインタ+メタデータ(例:slice→ポインタ、len、cap)を値渡しし、それによってポインタのように動作する)。
本題
ポインタ型
- ポインタ型とはアドレス(メモリ上の特定の場所(どこを指しているのか)を示す情報)情報のみを格納する型。
- ポインタとはアドレスという情報を持つデータ。
- 基本的にデータは宣言するとメモリ上のどこかに割り当てられるため、データ毎に固有のメモリアドレスが存在する。このメモリアドレスを取得し、ポインタ型に代入することでデータを関節的に参照、操作することができる。
- 型の前に
*
をつけてポインタ型を表す。&n
とすることで変数nのメモリアドレスを取得する。
ポインタ型pに変数nのメモリアドレスを代入する。(基本型のpにメモリアドレスを代入すると型の不一致でコンパイルエラーになる)。
p *int = &n
ポインタが指す値を取得する(デリファレンス)
*p
ポインタ型の変数に、異なる関数間で共有したいデータのメモリアドレスを代入することで、異なる関数間で同じメモリ領域のデータを共有できる。
func updateValue(pointer *int) {
*pointer = 100 // ポインタを使って値を変更
}
func main() {
num := 10
fmt.Println(num) // 出力結果:10
updateValue(&num) // numのアドレスを取得したポインタを渡す
fmt.Println(num) // 出力結果:100
}
主要な参照型整理
各参照型の構造体の各フィールドにコメントで説明追加(ChatGPTで日本語訳+追加)
slice
sliceの実態。
type slice struct {
array unsafe.Pointer // ポインタ
len int // 長さ
cap int // 容量
}
- スライスを宣言すると、内部的にはスライス構造体(slice)が生成される。このスライス構造体にはポインタが含まれているため、スライスのコピーが行われると、スライス構造体に含まれるポインタもコピーされるため、スライスはポインタのような動作をする。
map
mapの実態。
type hmap struct {
// count: マップ内の有効なキーと値のペアの数
// ※ len(map) の計算に使われるため、最初のフィールドである必要がある
count int
flags uint8 // マップの状態を示すフラグ
B uint8 // バケット数の指数 (2^B 個のバケットを持つ)
noverflow uint16 // 溢れたバケットの数(概算値)
hash0 uint32 // ハッシュ計算のためのシード値 (ランダムな値)
buckets unsafe.Pointer // 2^B 個のバケットを格納する配列へのポインタ (マップが空なら nil)
oldbuckets unsafe.Pointer // マップのリサイズ時に使われる、古いバケット配列へのポインタ (通常は nil)
nevacuate uintptr // リサイズの進行状況を示すカウンタ (この値より小さいバケットは移行済み)
clearSeq uint64 // クリア処理のためのシーケンス番号
extra *mapextra // オプションの追加フィールド (マップの種類によって異なる)
}
hmapのhはハッシュを意味するそう。mapはデータ構造がハッシュテーブルで、が〇〇でこういう名前になっているらしい。
参考
【Go】Mapの内部構造とO(1)のメカニズム)
Go言語: ポインターとそれに関する型(uintptr, unsafe.Pointer)
channel
channelの実態。
type hchan struct {
qcount uint // 現在のキュー内の要素数
dataqsiz uint // キューのサイズ(バッファサイズ、0なら非バッファチャネル)
buf unsafe.Pointer // dataqsiz 要素分の配列を指すポインタ(バッファ)
elemsize uint16 // 要素のサイズ(バイト単位)
synctest bool // true の場合、同期テスト用のバブル内で作成されたことを示す
closed uint32 // チャネルが閉じられているかどうか (0: 開いている, 1: 閉じている)
timer *timer // チャネルに関連するタイマー(タイムアウトなどに使用)
elemtype *_type // チャネルで扱う要素の型情報
sendx uint // 送信時のインデックス(バッファのリングバッファ用)
recvx uint // 受信時のインデックス(バッファのリングバッファ用)
recvq waitq // 受信待ちのゴルーチンリスト(ブロックしている受信側)
sendq waitq // 送信待ちのゴルーチンリスト(ブロックしている送信側)
// 🔒 lock: hchan の全フィールドと、チャネルでブロックされている goroutine の一部の状態を保護するミューテックス
//
// ⚠️ lock を保持したまま別の goroutine の状態を変更しないこと!
// (特に、別の goroutine をレディ状態にしないこと)。
// これは、スタックの縮小とデッドロックを引き起こす可能性があるため。
lock mutex
}
interface
interfaceの実態
type iface struct {
tab *itab // 型情報 (メソッドテーブル)
data unsafe.Pointer // 実際のデータへのポインタ
}
func
funcの実態。
type funcval struct {
fn uintptr // 関数の実行コードへのポインタ
// ここから先は可変サイズのデータ領域
// クロージャの場合、キャプチャされた変数がここに格納される
}
一旦フィールドに何があるかだけ理解。
参考
データ型について動作確認(基本型・合成型・参照型・型変換[キャスト])
Golang_ポインタ型と参照型 #460
もう怖くない!Goのポインタを理解しよう!