0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Go】ポインタ変数を reflect パッケージで動的に操作する

Last updated at Posted at 2020-04-13

Javaとかオブジェクト指向言語のリフレクションと同じ感覚で操作しようとすると結構ハマりますよね、golangのreflect。

特にポインタの場合、一味工夫が必要だったりするので、現場で使いそうな操作をまとめておきます。(これが更に "構造体の中のフィールド" だったりするとまた色々あるのですが、また後日にでも。)

package main

import (
	"fmt"
	"reflect"
	"strconv"
)

func main() {
	s := "Aerith"

	print("************* ポインタを reflect パッケージで色々いじくってみる ***********")

	ps := &s
	print(*ps)  // Aerith

	//ps のreflect.Value構造体を取得
	psValue := reflect.ValueOf(ps)
	print(psValue.String())  // <*string Value>

	//ps のreflect.Type構造体を取得
	psType := reflect.TypeOf(ps)
	print(psType.String())  // *string

	//reflect.Type構造体をreflect.Value経由で取得
	psTypeFromValue := psValue.Type()
	print(psTypeFromValue.String())  // *string

	//ポインタの型が *string かどうかを判定する
	var stringPointer *string
	typeOfStringPointer := reflect.TypeOf(stringPointer)
	print(strconv.FormatBool(psType == typeOfStringPointer))  // true

	//ps のreflect.Kind値を取得
	psKind := psType.Kind()
	print(psKind.String())  // ptr

	//psがポインタであるかどうかのbool値を取得
	psIsPointer := psKind == reflect.Ptr
	print(strconv.FormatBool(psIsPointer))  // true


	//ps のアドレス番号を取得
	//(※ アドレス番号自体は実行するたびに変わったりします)
	print(strconv.FormatUint(uint64(psValue.Pointer()), 16))  // c0000401f0

	//逆に、reflect.Value構造体から新たなポインタを作成する
	psCopy := psValue.Interface().(*string)
	print(*psCopy)  // Aerith

	//同じアドレス c0000401f0 を指し示す入れ物 psCopy が増えただけ
	psCopyValue := reflect.ValueOf(psCopy)
	print(strconv.FormatUint(uint64(psCopyValue.Pointer()), 16))  // c0000401f0

	//新しい入れ物 psCopy に違うアドレスを代入しても、コピー元の入れ物 ps には影響ない
	s2 := "Barret"
	psCopy = &s2
	psCopyValue = reflect.ValueOf(psCopy)
	print(*psCopy)  // Barret
	print(strconv.FormatUint(uint64(psCopyValue.Pointer()), 16))  // c0000402a0

	print(*ps)  // Aerith
	print(strconv.FormatUint(uint64(psValue.Pointer()), 16))  // c0000401f0


	print("********* ここからは、ポインタアドレスが指し示す先の値をダイレクトにいじくってみる *********")

	//ポインタ ps が指し示す先の値のreflect.Value構造体を取得
	psElemValue := psValue.Elem()
	print(psElemValue.String())  // Aerith

	//ポインタ ps が指し示す先の値のreflect.Type構造体を取得
	psElemType := psElemValue.Type()
	print(psElemType.String())  // string

	//ポインタ ps が指し示す先の値のreflect.Kind値を取得
	psElemKind := psElemType.Kind()
	print(psElemKind.String())  // string

	//ポインタ ps が指し示す先の値を書き換える
	psElemValue.SetString("Cloud")
	print(psElemValue.String())  // Cloud

	// *ps がちゃんと書き換わっている
	print(*ps)  // Cloud

	//大本の変数 s もちゃんと書き換わっている
	print(s)  // Cloud

	//逆に、reflect.Value構造体から新たな変数を作成する
	sCopyInterface := psElemValue.Interface()
	sCopy := sCopyInterface.(string)
	print(sCopy)  // Cloud

	//ポインタが指し示す先の値は同じでも、メモリ上のデータとしては別物になっている
	sCopyPointer := &sCopy
	sCopyValue := reflect.ValueOf(sCopyPointer)
	print(strconv.FormatUint(uint64(sCopyValue.Pointer()), 16))  // c0000402c0

	//ポインタ sCopyPointer が指し示す先の値を書き換える
	sCopyValue.Elem().SetString("Tifa")
	print(sCopyValue.Elem().String())  // Tifa

	//大本の変数 sCopy もちゃんと書き換わっている
	print(sCopy)  // Tifa

	//コピー元の変数 s は変わっていない
	print(s) // Cloud


	print("****************** ポインタがnilの場合 ******************")

	//nilポインタが指し示す先の値を reflect で書き換えることはできない
	sCopyPointer = nil
	sCopyValue = reflect.ValueOf(sCopyPointer)
	print(sCopyValue.String())  // <*string Value>
	print(strconv.FormatBool(sCopyValue.CanSet()))  // false

	sCopyKind := sCopyValue.Kind()
	print(sCopyKind.String())  // ptr

	//ポインタが nil かどうかを判定する
	print(strconv.FormatBool(sCopyValue.IsNil()))  // true

	//ポインタが指し示す先の値は <invalid Value>
	sCopyElemValue := sCopyValue.Elem()
	print(sCopyElemValue.String())  // <invalid Value>

	sCopyElemKind := sCopyElemValue.Kind()
	print(sCopyElemKind.String())  // invalid

	//reflect.Type が取得不可能
	sCopyElemValue.Type()  // panic発生
	/*
		panic: reflect: call of reflect.Value.Type on zero Value

		goroutine 1 [running]:
		reflect.Value.Type(0x0, 0x0, 0x0, 0x1, 0x1)
			/go/src/reflect/value.go:1877 +0x16d
		reflect_pointer.main()
			/golang/workspace/awesomeProject/main/reflect_pointer.go:110 +0x1ce2
	*/
}

func print(s interface{}) {
	fmt.Println(s)
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?