ホームページ  >  記事  >  バックエンド開発  >  Go ポインターをいつ使用するか知っていますか?

Go ポインターをいつ使用するか知っていますか?

藏色散人
藏色散人転載
2021-09-24 15:08:563009ブラウズ

この記事は、go language チュートリアルコラムで、Go ポインター (Go Pointer) がどのような状況で使用されるのかを紹介するものです。 !

Go ポインターをいつ使用するか知っていますか?

GOコードでのポインターの使用は、特に使用法のシナリオを区別するのが困難な場合、初心者にはそれほど友好的ではありません。

ポインターを使用する際の最大の誤解の 1 つは、Go のポインターが C 言語のポインターに非常に似ているということだと思います。しかし、そうではありません。 Go では、ポインタは C/C と同じように機能しません。

この記事では、Go ポインターを正しく使用する方法について説明します。

間違った結論: ポインターを使用する方が良いでしょうか?

一般に、ポインターを使用すると常に値をコピーする必要がなくなるため、アプリケーションの実行が高速になると考えられています。 Go でも同じ考えがあるのは不思議ではありません。

ただし、Go での ポインターの受け渡しは、通常、値の受け渡し よりも遅くなります。これは、Go がガベージ コレクション言語であることの結果です。関数へのポインタを渡すとき、Go はエスケープ解析を実行して、変数をヒープに格納するかスタックに格納するかを決定する必要があります。これによりすでに余分なオーバーヘッドが追加されていますが、それ以外の場合は変数をヒープに保存できます。変数をヒープに保存すると、GC の実行中に時間もロスします。

Go の便利な機能は、コマンド go build -gcflags="-m" を実行することで、エスケープ解析がどのように行われたかを確認できることです。これを行うと、Go は変数がヒープにエスケープするかどうかを教えてくれます:

./main.go:44:20: greet ... argument does not escape
./main.go:44:21: greeting escapes to heap
./main.go:44:21: name escapes to heap

変数がヒープにエスケープしない場合、変数はスタック上に残ります。スタックは変数をクリアするためにガベージ コレクターを必要とせず、push/pop 操作のみを実行します。

コンテンツが値によって渡される場合、そのコンテンツは常にスタック上で処理されるため、ガベージ コレクションのオーバーヘッドは発生しません。 (GC はデフォルトで実行されます。ヒープ内のコンテンツが少ないということは、GC が行うべき作業が少ないことを意味します)。

ポインターを使用するとパフォーマンスが低下することがわかりました。必要な場合はポインターを使用しますか?

大規模なデータ構造のコピー

ポインタのパフォーマンスは常に値の転送より悪いのでしょうか?明らかにそうではありません。大規模なデータ構造を扱う場合、ポインタが役に立ちます。これにより、ガベージ コレクションのコストが大量のデータのコピーのコストで相殺される可能性があります。

これについて話すと、「そのビッグデータはどのくらいの大きさにすべきですか?」といつも尋ねられます。

ここに固定値はないと思いますが、パフォーマンスに関連するものはすべてベンチマークする必要があります。 Go には強力なベンチマーク ツールが組み込まれており、これを最大限に活用できます。

変数

関数のパラメーターを変更する唯一の方法は、ポインターを渡すことです。デフォルトでは、値の変更はコピーに対して実行されます。したがって、これらの変更は、それを呼び出す関数に反映できません。

次のコードを見てください:

type person struct {
 name string
}func main() {
 p := person{"Richard"}
 rename(p)
 fmt.Println(p)
}func rename(p person) {
 p.name = "test"
}
person への変更はそのコピーに対して実行されるため、出力は

Richard になります。基になる人物オブジェクトの値を変更する場合は、ポインターを使用する必要があります。

func main() {
 p := person{"Richard"}
 rename(&p)
 fmt.Println(p)
}func rename(p *person) {
 p.name = "test"
}
上記と同様に、

testを出力します。可変性とは、Go でポインターが使用される状況です。これが良いことなのかどうかは議論の余地がある。

API の一貫性

ポインターを使用して最新の値を維持します。これにより、すべてのメソッドがその値を変更しない場合でも、API の一貫性が保たれます。

したがって、これ:

func (p *person) rename(s string) {
   p.name = s 
}func (p *person) printName() {
  fmt.Println(p.name)
}

func (p *person) rename(s string) {
   p.name = s 
}func (p person) printName() {
  fmt.Println(p.name)
}
よりも優れていますが、一貫性のために

printName でポインターを使用する必要はありません。ただし、これにより API が簡素化され、参照が必要な場所を正確に覚えておく必要がなくなります。

欠落していることを示します

一般的な値を使用する場合、デフォルト値はゼロになります。ただし、何かが欠落しているか、値が入力されていないことを知る必要があるシナリオもあります。たとえば、構造体には学生のテストのスコアが含まれています。構造体が空でスコアが 0 の場合、学生はテストの成績が良くなかった、またはテストをまったく受けなかったことを意味しますか?

ポインターのデフォルトのゼロ値は

nil ポインターであり、値が設定されていないことを意味します。この要件は、次のように実装することもできます。

type exam struct {
    score   int
    present bool
}
別の

present フィールドを使用して、学生が試験を受けなかったことを示します。

価値を選択する理由は何ですか?

#これはやや主観的なものです。プログラミングに対する理解は人によって異なるため、同じ概念を持つ必要はありません。

Go では可能な限りデフォルト値を使用することが合理的であると考えています。これはすべての状況で機能するわけではありませんが、私の場合は大きな事故を防ぐことができました。ポインターの代わりに値を使用すると、NULL ポインターによるトニー・ホアの「100 万ドルの間違い」は発生しません。

デフォルト値のゼロは、多数の宣言を避けるのに役立ちます。

另一个好处是易变性造成的问题比它解决的问题多的得多。易变性给函数带来的副作用同时使得调试变得更加困难。 通过让函数返回修改之后的结构体,可以避免这种突变。

重写之前的例子

func main() {
 p := person{"richard"}
 p = rename(p)
 fmt.Println(p)
}func rename(p person) person {
 p.name = "test"
 return p
}

这也是 append 如何工作的,所以并不陌生。

x := []int{1,2}
x = append(x, 3)
x = append(x, 4)

鉴于指针的安全性,和值处理比指针处理更快,使用指针需要反复斟酌。

原文地址:https://medium.com/@meeusdylan/when-to-use-pointers-in-go-44c15fe04eac

译文地址:https://learnku.com/go/t/60923

以上がGo ポインターをいつ使用するか知っていますか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はlearnku.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。