Golangのポインタの基本では参照渡しと値渡しの違いの観点からポインタについて書いたが、それ以前にポインタを扱っているうちに混乱してきたため、ポインタの概要をまとめる。
var s *string // 変数sはstring型のポインタ。型は*string
package main
import "fmt"
func main() {
/**
* ポインタ型の定義
*/
var pointer *string // *string型のポインタ変数
var s string
s = "Hello World"
/*
* アドレス演算子でポインタを扱う
*/
fmt.Printf("%T\n", pointer) // *string
fmt.Printf("%v\n", pointer) // <nil> ※初期化されていないポインタ型の値はnil
fmt.Printf("%T\n", &pointer) // **string ※pointerは*string型のポインタ変数なので、&pointerは*stringのポインタを生成する
fmt.Printf("%v\n", &pointer) // 0xc00000c028 ※&を使ってポインタのアドレスを得る
fmt.Printf("%T\n", s) // string
fmt.Printf("%v\n", s) // Hello World
pointer = &s // ※*string型のポインタ変数(値は初期化前なのでnil)にsのポインタ型を生成し、アドレスを代入。(変数pointerは*string型のポインタ変数として定義済みなので、&を使って変数sのポインタ型を生成し、代入。)
fmt.Printf("%T\n", pointer) // *string ※pointerは*string型のポインタのまま
fmt.Printf("%v\n", pointer) // 0xc00000e1e0
fmt.Printf("%v\n", *pointer) // Hello World ※pointer(*string型ポインタ変数)から値を参照する
fmt.Printf("%T\n", s) // string
fmt.Printf("%v\n", s) // Hello World
fmt.Printf("%T\n", &s) // *string
fmt.Printf("%v\n", &s) // 0xc00000e1e0
fmt.Printf("%v\n", *s) // invalid indirect of s (type string) ※sはポインタ型変数ではないためエラー。indirectは間接の意
*pointer = "New World" // ※*pointerは変数なので代入できる。ポインタの変数定義は*を使う。
fmt.Printf("%T\n", *pointer) // string ※*pointerはポインタ変数から値を参照している
fmt.Printf("%v\n", *pointer) // New World
}
package main
import "fmt"
type Human struct {
Name string
Age int
}
func main() {
var h Human
fmt.Printf("%v\n", h.Name) // "" ※stringのゼロ値
fmt.Printf("%v\n", h.Age) // 0 ※intのゼロ値
h = Human{
Name: "Tom",
Age: 20,
}
fmt.Printf("%v\n", h) // {Tom 20}
}
package main
import "fmt"
type Human struct {
Name string
Age int
}
func main() {
var h *Human // ポインタ型変数の定義
h = &Human{ // ※変数hは*Human型のポインタ型なので、&を使って構造体Humanから*Human型のポインタ型を使って代入。
Name: "John",
Age: 20,
}
fmt.Printf("%T\n", h) // *main.Human ※&HumanでHumanのポインタ型を生成している
fmt.Printf("%v\n", h) // &{John 20} ※*main.Human型のポインタ
fmt.Printf("%v\n", *h) // {John 20} ※値の取り出し
h.Name = "Tom"
h.Age = 40
fmt.Printf("%T\n", h) // *main.Human
fmt.Printf("%v\n", h) // &{Tom 40}
fmt.Printf("%v\n", *h) // {John 40} ※値の取り出し
}
package main
import "fmt"
type Human struct {
Name string
Age int
}
func main() {
h := new(Human)
h.Name = "John"
h.Age = 20
fmt.Printf("%T\n", h) // *main.Human
fmt.Printf("%v\n", h) // &{John 20}
h.Name = "Tom"
h.Age = 40
fmt.Printf("%T\n", h) // *main.Human
fmt.Printf("%v\n", h) // &{Tom 40}
fmt.Printf("%v\n", *h) // {Tom 40} ※値を取り出した
}
package main
import "fmt"
type Human struct {
Name string
Age int
}
func (h Human) say(msg string) {
fmt.Printf("%v(%v) said %v\n", h.Name, h.Age, msg)
}
func main() {
var h Human // Human型の変数hを定義
fmt.Printf("%T\n", h) // main.Human
fmt.Printf("%v\n", h) // { 0} ※NameとAgeのゼロ値
h = Human{
Name: "Taro",
Age: 20,
}
fmt.Printf("%T\n", h) // main.Human
fmt.Printf("%v\n", h) // {Taro 20}
h.say("Hello") // Taro(20) said Hello
}
package main
import "fmt"
type Human struct {
Name string
Age int
}
// ポインタレシーバ
func (h *Human) say(msg string) {
fmt.Printf("%v(%v) said %v\n", h.Name, h.Age, msg)
}
func main() {
var h *Human // ※ポインタ型の定義
fmt.Printf("%T\n", h) // *main.Human
fmt.Printf("%v\n", h) // <nil> ※ポインタ型のゼロ値
h = &Human{
Name: "Taro",
Age: 20,
}
fmt.Printf("%T\n", h) // *main.Human ※&Human構造体のポインタを生成し、hに代入済み
fmt.Printf("%v\n", h) // &{Taro 20}
h.say("Hello") // Taro(20) said Hello
}
package main
import "fmt"
type Human struct {
Name string
Age int
}
func (h Human) setDataForValue(name string, age int) {
h.Name = name
h.Age = age
}
func (h *Human) setDataForPointer(name string, age int) {
h.Name = name
h.Age = age
}
func main() {
// 値関数の呼び出し
var hForValue Human
hForValue = Human{
Name: "Taro",
Age: 20,
}
hForValue.setDataForValue("Jiro", 40)
fmt.Printf("Name: %v\n Age: %v\n", hForValue.Name, hForValue.Age) // Name: Taro Age: 20
// ポインタ関数の呼び出し
var hForPointer *Human
hForPointer = &Human{
Name: "Taro",
Age: 20,
}
hForPointer.setDataForPointer("Jiro", 40)
fmt.Printf("Name: %v\n Age: %v\n", hForPointer.Name, hForPointer.Age) // Name: Jiro Age: 40
}
関数内で構造体のフィールドの値を変更したいときはポインターレシーバを使う。
mapやchanのような参照型をレシーバーに扱う場合は値レシーバでも良い。
ただし、厳密にパフォーマンスを考慮したりする場合には使い分けはこの限りではない。
前半に記述したポインタ型の定義、演算子の役割について思い出す。
関連書籍