Maison >développement back-end >Golang >Savez-vous quand utiliser les pointeurs Go ?

Savez-vous quand utiliser les pointeurs Go ?

藏色散人
藏色散人avant
2021-09-24 15:08:563091parcourir

Cet article est fourni par la rubrique go languagetutoriel pour vous présenter dans quelles circonstances utiliser Go pointer (Go Pointer).

Je pense que l'un des plus gros malentendus lors de l'utilisation des pointeurs est que les pointeurs en Go sont très similaires aux pointeurs en C. Cependant, ce n’est pas le cas. Les pointeurs ne fonctionnent pas en Go de la même manière qu’en C/C++. Savez-vous quand utiliser les pointeurs Go ?

Cet article explique comment utiliser correctement les pointeurs Go.

Mauvaise conclusion : utiliser des pointeurs est-il préférable ?

On pense généralement que les applications s'exécuteront plus rapidement lors de l'utilisation de pointeurs, car cela évitera de copier les valeurs à tout moment. Il n’est pas surprenant qu’au Go nous ayons la même idée.

Cependant, le passage du pointeur

Go est généralement plus lent que le passage de la valeur

. C'est une conséquence du fait que Go est un langage ramassé. Lorsque vous transmettez un pointeur vers une fonction, Go doit effectuer une analyse d'échappement pour déterminer si la variable doit être stockée sur le tas ou sur la pile. Cela ajoute déjà une surcharge supplémentaire, mais sinon les variables peuvent être stockées dans le tas. Lorsque vous stockez une variable dans le tas, vous perdez également du temps pendant l'exécution du GC.

Une fonctionnalité pratique de Go est que vous pouvez vérifier ce que l'analyse d'échappement a fait en exécutant la commande go build -gcflags="-m". Si vous faites cela, Go vous dira si une variable s'échappe vers le tas :

