GO 言語インタビュー エッセンス - 値レシーバーとポインター レシーバーの違いは何ですか?

方法

メソッドは、ユーザー定義型に新しい動作を追加できます。関数との違いは、メソッドにはレシーバーがあり、関数にレシーバーを追加するとメソッドになります。受信者は值接收者または です指针接收者

メソッドを呼び出す場合、値の型は值接收者呼び出されるメソッドまたは指针接收者呼び出されるメソッドのいずれかになり、ポインタの型は指针接收者呼び出されるメソッドまたは值接收者呼び出されるメソッドになります。

つまり、メソッドのレシーバーがどのような型であっても、レシーバーの型に厳密に準拠せずに、この型の値とポインターを呼び出すことができます。

例を見てみましょう:

package main

import "fmt"

type Person struct {
	age int
}

func (p Person) howOld() int {
	return p.age
}

func (p *Person) growUp() {
	p.age += 1
}

func main() {
	// qcrao 是值类型
	qcrao := Person{age: 18}

	// 值类型 调用接收者也是值类型的方法
	fmt.Println(qcrao.howOld())

	// 值类型 调用接收者是指针类型的方法
	qcrao.growUp()
	fmt.Println(qcrao.howOld())

	// ----------------------

	// stefno 是指针类型
	stefno := &Person{age: 100}

	// 指针类型 调用接收者是值类型的方法
	fmt.Println(stefno.howOld())

	// 指针类型 调用接收者也是指针类型的方法
	stefno.growUp()
	fmt.Println(stefno.howOld())
}

上記の例の出力は次のとおりです。

18
19
100
101

関数を呼び出した後は、growUp呼び出し元が値型であるかポインタ型であるかに関係なく、そのAge値は変化します。

実際、型とメソッドの受信側の型が異なる場合、コンパイラーは実際に舞台裏で何らかの作業を実行し、それを表に表示します。

- 値の受信者 ポインタ受信機
値型の呼び出し元 このメソッドは、「値による受け渡し」と同様に、呼び出し元のコピーを使用します。 値への参照を使用してメソッドを呼び出します。上記の例では、qcrao.growUp()実際には(&qcrao).growUp()
ポインタ型の呼び出し元 ポインタは値に逆参照されます。上記の例では、stefno.howOld()実際には値が(*stefno).howOld() 実際、これは「値による受け渡し」でもあります。メソッド内の操作は呼び出し元に影響します。これは、ポインターによるパラメーターの受け渡しに似ています。ポインターのコピーがコピーされます。

値レシーバーとポインターレシーバー

前述したように、受信側の型が値型であるかポインタ型であるかに関係なく、値型またはポインタ型を通じて呼び出すことができ、これは実際には糖衣構文を通じて機能します。

最初に結論について話しましょう: レシーバーが値型であるメソッドの実装は、レシーバーがポインタ型であるメソッドを自動的に実装することと同等であり、レシーバーがポインタ型であるメソッドを実装しても、対応するメソッドは自動的に生成されません。受信側が値型であること。

例を見てみると、完全に理解できるでしょう。

package main

import "fmt"

type coder interface {
	code()
	debug()
}

type Gopher struct {
	language string
}

func (p Gopher) code() {
	fmt.Printf("I am coding %s language\n", p.language)
}

func (p *Gopher) debug() {
	fmt.Printf("I am debuging %s language\n", p.language)
}

func main() {
	var c coder = &Gopher{"Go"}
	c.code()
	c.debug()
}

上記のコードは、次の 2 つの関数を定義するインターフェイスを定義しますcoder

code()
debug()

Gopher次に、値レシーバとポインタ レシーバの 2 つのメソッドを実装する構造体が定義されます。

最後に、main関数内のインターフェイス型変数を通じて 2 つの定義された関数を呼び出しました。

実行すると、結果は次のようになります。

I am coding Go language
I am debuging Go language

しかし、main関数の最初のステートメントを変更すると、次のようになります。

func main() {
	var c coder = Gopher{"Go"}
	c.code()
	c.debug()
}

実行するとエラーが発生します。

src/main.go:23:6: cannot use Gopher literal (type Gopher) as type coder in assignment:
	Gopher does not implement coder (debug method has pointer receiver)

これら 2 つのコードの違いがわかりますか? 1 回目は&Gopherに割り当てられcoder、2 回目Gopherは に割り当てられましたcoder

2 番目のエラーは、Gopherそれが実装されていないことですcoderGopher型はメソッドを実装していないため、これは明らかです。debug表面的には、*Gopher型はcodeメソッドを実装していませんが、Gopher型がcodeメソッドを実装しているため、*Gopher型は自動的にcodeメソッドを持ちます。

もちろん、上記のステートメントには簡単な説明があります: レシーバーはポインター型のメソッドであり、レシーバーのプロパティがメソッド内で変更される可能性があり、それによってレシーバーに影響が及ぶ可能性があり、レシーバーがポインター型であるメソッドの場合value type、in このメソッドは受信側自体には影響しません。

したがって、レシーバーが値型であるメソッドを実装すると、レシーバーが対応するポインター型であるメソッドを自動的に生成できます。これは、どちらもレシーバーに影響を与えないためです。ただし、レシーバがポインタ型のメソッドを実装した場合、このときにレシーバが値型のメソッドが自動生成されてしまうと、レシーバを変更する(ポインタで実装する)という本来の期待が実現できなくなります。値の型は、呼び出し元に実際には影響を与えないコピーを生成します。

最後に、これだけは覚えておいてください。

レシーバーが値型であるメソッドを実装すると、レシーバーがポインタ型であるメソッドも暗黙的に実装されます。

いつ使用されますか?

メソッドの受信者が値型の場合、呼び出し元がオブジェクトであるかオブジェクト ポインターであるかに関係なく、変更されるのはオブジェクトのコピーであり、呼び出し元には影響しません。メソッドの受信者がポインター型の場合は、変更されます。 、呼び出し元は、ポインタが指すオブジェクト自体を変更します。

ポインターをメソッドのレシーバーとして使用する理由は次のとおりです。

  • メソッドは、受信側が指す値を変更できます。
  • メソッドが呼び出されるたびに値をコピーすることは避けてください。値の型が大きな構造体の場合、その方が効率的です。

値レシーバーを使用するかポインター レシーバーを使用するかは、メソッドが呼び出し元 (つまり、レシーバー) を変更するかどうかによって決まりませんが、 type に基づく必要があります本质

型に「プリミティブな性質」がある場合、つまり、そのメンバーがすべて Go 言語に組み込まれたプリミティブ型 (文字列、整数値など) である場合は、値レシーバー型のメソッドを定義します。これらの型は、スライス、マップ、インターフェイス、チャネルなどの組み込み参照型と同様に非常に特殊で、宣言すると実際に作成することになり、値の受け取り側の型を直接定義する方法でもありますheaderこのように、関数を呼び出すと、これらの型は直接コピーされheaderheader型自体はコピー用に設計されています。

型が非プリミティブな性質を持ち、安全にコピーできない場合は、型を常に共有し、ポインター レシーバーでメソッドを定義する必要があります。たとえば、go ソース コード内のファイル構造 (struct File) はコピーされるべきではなく、コピーは 1 つだけである必要があります实体

おすすめ

転載: blog.csdn.net/zy_dreamer/article/details/132795614