Go言語のポインタに関連して、structについて見て行きたいと思います。
structとの関連についてはすでに少しだけですが「new()とポインタ」のところで触れました。
ここではもう少しstructとポインタについて見ておこうと思います。
struct型の確認
structについて一度確認してみましょう。
package main
import "fmt"
type example struct {
a int
b int
c string
}
func main() {
e1 := example{
a: 10,
b: 100,
c: "golang",
}
fmt.Println(e1)
fmt.Println(e1.a, e1.b, e1.c)
e1.a = 20
fmt.Println(e1.a, e1.b, e1.c)
e2 := example{a: 100}
fmt.Println(e2)
e3 := example{100, 200, "Programming"}
fmt.Println(e3)
}
typeを使ってstructの新しい型のexampleを定義しています。
これをmain()関数の中でフィールド名を指定して値を割り当てて、Printin()で出力しています。
e1.aでフィールド名にアクセスして値を20に書き換えることができます。
e2、e3はフィールド名を指定せずに値を割り当てています。
実行すると次のようになります。
{10 100 golang}
10 100 golang
20 100 golang
{100 0 }
{100 200 Programming}
変数そのままを出力すると{}で囲まれて表示されます。フィールド名にアクセスして値を表示、値の変更ができています。
e2、e3の出力を比較するとわかりますが、値を入れていないe2の方は、int型のところは0、string型のところは空が表示されています。
structとポインタ
上のコードを以下のように修正して、structとポインタを確認してみましょう。
package main
import "fmt"
type example struct {
a int
b int
c string
}
func main() {
e4 := example{}
fmt.Println(e4)
fmt.Printf("%T %v\n", e4, e4)
var e5 example
fmt.Println(e5)
fmt.Printf("%T %v\n", e5, e5)
e6 := new(example)
fmt.Println(e6)
fmt.Printf("%T %v\n", e6, e6)
e7 := &example{}
fmt.Println(e7)
fmt.Printf("%T %v\n", e7, e7)
}
e4、e5は値を割り当てずにexample型を初期化して値と型を出力しています。e4は省略宣言、e5はvarで型宣言しています。
e6、e7はそれぞれnew()、&を使って値を割り当てずにexample型を初期化しています。
これはこちらで扱った内容と同じです。
実行すると、次のように表示されます。
{0 0 }
main.example {0 0 }
{0 0 }
main.example {0 0 }
&{0 0 }
*main.example &{0 0 }
&{0 0 }
*main.example &{0 0 }
e4とe5が同じことを意味し、e6とe7が同じことを意味しているのがわかります。
e6、e7の出力が、&、*がついているのでアドレス、ポインタ型となっているのが理解できます。
こうい書き方と意味の違いを理解しておくといいでしょう。また、e6とe7は同じ意味の表現でも、e7は&enampe{}と&を付けているので、ポインタ型と見ただけでわかるという利点があります。
今度は、次のように関数の引数の利用での違いを見てみましょう。
上のコードを下記のように修正してみます。
package main
import "fmt"
type example struct {
a int
b int
c string
}
func changeEx(e example) {
e.a = 1000
}
func changeEx2(e *example) {
e.a = 1000
// (*e).a = 1000 が厳密な書き方ですが、同じ意味として処理されます。
}
func main() {
e8 := example{10, 100, "GOLANG"}
changeEx(e8)
fmt.Println(e8)
e9 := &example{10, 100, "GOLANG"}
changeEx2(e9)
fmt.Println(e9)
}
2つの似たような関数を定義しています。引数にstructのexample型を渡す関数で、フィールドのひとつの値を変更する処理になっています。
下側の関数の引数は、ポインタ型のstructを渡しているという違いになっています。
これを、main()関数の中で値を渡して処理をして、それぞれ出力しています。
それぞれ同じ値を渡していますが、e9の方は引数をポインタ型にしています。
出力するとこうなります。
{10 100 GOLANG}
&{1000 100 GOLANG}
上側は処理をしても、アドレスに格納された値が変更されているわけでは無いので、関数の処理で値に変化はありません。
下側は、アドレスとして出力され、値も関数の処理が反映されているのがわかります。
最後に
ここでは、Go言語のポインタについて、structとの関連を見てきました。
new()を使った方法と、&を使った方法で同じことを表現することができますが、&を使った方法の方が、コードの意味をポインタ型として理解しやすいという利点があります。