Go言語の構造体とポインタの関係!ポインタを使ったアクセス方法
生徒
「Go言語の構造体にポインタってどうやって使うんですか?」
先生
「構造体とポインタを組み合わせると、同じ変数を効率よく使い回すことができます。変更も共有されるので便利ですよ。」
生徒
「それってメモリとか関係してますか?」
先生
「はい、ポインタは“メモリの住所”のようなものです。詳しく説明していきましょう!」
1. ポインタとは?初心者向けにかんたん解説
ポインタ(pointer)とは、「変数が保存されている場所(メモリの番地)」を指し示す特別な変数です。ふつうの変数は数字や文字などの値そのものを入れていますが、ポインタはその値が置かれている場所を覚えています。
イメージで言うと、自分の家の場所を教える「住所」がポインタで、家の中にある家具などの中身が「値」です。住所だけ知っていれば、あとから家に行って中身を見たり並べ替えたりできますよね。同じように、ポインタがあれば変数の中身を直接変えることができます。
これがなぜ便利なのかというと、ポインタを使えば大きなデータを丸ごとコピーする必要がなくなり、プログラムが効率よく動くようになるからです。特に構造体のように複数の値をまとめて持つデータでは、ポインタがとても役に立ちます。
まずは、ポインタが「変数の場所を覚えているだけ」という点だけ理解できれば大丈夫です。下の短いサンプルを見ると、普通の変数とポインタ変数の違いが少しイメージしやすくなります。
num := 10
p := &num
fmt.Println(num) // 10
fmt.Println(*p) // 10(ポインタを使って中身を取り出せる)
&numで変数numの場所を取得し、*pを使うとその場所の中身を読み取れます。「場所を覚える」と「場所の中身を見る」という二段階がポインタの基本です。
2. 構造体にポインタを使う理由とは?
構造体は、名前や年齢などいくつかの情報をまとめたものです。この構造体を関数に渡すとき、ポインタを使うと効率的に処理できます。
ポインタを使わずに渡すと、構造体のコピーが作られます。でもポインタを使えば、元のデータそのものを扱えるので、無駄なコピーが減って処理が速くなります。
3. 構造体のポインタを作る基本文法
まずは構造体と、そのポインタを使ってみましょう。
type User struct {
Name string
Age int
}
func main() {
u := User{Name: "たろう", Age: 25}
ptr := &u // ポインタを作成
fmt.Println(ptr.Name) // ポインタ経由でも「.」でアクセスできる
}
&uでuのポインタを作っています。Go言語では、ポインタでもそのままptr.Nameのようにアクセスできます。
4. 関数にポインタを渡して値を変更する
構造体をポインタで関数に渡せば、中身を直接変更できます。
func updateAge(u *User) {
u.Age = 30 // ポインタを通じて元の変数を変更
}
func main() {
user := User{Name: "はなこ", Age: 20}
updateAge(&user)
fmt.Println(user.Age) // => 30
}
このように、*User型を引数にすることで、関数の中から直接データを更新できます。
5. new関数で構造体のポインタを作る方法
Go言語にはnew関数もあります。これを使うと、構造体のポインタを簡単に作成できます。
userPtr := new(User)
userPtr.Name = "じろう"
userPtr.Age = 18
newは、ゼロ値で初期化された構造体のポインタを返します。あとから値を代入すればOKです。
6. 構造体ポインタの応用:スライスと組み合わせる
構造体ポインタは、スライスと組み合わせて大量のデータを扱うときにも便利です。
users := []*User{
&User{Name: "たろう", Age: 25},
&User{Name: "はなこ", Age: 30},
&User{Name: "じろう", Age: 22},
}
このように、スライスの中にポインタを入れることで、メモリの無駄を省きながら柔軟にデータを操作できます。
7. 値型とポインタ型の違いをまとめて整理
- 値型:コピーされる。元のデータは変わらない。
- ポインタ型:参照される。元のデータが直接変わる。
ポインタをうまく使えば、プログラムの効率も見た目のコードもスッキリさせることができます。
まとめ
Go言語の構造体とポインタについて学んできました。とくに初心者がつまずきやすいのが、値型とポインタ型の違いです。ふだんの変数は値そのものを持ちますが、ポインタは値の置かれている場所の情報を持っています。この違いを理解すると、構造体を扱うときに無駄なコピーを防いだり、関数の内側から元の値を直接変更したりすることができるようになります。プログラムは目に見えない世界ですが、住所と家の関係に置き換えると、ポインタは「家の場所」を教えてくれる存在だということが分かります。家の場所さえ分かれば、中にある家具を見たり並べ替えたりできます。まさにそれがポインタの役割なのです。
とくに構造体は、名前や年齢などの複数の情報をまとめて扱うため、小さなデータでもコピーを繰り返すとメモリが無駄になります。ポインタを使えばその負担を抑えることができ、大量のデータを処理するときにも効率よく動かせます。たとえば構造体を関数に渡す場合、値型で渡すと関数の中で変更しても外側には反映されません。しかしポインタで渡せば、関数の内側で書き換えた内容がそのまま呼び出し元に反映されます。この仕組みを知っておくと、データ更新が必要な場面で自然に使えるようになります。
さらにGo言語は、ポインタ経由であっても構造体のフィールドにそのまま「ドット記法」でアクセスできます。多くの言語ではポインタ用の記号を使ったり特別な書き方をしたりしますが、Go言語では自動で参照外しを行ってくれるため、とても読みやすく、初心者でも扱いやすい設計になっています。この働きのおかげで、ポインタを使う場面でもコードが複雑になりません。書き方のルールがシンプルなため、構造体とポインタの関係に慣れるほど「便利だな」と思えるようになります。
また、ポインタとスライスを組み合わせると、大量の構造体を無駄なく扱えます。実体をコピーするのではなく、場所だけを並べるので、どれだけデータが増えても負担が少なくなります。スライスから取り出したポインタを通じて値を変更すると、その変更は同じデータを参照しているすべての場所に反映されます。データの一覧を管理したり、条件に応じて変更したりといった処理の幅が広がるため、実用的なプログラムでも役立ちます。
簡単なサンプルを振り返る
値型で渡した場合と、ポインタで渡した場合の違いをもう一度確認しましょう。
type User struct {
Name string
Age int
}
// 値型で受け取る(コピー)
func changeByValue(u User) {
u.Age = 99
}
// ポインタで受け取る(実体)
func changeByPointer(u *User) {
u.Age = 99
}
func main() {
user := User{Name: "たろう", Age: 20}
changeByValue(user)
fmt.Println(user.Age) // 20(コピーが変わっただけ)
changeByPointer(&user)
fmt.Println(user.Age) // 99(実体が変更される)
}
どちらも同じ構造に見えますが、値型は別の入れ物を作ってそこだけが変わります。対してポインタは元の入れ物に直接触れるため、関数を抜けたあとにも結果が残ります。この違いを知っておくだけで、Go言語の構造体を扱うときの考え方が一気に分かりやすくなります。
生徒
「今日の内容で、ポインタがなんのために使われるのか分かった気がします。メモリの住所を持っているから、同じ変数をみんなで共有できるという感じですね。」
先生
「その通りです。同じ構造体を何度もコピーする必要がなくなるので、無駄な処理が減ります。とくに大量のデータを扱うとき、ポインタはとても役に立ちます。」
生徒
「関数に渡したときの違いも印象に残りました。値型だとコピーだけど、ポインタだと実体なんですね。」
先生
「はい。値型は安全に扱えますが、更新が必要ならポインタのほうが向いています。状況に応じて使い分けると良いですね。」
生徒
「あと、Go言語が自動で参照外しをしてくれるのがとても書きやすかったです。他の言語でポインタを触ったときは難しい印象があったので、ちょっと安心しました。」
先生
「読みやすさと扱いやすさはGo言語の魅力のひとつです。構造体とポインタを覚えるだけで、さまざまな処理を効率よく書けるようになりますよ。」
生徒
「今日の内容を踏まえて、今度はスライスとポインタを使ってデータの一覧を扱う練習もしてみたいです。」
先生
「とても良いですね。まずは小さなサンプルを作りながら、少しずつ慣れていきましょう。理解が深まると、より複雑なプログラムでも落ち着いて書けるようになります。」
生徒
「ありがとうございます!次の学習も楽しみです!」