LoginSignup
44
33

More than 5 years have passed since last update.

Go言語の構造体の値渡しとポインタ渡しの動作を確認してみる

Last updated at Posted at 2017-06-27

こちらの記事

Go言語で、できるだけお手軽かつ安全にシリアライズ⇒デシリアライズしてみる
http://qiita.com/y_ussie/items/58519d3e1b1427c959eb

を書いた時に、今ひとつちゃんと理解できていないと感じたので、動作を見てみることにしました。

準備

以下の構造体とインターフェースを定義します。

// ST1 は文字列とmap[string]stringを持つ構造体です。
type ST1 struct {
    f1 string
    f2 map[string]string
}

func (s ST1) a() {

}

// IF1 は関数a()を持つinterfaceです。
type IF1 interface {
    a()
}

ST1 は文字列とmap[string]stringの2つのフィールドを持つ構造体です。
ST1 は IF1 interface を実装しています。

試してみる

1. 値渡しで、構造体のフィールドの値を変更した場合

    fmt.Println("*** 1.値渡しでフィールドの値を変更した場合")
    s1 := ST1{"", map[string]string{}}
    fmt.Printf("1: s1 = %#v\n", s1)
    F1(s1)
    fmt.Printf("2: s1 = %#v\n", s1)
// F1 は構造体の値渡しでフィールドの値を変更します。
func F1(s ST1) {
    s.f1 = "f1"
    s.f2["f2"] = "test"
}

結果

値渡しなので文字列のフィールドは変更されませんが、mapは参照型なので変更されています。

*** 1.値渡しでフィールドの値を変更した場合
1: s1 = main.ST1{f1:"", f2:map[string]string{}}
2: s1 = main.ST1{f1:"", f2:map[string]string{"f2":"test"}}

2. ポインタ渡しで、構造体のフィールドの値を変更した場合

    fmt.Println("*** 2.ポインタ渡しでフィールドの値を変更した場合")
    s1 = ST1{"", map[string]string{}}
    fmt.Printf("1: s1 = %#v\n", s1)
    F2(&s1)
    fmt.Printf("2: s1 = %#v\n", s1)
// F2 は構造体のポインタ渡しでフィールドの値を変更します。
func F2(s *ST1) {
    s.f1 = "f1"
    s.f2["f2"] = "test"
}

結果

ポインタ渡しなので文字列、mapともに変更されています。

*** 2.ポインタ渡しでフィールドの値を変更した場合
1: s1 = main.ST1{f1:"", f2:map[string]string{}}
2: s1 = main.ST1{f1:"f1", f2:map[string]string{"f2":"test"}}

3. interface の値渡しで、構造体のフィールドの値を変更した場合

    fmt.Println("*** 3.interfaceの値渡しでフィールドの値を変更した場合")
    s1 = ST1{"", map[string]string{}}
    fmt.Printf("1: s1 = %#v\n", s1)
    F3(s1)
    fmt.Printf("2: s1 = %#v\n", s1)
// F3 はinterfaceの値渡しでフィールドの値を変更します。
func F3(i IF1) {
    if s, ok := i.(ST1); ok {
        s.f1 = "f1"
        s.f2["f2"] = "test"
    } else {
        fmt.Println("error: パラメータが ST1 ではありません")
    }
}

結果

構造体を値渡しした時と同様、文字列は変更されず、mapは変更されています。

*** 3.interfaceの値渡しでフィールドの値を変更した場合
1: s1 = main.ST1{f1:"", f2:map[string]string{}}
2: s1 = main.ST1{f1:"", f2:map[string]string{"f2":"test"}}

4. interface のポインタ渡しで、構造体のフィールドの値を変更した場合

    fmt.Println("*** 4.interfaceのポインタ渡しでフィールドの値を変更した場合")
    s1 = ST1{"", map[string]string{}}
    fmt.Printf("1: s1 = %#v\n", s1)
    F4(&s1)
    fmt.Printf("2: s1 = %#v\n", s1)
// F4 はinterfaceのポインタ渡しでフィールドの値を変更します。
func F4(i IF1) {
    if s, ok := i.(*ST1); ok {
        s.f1 = "f1"
        s.f2["f2"] = "test"
    } else {
        fmt.Println("error: パラメータが *ST1 ではありません")
    }
}

結果

構造体をポインタ渡しした時と同様、文字列、mapともに変更されています。

*** 4.interfaceのポインタ渡しでフィールドの値を変更した場合
1: s1 = main.ST1{f1:"", f2:map[string]string{}}
2: s1 = main.ST1{f1:"f1", f2:map[string]string{"f2":"test"}}

上記の結果からの注意点

ということで、構造体を直接渡した場合も interface で抽象化した場合も、

  • 値渡しの場合には参照型以外のフィールドを渡された側で変更しても変更されない
  • ポインタ渡しの場合には参照型でないフィールド含めて変更される

ということなのですが、一つ引っかかる点が。

interface を関数のパラメータにする場合、以下のように書いただけで値渡しでもポインタ渡しでも受け取ることができてしまいます。

func F3(i IF1) {
...
}

なぜここの記述方法を(値渡しとポインタ渡しで)分けてないんだとちょっと不思議に思いましたが、よくよく考えると interface で抽象化したいのは関数呼び出しなので、そこは interface の参照先が値かポインタかなどを気にすることなく呼び出せるようにしておいてくれているという事でしょうか。。

なので、どうしても interface を受け取る関数側にて値渡しとポインタ渡しで処理を変えたい場合には
こちらの記事

Go で interface {} の中身がポインタならその参照先を取得する
http://qiita.com/chimatter/items/b0879401d6666589ab71

にもありますように
以下のように実行時に reflect でチェックするしかなさそうですね。。
(あまりそんなことを気にするようなコードは書かないほうがよさそうですが)

// F5 はinterfaceに渡されたパラメータが値渡しかポインタ渡しか確認します。
func F5(i IF1) {
    if reflect.TypeOf(i).Kind() == reflect.Ptr {
        fmt.Println("i はポインタ渡しです")
    } else {
        fmt.Println("i は値渡しです")
    }
}

今回使用したコード

今回使用したコードの全文は以下のGistに置いてあります。
https://gist.github.com/yoshinoyaussie/85148d27abdd77feb08ffbd56f61007a

44
33
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
44
33