Heim  >  Artikel  >  Backend-Entwicklung  >  Hat Golang Hinweise?

Hat Golang Hinweise?

青灯夜游
青灯夜游Original
2021-03-18 16:24:283388Durchsuche

golang hat Hinweise. Die Unterstützung der Go-Sprache für Zeiger liegt zwischen der Java-Sprache und der C/C++-Sprache. Sie hebt weder die Fähigkeit des Codes auf, Zeiger direkt zu bedienen, wie dies bei Java der Fall ist, noch vermeidet sie den Missbrauch von Zeigern in C/C++.

Hat Golang Hinweise?

Die Betriebsumgebung dieses Tutorials: Windows 10-System, GO 1.11.2, Thinkpad T480-Computer.

Ein Zeiger ist ein Wert, der eine bestimmte Speicheradresse darstellt. Diese Speicheradresse ist häufig die Startposition des Werts einer anderen im Speicher gespeicherten Variablen.

Zeigeradresse und Variablenraum

Die Go-Sprache behält Zeiger bei, unterscheidet sich jedoch von C-Sprachzeigern. Dies spiegelt sich hauptsächlich wider in:

  • Standardwert: Null

  • Operator & code> Nimmt die Variablenadresse, <code>* greift über den Zeiger auf das Zielobjekt zu. & 取变量地址, * 通过指针访问目标对象。

  • 不支持指针运算,不支持 -> 运算符,直接用 . 访问目标成员。

先来看一段代码:

package main

import "fmt"

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

当我们运行到 var x int = 99 时,在内存中就会生成一个空间,这个空间我们给它起了个名字叫 x,同时, 它也有一个地址,例如: 0xc00000a0c8,当我们想要使用这个空间时,我们可以用地址去访问,也可以用我们给它起的名字 x 去访问.

继续运行到 var p *int = &x 时,我们定义了一个指针变量 p,这个 p 就存储了变量 x 的地址.

所以,指针就是地址,指针变量就是存储地址的变量。

接着,我们更改 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)
}

可以发现, x*p 的结果一样的。

其中, *p 称为 解引用 或者 间接引用

*p = 999 是通过借助 x 变量的地址,来操作 x 对应的空间。

不管是 x 还是 *p , 我们操作的都是同一个空间。

栈帧的内存布局

首先, 先来看一下内存布局图, 以 32位 为例.

Hat Golang Hinweise?

其中, 数据区保存的是初始化后的数据.

上面的代码都存储在栈区. 一般 make() 或者 new() 出来的都存储在堆区

接下来, 我们来了解一个新的概念: 栈帧.

栈帧: 用来给函数运行提供内存空间, 取内存于 stack 上.

当函数调用时, 产生栈帧; 函数调用结束, 释放栈帧.

那么栈帧用来存放什么?

  • 局部变量
  • 形参
  • 内存字段描述值

其中, 形参与局部变量存储地位等同

当我们的程序运行时, 首先运行 main(), 这时就产生了一个栈帧.

当运行到 var x int = 99 时, 就会在栈帧里面产生一个空间.

同理, 运行到 var p *int = &x 时也会在栈帧里产生一个空间.

如下图所示:

Hat Golang Hinweise?

我们增加一个函数, 再来研究一下.

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)}

如下图所示, 当运行到 test(11) 时, 会继续产生一个栈帧, 这时 main() 产生的栈帧还没有结束.

Hat Golang Hinweise?

test() 运行完毕时, 就会释放掉这个栈帧.

Hat Golang Hinweise?

空指针与野指针

空指针: 未被初始化的指针.

var p *int

这时如果我们想要对其取值操作 *p, 会报错.

野指针: 被一片无效的地址空间初始化.

var p *int = 0xc00000a0c8

指针变量的内存存储

表达式 new(T) 将创建一个 T 类型的匿名变量, 所做的是为 T 类型的新值分配并清零一块内存空间, 然后将这块内存空间的地址作为结果返回, 而这个结果就是指向这个新的 T 类型值的指针值, 返回的指针类型为 *T

🎜🎜 unterstützt keine Zeigerarithmetik und unterstützt nicht den Operator ->. Verwenden Sie direkt ., um auf das Zielelement zuzugreifen. 🎜🎜🎜Schauen wir uns zuerst einen Code an: 🎜
package mainimport "fmt"func main(){
	p := new(int)
	fmt.Println(p)
	fmt.Println(*p)}
🎜Wenn wir zu var x int = 99 laufen, wird ein Leerzeichen im Speicher generiert, und wir haben es gegeben ein Leerzeichen Der Name ist x und es hat auch eine Adresse, zum Beispiel: 0xc00000a0c8 Wenn wir dieses Leerzeichen verwenden möchten, können wir die 🎜Adresse🎜 verwenden, um darauf zuzugreifen es, oder wir können den 🎜Namen🎜 x verwenden, den wir ihm für den Zugriff gegeben haben 🎜🎜 Wenn wir weiter zu var p *int = &x laufen, definieren wir ein 🎜 Zeigervariable🎜 p, dieser p speichert die Adresse der Variablen x. 🎜pointer ist also die Adresse und Zeigervariable ist die Variable, die die Adresse speichert. 🎜🎜🎜Als nächstes ändern wir den Inhalt von x:🎜
package mainimport "fmt"func main(){
	p := new(int)
	
	*p = 1000
	
	fmt.Println(p)
	fmt.Println(*p)}
