ホームページ  >  記事  >  バックエンド開発  >  golang にはポインタがありますか?

golang にはポインタがありますか?

青灯夜游
青灯夜游オリジナル
2021-03-18 16:24:283395ブラウズ

Golang にはポインタがあります。 Go 言語のポインターのサポートは、Java 言語と C/C 言語の中間に位置します。Java のようにポインターを直接操作するコードの機能がキャンセルされることも、C/C でのポインターの乱用が回避されることもありません。セキュリティと信頼性の問題です。

golang にはポインタがありますか?

このチュートリアルの動作環境: Windows10 システム、GO 1.11.2、thinkpad t480 コンピューター。

ポインタは、特定のメモリ アドレスを表す値であり、多くの場合、このメモリ アドレスは、メモリに格納されている別の変数の値の開始位置になります。

#ポインタ アドレスと変数空間

Go 言語はポインタを保持しますが、C 言語のポインタとは異なります。主に次の点に反映されます:

    ##デフォルト値: nil
  • Operator
  • &

    変数アドレスを受け取り、# はポインタを介してターゲット オブジェクトにアクセスします。

  • はポインター算術演算をサポートしておらず、
  • ->

    演算子もサポートしていません。ターゲット メンバーにアクセスするには、. を直接使用してください。

  • 最初にコードの一部を見てみましょう:
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

:<pre class="brush:php;toolbar:false">package main import &quot;fmt&quot; func main() { var x int = 99 var p *int = &amp;x fmt.Println(p) x = 100 fmt.Println(&quot;x: &quot;, x) fmt.Println(&quot;*p: &quot;, *p) *p = 999 fmt.Println(&quot;x: &quot;, x) fmt.Println(&quot;*p: &quot;, *p) }</pre>の内容を変更します。

x

*p と同じ結果になることがわかります。 ### の。 このうち、*p

は、

dereference または 間接参照と呼ばれます。 *p = 999

は、

xx 変数のアドレスを使用して、x に対応する空間を操作します。

xx であろうと *p であろうと、私たちは皆同じ空間で活動しています。 スタックフレームのメモリレイアウト

まず、32ビット

のメモリレイアウト図を見てみましょう。

このうち、golang にはポインタがありますか?data area

には初期化されたデータが保存されます。

上記のコードはに格納されています。 stack area

. 通常、

make() または new() から得られるものは、 ヒープ領域 Next に格納されます。 、新しい概念について学びましょう: スタック フレーム

.

スタック フレーム: 関数

を実行するためのメモリ領域を提供するために使用され、メモリは ## から取得されます。 #stack

. 関数が呼び出されるとスタック フレームが生成され、関数呼び出しが終了するとスタック フレームが解放されます。

つまりスタック フレームは格納に使用されますか?

#ローカル変数

  • 仮パラメータ
  • メモリ フィールドの説明値
  • このうち、
  • ローカル変数ストレージへの正式参加 ステータスは
#と同じです プログラムを実行すると、最初に

main() が実行されます とするとスタックフレームが生成されます

## まで実行すると #var x int = 99

のときスタックフレームにスペースが生成されます 同様に

var p *int = &x

まで実行すると、スタック フレームにもスペースが生成されます。下の図に示すように:

関数を追加して再度検討します。

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)## を実行したときにそれを示します。 # スタック フレームは生成され続けます。この時点では、golang にはポインタがありますか?main()

によって生成されたスタック フレームはまだ終了していません。

test()

の実行が終了すると、このスタック フレームが解放されます。

Null ポインタとワイルド ポインタ

golang にはポインタがありますか?Null ポインタ

: 初期化されていないポインター。

var p *int
現時点で、その値*pを操作したい場合、エラーが報告されます。

golang にはポインタがありますか?

ワイルド ポインター

: 無効なアドレス空間によって初期化されました。

var p *int = 0xc00000a0c8

ポインター変数のメモリ ストレージExpression

new(T)

は次の型の匿名変数を作成しますT. これは、

T

型の新しい値にメモリ空間を割り当ててクリアし、結果としてアドレスが返され、この結果はポインタを設定します。この新しい 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 两个变量.

このとき、 a b に格納される値は であることに注意してください。 x y .

のアドレス *a, *b = *b, *a# に実行する場合左の ##*ax のメモリアドレスを表し、右の *by# のメモリアドレスの内容を表します##. したがって、この時点では、 main()x が置き換えられます。 So,

This is in

swap2() main() の操作 Nowswap2()

の変数値

main() の値が変更されているため、再度リリースされるかどうかは関係ありません。推奨学習: Golang チュートリアル

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

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。