4

私はEffective Goを読んでいて、ポインターと値のセクションで、最後の近くに次のように書かれています:

レシーバーのポインターと値に関するルールは、値メソッドはポインターと値で呼び出すことができますが、ポインター メソッドはポインターでのみ呼び出すことができるということです。これは、ポインター メソッドがレシーバーを変更できるためです。値のコピーでそれらを呼び出すと、それらの変更が破棄されます。

それをテストするために、私はこれを書きました:

package main

import (
  "fmt"
  "reflect"
)

type age int

func (a age) String() string {
  return fmt.Sprintf("%d yeasr(s) old", int(a))
}

func (a *age) Set(newAge int) {
  if newAge >= 0 {
    *a = age(newAge)
  }
}

func main() {
  var vAge age = 5
  pAge := new(age)

  fmt.Printf("TypeOf =>\n\tvAge: %v\n\tpAge: %v\n", reflect.TypeOf(vAge),
    reflect.TypeOf(pAge))

  fmt.Printf("vAge.String(): %v\n", vAge.String())
  fmt.Printf("vAge.Set(10)\n")
  vAge.Set(10)
  fmt.Printf("vAge.String(): %v\n", vAge.String())

  fmt.Printf("pAge.String(): %v\n", pAge.String())
  fmt.Printf("pAge.Set(10)\n")
  pAge.Set(10)
  fmt.Printf("pAge.String(): %v\n", pAge.String())
}

そして、ポインターメソッドSet()は値 var を介して呼び出し可能であってはならないため、ドキュメントにはそうすべきではないと書かれていますが、コンパイルされますvAge。ここで何か間違ったことをしていますか?

4

2 に答える 2

9

vAgeアドレス可能であるため、それは有効です。言語仕様のCallsの最後の段落を参照してください。

メソッド呼び出し xm() は、x (の型) のメソッド セットに m が含まれ、引数リストを m のパラメーター リストに割り当てることができる場合に有効です。x がアドレス指定可能で、&x のメソッド セットに m が含まれている場合、xm() は (&x).m() の短縮形です。

于 2012-11-09T06:49:49.553 に答える
3

vAgetype の値を格納するメモリ内の既知の場所であるため、単なる「値変数」とは見なされませんagevAge値としてのみ見ると、それ自体では式として有効でvAge.Set(10)はありませんが、 はアドレス指定可能であるため、仕様では、コンパイル時vAgeに「vAge のアドレスを取得し、その上で Set を呼び出す」の省略形として式を扱ってもよいと宣言しています。 -time 、それが または のいずれかSetに設定されたメソッドの一部であることを確認できるとき。基本的に、コンパイラが必要かつ可能であると判断した場合、コンパイラが元の式に対してテキスト展開を行うことを許可しています。age*age

一方、コンパイラでは、呼び出しは許可されますage(23).String()が、age(23).Set(10). この場合、 type のアドレス不可能な値を使用していますage。と言うのは妥当&age(23)ではないので、言うのは妥当ではありません(&age(23)).Set(10)。コンパイラはその展開を行いません。

有効な Go の例を見ると、の完全な型b.Write()がわかっているスコープで直接呼び出しているわけではありません。b代わりに、の一時的なコピーを作成し、bそれを type の値として渡そうとしていますinterface io.Writer()。問題は、 の実装は、Printfを受け取る方法を知っていると約束していることを除いて、渡されるオブジェクトについて何も知らないため、関数を呼び出す前にを取得して に変換することをWrite()知らないことです。アドレス指定するかどうかの決定はコンパイル時に行う必要があり、最初の引数が参照されずに受け取る方法を知っているという前提条件でコンパイルされました。byteSlice*ByteSlicebPrintFWrite()

ageシステムがポインターを取得して値に変換する方法を知っている場合age、逆のことができるはずだと思うかもしれません。ただし、できることはあまり意味がありません。有効な Go の例では、bの代わりに渡すと&b、 PrintF が返された後に存在しなくなるスライスを変更することになり、ほとんど役に立ちません。上記の例では、値を取得して値で上書きすることageは文字通り意味がありません。最初のケースでは、コンパイラーが停止して、引き継ぎ時にプログラマーが本当に何をするつもりだったのかをプログラマーに尋ねることは理にかなっています。後者の場合、もちろん、コンパイラが定数値の変更を拒否することは理にかなっています。2310b

ageさらに、システムがのメソッド セットを*age;に動的に拡張しているとは思わない。私の勝手な推測では、ポインター型には基本型の各メソッドのメソッドが静的に与えられ、ポインターを逆参照して基本型のメソッドを呼び出すだけです。いずれにしても、値による受信メソッドではポインターを変更できないため、これを自動的に行うことは安全です。逆に言えば、データの変更を要求する一連のメソッドを、変更したデータがその後すぐに消えるようにラップして拡張することは、常に意味があるとは限りません。これを行うことが理にかなっている場合は確かにありますが、これはプログラマーが明示的に決定する必要があり、コンパイラーが停止してそのように要求することは理にかなっています。

tl;drEffective Go のパラグラフは少し言い直しが必要だと思います (ただし、私は仕事を引き受けるには長すぎるかもしれませんが) が、それは正しいです。型のポインターは*X実質的にすべてのXのメソッドにアクセスできますが、'X' は のメソッドにアクセスできません*X。したがって、オブジェクトが特定のインターフェースを満たすことができるかどうかを判断する場合、*Xは任意のインターフェースを満たすことXができますが、その逆は当てはまりません。さらに、Xスコープ内の型の変数がコンパイル時にアドレス指定可能であることがわかっている場合でも (つまり、コンパイラはそれを に変換でき*Xます)、インターフェースを満たす目的でこれを行うことは意味がないため、拒否します。 .

于 2012-11-13T00:13:57.073 に答える