./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
Si une variable ne s'échappe pas vers le tas, elle est sur la pile. La pile n'a pas besoin d'un ramasse-miettes pour effacer les variables, elle effectue uniquement des opérations push/pop. Si un contenu est transmis par valeur, il sera toujours traité sur la pile, ce qui n'entraînera pas de surcharge de garbage collection. (Le GC s'exécutera par défaut. Moins de contenu dans le tas signifie que le GC a moins à faire).

go build -gcflags="-m" 来检查逃逸分析做了什么。如果你这样做,Go 将告诉你一个变量是否逃到堆上:

type person struct {
 name string
}func main() {
 p := person{"Richard"}
 rename(p)
 fmt.Println(p)
}func rename(p person) {
 p.name = "test"
}

如果一个变量没有逃逸到堆中,它就在栈中。栈是不需要垃圾回收器来清除变量的,它只做 push/pop 操作。

如果任何内容都进行值传递,那么将一直在栈中做相关处理,这不会带来垃圾回收方面的开销。(GC 将按默认设置运行。堆中内容越少使得 GC 需要做的事情也越少)。

现在你知道了吧,使用指针反而会降低性能,那么什么时候需要使用指针呢?

拷贝大的数据结构

指针是否一直表现的比值传递差呢?显然不是这样的。对大的数据结构进行处理时,指针将发挥作用。这样可能会使得垃圾回收的开销被拷贝大量数据的开销抵消掉。

当我提到这点时,总是被问到‘那个大数据应该多大’?

我觉得这里没有一个固定的数值,凡是与性能相关的,都应该对其进行基准测试。 Go 有内置的强大的基准测试工具,完全可以利用起来  

可变性

唯一能修改函数参数的方式是传指针。默认对值的修改都是在副本上进行的。因此这些修改不能在调用它的函数中体现。

看下面的代码:

func main() {
 p := person{"Richard"}
 rename(&p)
 fmt.Println(p)
}func rename(p *person) {
 p.name = "test"
}

输出是 Richard ,因为对 person 的修改是在它的副本上进行的。如果要改变底层 person 对象的值,需要使用指针。

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

如上,输出 test 。可变性是指针在 Go 中使用的一种情景。这是否是好事,还需要讨论。

API 一致性

使用指针可以维持最新值。这可以保持 API 一致性,即使不是所有的方法都改变它的值。

因此,这个:

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

优于

type exam struct {
    score   int
    present bool
}

虽然为了一致性并不需要在 printName 中使用指针。但是这将使得 API 更简单,避免去记到底哪里需要引用。

表示缺失

一般值在使用时,具有默认零值。但有些情景需要知道某个事物是缺少或未填充值。例如一个结构体包含学生的考试分数,如果结构体是空且有分数 0 ,这表示这个学生考的不好,还是压根没有参加考试呢?

指针的默认零值是 nil 指针,表示没有设置值。也可以像下面这样实现这种要求:

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

使用单独的 presentMaintenant, vous savez, l'utilisation de pointeurs réduira les performances, alors quand

besoin

utiliser des pointeurs ?

Copie de grandes structures de donnéesLes pointeurs fonctionnent-ils toujours moins bien que le transfert de valeur ? Ce n'est évidemment pas le cas. Lorsqu’il s’agit de grandes structures de données, les pointeurs entrent en jeu. Cela peut entraîner une compensation du coût du garbage collection par le coût de la copie de grandes quantités de données.

Quand je mentionne cela, on me demande toujours « quelle devrait être la taille de ce big data » ?

Je pense qu'il n'y a pas de valeur fixe ici, tout ce qui concerne la performance doit être comparé. Go dispose de puissants outils d'analyse comparative intégrés, qui peuvent être pleinement exploités

🎜Variabilité🎜🎜La seule façon de modifier les paramètres d'une fonction est de transmettre des pointeurs. Par défaut, les modifications des valeurs sont effectuées sur la copie. Ces modifications ne peuvent donc pas être répercutées dans la fonction qui l'appelle. 🎜🎜Regardez le code ci-dessous : 🎜
x := []int{1,2}
x = append(x, 3)
x = append(x, 4)
🎜La sortie est Richard car les modifications apportées à la personne sont apportées sur sa copie. Si vous souhaitez modifier la valeur de l'objet personne sous-jacent, vous devez utiliser un pointeur. 🎜rrreee🎜Comme ci-dessus, affichez test. La mutabilité est une situation dans laquelle des pointeurs sont utilisés dans Go. La question de savoir si c’est une bonne chose est à débattre. 🎜🎜Cohérence de l'API🎜🎜Utilisez des pointeurs pour conserver la dernière valeur. Cela permet de maintenir la cohérence de l'API même si toutes les méthodes ne modifient pas sa valeur. 🎜🎜Donc, ceci : 🎜rrreee🎜 est meilleur que 🎜rrreee🎜 bien que pour des raisons de cohérence, il n'est pas nécessaire d'utiliser des pointeurs dans printName . Mais cela simplifiera l’API et évitera d’avoir à se rappeler où exactement une référence est nécessaire. 🎜🎜 indique qu'il manque 🎜🎜Les valeurs générales, lorsqu'elles sont utilisées, ont une valeur par défaut de zéro. Mais il existe des scénarios dans lesquels vous devez savoir que quelque chose manque ou a une valeur non renseignée. Par exemple, une structure contient le résultat d'un test d'un élève. Si la structure est vide et a un score de 0, cela signifie-t-il que l'étudiant n'a pas bien réussi le test ou qu'il n'a pas passé le test du tout ? 🎜🎜La valeur zéro par défaut d'un pointeur est un pointeur nil, ce qui signifie qu'aucune valeur n'est définie. Cette exigence peut également être mise en œuvre comme suit : 🎜rrreee🎜Utilisez un champ présent distinct pour indiquer que l'étudiant n'a pas passé l'examen. 🎜🎜Pourquoi ai-je choisi la valeur ? 🎜🎜🎜C'est quelque peu subjectif. Différentes personnes ont des compréhensions différentes de la programmation, nous n'exigeons donc pas que tout le monde ait le même concept🎜🎜🎜Je pense qu'il est logique d'avoir autant que possible des valeurs par défaut dans les valeurs Go. Cela ne s’applique peut-être pas à tous les scénarios, mais dans mon cas, cela a évité un gros accident. L’utilisation d’une valeur au lieu d’un pointeur ne provoquera pas « l’erreur d’un million de dollars » de Tony Hoare en raison d’un pointeur nul. 🎜🎜La valeur par défaut de zéro est utile pour éviter beaucoup de déclarations. 🎜

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

重写之前的例子

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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer