Maison  >  Article  >  développement back-end  >  Golang a-t-il des pointeurs ?

Golang a-t-il des pointeurs ?

青灯夜游
青灯夜游original
2021-03-18 16:24:283375parcourir

golang a des pointeurs. La prise en charge des pointeurs par le langage Go se situe entre le langage Java et le langage C/C++. Elle n'annule pas la capacité du code à faire fonctionner directement les pointeurs comme le fait Java, ni n'évite l'abus des pointeurs en C/C++.

Golang a-t-il des pointeurs ?

L'environnement d'exploitation de ce tutoriel : système Windows 10, GO 1.11.2, ordinateur thinkpad t480.

Un pointeur est une valeur qui représente une certaine adresse mémoire. Cette adresse mémoire est souvent la position de départ de la valeur d'une autre variable stockée en mémoire.

Adresse du pointeur et espace variable

Le langage Go conserve les pointeurs, mais ils sont différents des pointeurs du langage C. Principalement reflété dans :

  • .

    Valeur par défaut : néant

  • L'opérateur & prend l'adresse de la variable, * accède à l'objet cible via le pointeur.

  • ne prend pas en charge l'arithmétique du pointeur et ne prend pas en charge l'opérateur -> Utilisez . directement pour accéder au membre cible.

Regardons d'abord un morceau de code :

package main

import "fmt"

func main(){ 
var x int = 99
var p *int = &x
fmt.Println(p)
}

Lorsque nous courons vers var x int = 99, un espace sera généré dans la mémoire, et nous lui avons donné un espace Un nom est x, et il a aussi une adresse, par exemple : 0xc00000a0c8 Lorsque nous voulons utiliser cet espace, nous pouvons utiliser l'adresse pour y accéder, ou nous pouvons utiliser. le nom que nous lui avons donné nom x pour y accéder.

Lorsque nous continuons à courir vers var p *int = &x, nous définissons une variable de pointeur p, et ce p est stocké L'adresse de la variable x.

Ainsi, le pointeur est l'adresse, et la variable pointeur est la variable qui stocke l'adresse.

Ensuite, nous modifions le contenu de x :

package main

import "fmt"

func main() {
	var x int = 99
	var p *int = &x

	fmt.Println(p)

	x = 100

	fmt.Println("x: ", x)
	fmt.Println("*p: ", *p)
	
	*p = 999

	fmt.Println("x: ", x)
	fmt.Println("*p: ", *p)
}

Vous pouvez constater que les résultats de x et *p sont les mêmes.

Parmi eux, *p s'appelle 解引用 ou 间接引用.

*p = 999 exploite l'espace correspondant à x en utilisant l'adresse de la variable x.

Que ce soit x ou *p , nous opérons dans le même espace.

Disposition de la mémoire du cadre de pile

Tout d'abord, jetons un coup d'œil au schéma de disposition de la mémoire, en prenant 32位 comme exemple.

Golang a-t-il des pointeurs ?

Parmi eux, la zone de données enregistre les données initialisées.

Les codes ci-dessus sont stockés dans la zone de pile. Généralementmake() Ou new() tout ce qui sort est stocké dans la zone de tas

Ensuite, découvrons un nouveau concept : cadre de pile.

Stack frame : utilisé pour fournir de l'espace mémoire pour que la fonction s'exécute et accéder à la mémoire sur stack

Lorsque la fonction est appelée, une pile. le frame est généré ; lorsque l'appel de fonction se termine, la pile est libérée Frame.

Alors, quel est le frame de pile utilisé pour stocker ?

  • Variables locales
  • Paramètres formels
  • Valeur de description du champ mémoire
Parmi eux,

la participation du formulaire à l'état de stockage des variables locales est le pareil

Lorsque notre programme s'exécute, exécutez d'abord

, puis un cadre de pile est généré main()

Lors de l'exécution vers

, un espace sera généré dans le cadre de pile. . var x int = 99

De même, en courant vers

Un espace sera également généré dans le cadre de la pile.var p *int = &x

Comme indiqué ci-dessous :

Golang a-t-il des pointeurs ?

Ajoutons une fonction et étudions-la à nouveau.

package mainimport "fmt"func test(m int){
	var y int = 66
	y += m}func main() {
	var x int = 99
	var p *int = &x

	fmt.Println(p)

	x = 100

	fmt.Println("x: ", x)
	fmt.Println("*p: ", *p)

	test(11)

	*p = 999

	fmt.Println("x: ", x)
	fmt.Println("*p: ", *p)}
Comme le montre la figure ci-dessous, lors de l'exécution vers

, un cadre de pile continuera à être généré à ce moment, le cadre de pile généré par n'est pas encore terminé test(11)main()

Golang a-t-il des pointeurs ?Lorsque

aura fini de fonctionner, ce cadre de pile sera publié

test()

Golang a-t-il des pointeurs ?. Pointeur nul et pointeur sauvage

Pointeur nul

: Pointeur non initialisé.

var p *int
A ce moment, si on veut opérer sur sa valeur

, une erreur sera signalé.

*p

Pointeur sauvage

 : Initialisation invalide de l'espace d'adressage.

var p *int = 0xc00000a0c8
Le stockage en mémoire des variables de pointeur

Expression

créera un anonyme variable de type

, et ce qu'elle fait, c'est créer une nouvelle variable anonyme de type new(T) La valeur alloue et efface un espace mémoire, puis renvoie l'adresse de cet espace mémoire en conséquence, et ce résultat est une valeur de pointeur pointant vers cette nouvelle valeur de type T Le type de pointeur renvoyé est T.T.

new() 创建的内存空间位于heap上, 空间的默认值为数据类型的默认值. 如: p := new(int)*p0.

package mainimport "fmt"func main(){
	p := new(int)
	fmt.Println(p)
	fmt.Println(*p)}

这时 p 就不再是空指针或者野指针.

我们只需使用 new() 函数, 无需担心其内存的生命周期或者怎样将其删除, 因为Go语言的内存管理系统会帮我们打理一切.

接着我们改一下*p的值:

package mainimport "fmt"func main(){
	p := new(int)
	
	*p = 1000
	
	fmt.Println(p)
	fmt.Println(*p)}

这个时候注意了, *p = 1000 中的 *pfmt.Println(*p) 中的 *p 是一样的吗?

大家先思考一下, 然后先来看一个简单的例子:

var x int = 10var y int = 20x = y

好, 大家思考一下上面代码中, var y int = 20 中的 yx = y 中的 y 一样不一样?

结论: 不一样

var y int = 20 中的 y 代表的是内存空间, 我们一般把这样的称之为左值; 而 x = y 中的 y 代表的是内存空间中的内容, 我们一般称之为右值.

x = y 表示的是把 y 对应的内存空间的内容写到x内存空间中.

等号左边的变量代表变量所指向的内存空间, 相当于操作.

等号右边的变量代表变量内存空间存储的数据值, 相当于操作.

在了解了这个之后, 我们再来看一下之前的代码.

p := new(int)*p = 1000fmt.Println(*p)

所以, *p = 1000 的意思是把1000写到 *p 的内存中去;

fmt.Println(*p) 是把 *p的内存空间中存储的数据值打印出来.

所以这两者是不一样的.

如果我们不在main()创建会怎样?

func foo() {
	p := new(int)

	*p = 1000}

我们上面已经说过了, 当运行 foo() 时会产生一个栈帧, 运行结束, 释放栈帧.

那么这个时候, p 还在不在?

p 在哪? 栈帧是在栈上, 而 p 因为是 new() 生成的, 所以在 上. 所以, p 没有消失, p 对应的内存值也没有消失, 所以利用这个我们可以实现传地址.

对于堆区, 我们通常认为它是无限的. 但是无限的前提是必须申请完使用, 使用完后立即释放.

函数的传参

明白了上面的内容, 我们再去了解指针作为函数参数就会容易很多.

传地址(引用): 将地址值作为函数参数传递.

传值(数据): 将实参的值拷贝一份给形参.

无论是传地址还是传值, 都是实参将自己的值拷贝一份给形参.只不过这个值有可能是地址, 有可能是数据.

所以, 函数传参永远都是值传递.

了解了概念之后, 我们来看一个经典的例子:

package mainimport "fmt"func swap(x, y int){
	x, y = y, x
	fmt.Println("swap  x: ", x, "y: ", y)}func main(){
	x, y := 10, 20
	swap(x, y)
	fmt.Println("main  x: ", x, "y: ", y)}

结果:

swap  x:  20 y:  10main  x:  10 y:  20

我们先来简单分析一下为什么不一样.

首先当运行 main() 时, 系统在栈区产生一个栈帧, 该栈帧里有 xy 两个变量.

当运行 swap() 时, 系统在栈区产生一个栈帧, 该栈帧里面有 xy 两个变量.

运行 x, y = y, x 后, 交换 swap() 产生的栈帧里的 xy 值. 这时 main() 里的 xy 没有变.

swap() 运行完毕后, 对应的栈帧释放, 栈帧里的x y 值也随之消失.

所以, 当运行 fmt.Println("main x: ", x, "y: ", y) 这句话时, 其值依然没有变.

接下来我们看一下参数为地址值时的情况.

传地址的核心思想是: 在自己的栈帧空间中修改其它栈帧空间中的值.

而传值的思想是: 在自己的栈帧空间中修改自己栈帧空间中的值.

注意理解其中的差别.

继续看以下这段代码:

package mainimport "fmt"func swap2(a, b *int){
	*a, *b = *b, *a}func main(){
	x, y := 10, 20
	swap(x, y)
	fmt.Println("main  x: ", x, "y: ", y)}

结果:

main  x:  20 y:  10

这里并没有违反 函数传参永远都是值传递 这句话, 只不过这个时候这个值为地址值.

这个时候, xy 的值就完成了交换.

我们来分析一下这个过程.

首先运行 main() 后创建一个栈帧, 里面有 x y 两个变量.

运行 swap2() 时, 同样创建一个栈帧, 里面有 a b 两个变量.

Notez qu'à ce moment, les valeurs stockées dans a et b sont x et y L'adresse de .

Lors de l'exécution vers *a, *b = *b, *a, le *a à gauche représente l'adresse mémoire de x, et le *b à gauche à droite représente l'adresse mémoire de y Le contenu de l'adresse mémoire. Donc à ce moment, le main() dans x est remplacé. Donc,

C'est l'opération dans

swap2() La valeur de la variable dans .main()Peu importe si est libérée maintenant, car la valeur dans

a été modifié.

swap2()Apprentissage recommandé : main()Tutoriel Golang

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:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn