はじめに
この記事の対象は
- ポインタってなんとなく聞いたことあるけど詳しく説明できない
- Goを入門したばかりでポインタの扱い方が分からない
という方を対象としています。
**「Goの変数宣言ってどうするの??」**って方は一旦そちらを学んでからの方が理解できるかと思います!
また、この記事を読んでくださって、もし「認識が違うよ!」「ここ間違ってるよ!」などがあればご教授ください!
よろしくお願いします!
出てくるワード
この記事に関係するワードとして、
- 変数
- メモリ
- アドレス
- ポインタ
- ポインタ型
- ポインタ変数
- ポインタ値
- デリファレンス
などが出てきます。
今分からなくても、それぞれ説明するので安心してください!
(知らないワードがいくつも出たら思考停止しちゃいますよね...。)
変数とメモリとアドレスの関係
それぞれどんなものかをざっくり説明すると、
メモリ...データが入ってくる箱のイメージ。
アドレス...メモリを特定する為の住所のイメージ。メモリにデータが格納される時にメモリに付いてくる。
変数...データを格納するメモリに名前を付けるイメージ。
もうちょっと詳細に説明すると、
var num int = 100
上記のような変数宣言をすると、100と言うデータが、どこかのメモリに保存されます。
すると、この時にそれぞれのメモリを区別する為に番号が振られます。これがアドレスです。
ただ、このアドレスは16進数で表されて分かりにくいので変数と言う名前を付けて、人間に分かりやすくしています。
Goではどういう記法で書くか
例えばメモリを確保するということは、変数宣言すれば良いわけです。
アドレスの参照は、変数の前に&
(アンパサンド)を付けてあげればOKです。
package main
import "fmt"
func main() {
// 変数宣言+値代入
var num int = 100
// メモリに格納された値を表示
fmt.Println(num) // => 100(メモリの値)
// メモリのアドレスを表示
fmt.Println(&num) // => 0xc00009a008(16進数のアドレス)
}
【イメージ】
【参考】
数値を記憶する - 苦しんで覚えるC言語
変数とメモリの関係 - 苦しんで覚えるC言語
ポインタとは
ポインタってなんでしょう...?
ざっくり説明すると、
「アドレスの値を記憶する変数」のことを一般的には言うみたいです。
もっと詳細に説明
「アドレスを記憶する変数 - 苦しんで覚えるC言語」には下記のように書かれています。
世間では、アドレスの値を記憶する変数をポインタと呼んでいますが、
これは本当はあまり正確な呼び方ではありません。
何故なら、ポインタとは、アドレスを扱う機能3つの総称だからです。
ポインタという呼び方は総称であり、正確には3種類に別れています。
ポインタとは、アドレスを扱う機能3つの総称だそうです。
で、このポインタの3つと言うのがポインタ変数・ポインタ型・ポインタ値です。
まずはこの3つワードの説明。
ポインタ型
int型を「intを記憶する変数」の型、と表現するなら、ポインタ型は**「アドレスを記憶する変数」の型**です。
int型やstring型などの型の一種です。
ポインタ変数
ポインタ型で宣言された変数のことを指します。int型やstring型の変数と変わりありません。
ポインタ変数が記憶できる値はアドレスです。
他の型の変数と同じく、別のアドレス(ポインタ値)を代入できます。
「記憶しているアドレス」のメモリの値を取得・書き換えが可能です。
ポインタ値
**ポインタ変数が記憶できる値(アドレス)**のことを指します。
Goではどういう記法で書くか
次に、このポインタに関してGoではどのような書き方をするか見てみましょう。
以下のコードはGoでのポインタの記法です。
// メモリを確保(アドレス付き)
var num int = 100
fmt.Println(num) // => 100
// ポインタ変数宣言+アドレス代入
var pointer *int = &num
fmt.Println(pointer) // => 0xc00009a008
*int
がポインタ型、pointer
がポインタ変数、&num
がポインタ値にあたります。
*int
(ポインタ型)の*
はポインタ型を表す記号です。もしもstring型の値が格納されたアドレスを代入した場合は*string
となります。
&num
(ポインタ値)は、先ほど説明した通りアドレスのことです。この場合、num
のアドレス。
var pointer *int = &num
でポインタ変数の宣言とアドレス代入を行っています。
変数pointer
には変数num
のアドレスを代入しているので、当然「変数pointer
のメモリの値」と「変数num
のアドレス」は同じ値になります。
var num int = 100
fmt.Printf("value=%v type=%T", &num, num) // => value=0xc00009a008 type=int
var pointer *int = &num
fmt.Printf("value=%v type=%T", pointer, pointer) // => value=0xc00009a008 type=*int
ポインタ変数から値にアクセスする
繰り返しになりますがポインタ変数にはポインタ値、つまりアドレスが記憶されています。
しかし、このポインタ変数の前に*
を付けるとポインタ変数に記憶されているアドレスの中身の値にアクセスすることが可能になります。
var num int = 100
var pointer *int = &num
fmt.Println(num) // => 100
fmt.Println(*pointer) // => 100
デリファレンス
いきなりですが、ここで質問です。
以下のコードはどのような値が出力されるでしょうか??
package main
import "fmt"
func main() {
var num int = 100
var pointer *int = &num
*pointer = 999
fmt.Println(num) // => ???
fmt.Println(*pointer) // => ???
}
正解は
.
.
.
fmt.Println(num) // => 999
fmt.Println(*pointer) // => 999
どちらも「999」になります。どちらもです!
解説
まず変数pointer
はポインタ変数です。
ポインタ変数にはアドレスが記憶されるんでしたね。
で、*pointer
ですが、ポインタ変数の前に*
を付けるとポインタ変数に記憶されているアドレスの中身の値にアクセスできると説明しました。
結局*pointer = 999
は何をしているかと言うと、*pointer
つまり、記憶されているアドレスの中身の値つまり、num
のメモリの値を999
に変更している。と言うことです。
なので、どちらも999
になると言う訳です。
ちなみに、「ポインタ変数*pointer
は記憶されているアドレスの中身の値にアクセスできる」と言いましたが、このことをデリファレンスと呼びます。
参照元から参照先の値を得ることを特に指す語
引用元:間接参照 - Wikipedia
雑談:学んでいて面白いと思った部分
C言語(Goも)って参照渡しってのはできない
C言語(Goも)って参照渡しってのはできないんですね。
C言語では、あくまでも値渡ししか出来ないのですが、
アドレスを渡すことを慣習的に参照渡しと呼ぶことがあります。
&
付き変数の正体はアドレスを求める演算子
ちなみに、&
付き変数の正体についてはこんな記述がありました。
この&付き変数の正体は、非常に単純です。
実は、&は変数のアドレスを求める演算子なのです。
参照:&つけが必要な変数の正体
まあまあ&
に関しては気になっていたので紹介しました。
以上です。
まとめ
逃げてきたポインタの理解をする為にこの記事を書いていたのですが、調べだすとなかなか面白い領域だと感じました!
知らないことを知っていくって面白いですね〜。(しんどい時もあるけど...)
もし、認識が間違っているとか、「ここどう言うこと?」って言うのがあれば是非教えて下さい!
よろしくお願いします!
それでは、これからも一緒に良いエンジニアライフを歩みましょ〜
お疲れ様でした!
参考にさせて頂いたサイト
どのサイトもまとめ方が上手くて憧れます。そしてポインタの理解にとても役立ちました。
本当にありがとうございます!
数値を記憶する - 苦しんで覚えるC言語
変数とメモリの関係 - 苦しんで覚えるC言語
ポインタ (pointer)とは|「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典
ポインタ変数とは|「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典
Goで学ぶポインタとアドレス
アドレスを記憶する変数 - 苦しんで覚えるC言語
C言語のポインタ構文のつまづきどころ
デリファレンス (dereference)とは|「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典
間接参照 - Wikipedia
&つけが必要な変数の正体 - 苦しんで覚えるC言語