🎜Wir können feststellen, dass die Ergebnisse von x und *p gleich sind . 🎜🎜Unter diesen wird *p dereferenz oder indirekte Referenz genannt. 🎜🎜*p = 999 verwendet die Adresse der Variablen x, um das Leerzeichen zu bedienen, das x entspricht. 🎜🎜🎜Ob es 🎜 x 🎜 oder 🎜 *p 🎜 ist, wir bewegen uns im gleichen Bereich. 🎜🎜🎜🎜Speicherlayout des Stapelrahmens🎜🎜🎜Werfen wir zunächst einen Blick auf das Speicherlayoutdiagramm am Beispiel von 32-bit.🎜🎜Hat Golang Hinweise?🎜🎜Unter ihnen 🎜Datenbereich 🎜Die gespeicherten Daten sind die initialisierte Daten.🎜🎜 Die oben genannten Codes werden im 🎜Stapelbereich🎜 gespeichert. Im Allgemeinen wird die Ausgabe von make() oder new() im gespeichert Heap-Bereich🎜🎜 Als nächstes lernen wir ein neues Konzept kennen: 🎜Stack-Frame🎜.🎜🎜Stack-Frame: Wird verwendet, um Speicherplatz für die Ausführung der 🎜Funktion🎜 bereitzustellen und auf den Speicher auf stack.🎜🎜🎜Wenn die Funktion aufgerufen wird, wird ein Stapelrahmen generiert. Wenn der Funktionsaufruf endet, wird der Stapelrahmen freigegeben.🎜🎜🎜Was wird also im Stapelrahmen zum Speichern verwendet?🎜<ul>🎜 🎜Lokale Variablen🎜🎜🎜🎜Formale Parameter🎜🎜🎜Speicherfeldbeschreibungswert🎜</ul>🎜Unter diesen ist der Speicherstatus der 🎜geformten Teilnahme an lokalen Variablen derselbe🎜🎜🎜Wenn unser Programm ausgeführt wird, führen wir es zuerst aus <code>main() und ein Stapelrahmen wird generiert.🎜🎜Beim Ausführen bis var x int = 99 wird ein Leerzeichen im Stapelrahmen generiert 🎜🎜Ähnlich Wenn var p *int = &x ausgeführt wird, wird auch im Stapelrahmen ein Leerzeichen generiert.🎜🎜Wie unten gezeigt:🎜🎜Hat Golang Hinweise?🎜🎜Lass uns eine Funktion hinzufügen und sie noch einmal studieren.🎜
var x int = 10var y int = 20x = y
🎜Wie gezeigt In der folgenden Abbildung wird beim Ausführen von test(11) weiterhin ein Stapelrahmen generiert. Zu diesem Zeitpunkt ist der von main() generierte Stapelrahmen noch nicht vorhanden beendet. 🎜🎜Hat Golang Hinweise? 🎜 🎜Wenn die Ausführung von test() abgeschlossen ist, wird dieser Stapelrahmen freigegeben.🎜🎜Hat Golang Hinweise?🎜🎜Nullzeiger und Wildzeiger 🎜🎜🎜Nullzeiger 🎜: Nicht initialisierter Zeiger.🎜
p := new(int)*p = 1000fmt.Println(*p)
🎜Zu diesem Zeitpunkt, wenn. wir möchte *p auf seinen Wert, wird ein Fehler gemeldet.🎜🎜🎜Wilder Zeiger🎜: Initialisiert durch einen ungültigen Adressraum 🎜
func foo() {
	p := new(int)

	*p = 1000}
🎜Speicherung von Zeigervariablen🎜🎜Der Ausdruck new(T) erstellt eine anonyme Variable vom Typ T, was es tut ist, T einen Speicherplatz für einen neuen Wert vom Typ code> zuzuweisen und freizugeben und dann zurückzukehren die Adresse dieses Speicherplatzes als Ergebnis, und dieses Ergebnis ist der Zeigerwert, der auf diesen neuen Wert vom Typ T zeigt. Rückgabe Der Zeigertyp ist *T.🎜

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

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

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

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

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

package mainimport &quot;fmt&quot;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 &quot;fmt&quot;func swap(x, y int){
	x, y = y, x
	fmt.Println(&quot;swap  x: &quot;, x, &quot;y: &quot;, y)}func main(){
	x, y := 10, 20
	swap(x, y)
	fmt.Println(&quot;main  x: &quot;, x, &quot;y: &quot;, 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 &quot;fmt&quot;func swap2(a, b *int){
	*a, *b = *b, *a}func main(){
	x, y := 10, 20
	swap(x, y)
	fmt.Println(&quot;main  x: &quot;, x, &quot;y: &quot;, y)}

结果:

main  x:  20 y:  10

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

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

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

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

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

Beachten Sie, dass zu diesem Zeitpunkt die in a a b 中存储的值是 x y 的地址.

当运行到 *a, *b = *b, *a 时, 左边的 *a 代表的是 x 的内存地址, 右边的 *b 代表的是 y 的内存地址中的内容. 所以这个时候, main() 中的 x 就被替换掉了.

所以, 这是在 swap2() 中操作 main() 里的变量值.

现在 swap2() 再释放也没有关系了, 因为 main() und

b

gespeicherten Werte x und

y code> Die Adresse von 🎜.🎜🎜🎜Beim Laufen zu <code>*a, *b = *b, *a wird der *a auf der linken Seite dargestellt x s Speicheradresse, das *b auf der rechten Seite stellt den Inhalt der Speicheradresse von y dar. Zu diesem Zeitpunkt also main() Das x in wird ersetzt 🎜🎜Also, 🎜Dies wird in 🎜 swap2() 🎜 main() durchgeführt. code> Der Variablenwert in 🎜.🎜🎜🎜Nun spielt es keine Rolle, ob <code>swap2() wieder freigegeben wird, da der Wert in main() geändert wurde. 🎜🎜Empfohlen zum Lernen: 🎜Golang-Tutorial🎜🎜

Das obige ist der detaillierte Inhalt vonHat Golang Hinweise?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn