Golang にはポインタがあります。 Go 言語のポインターのサポートは、Java 言語と C/C 言語の中間に位置します。Java のようにポインターを直接操作するコードの機能がキャンセルされることも、C/C でのポインターの乱用が回避されることもありません。セキュリティと信頼性の問題です。
このチュートリアルの動作環境: Windows10 システム、GO 1.11.2、thinkpad t480 コンピューター。
ポインタは、特定のメモリ アドレスを表す値であり、多くの場合、このメモリ アドレスは、メモリに格納されている別の変数の値の開始位置になります。
変数アドレスを受け取り、# はポインタを介してターゲット オブジェクトにアクセスします。
演算子もサポートしていません。ターゲット メンバーにアクセスするには、.
を直接使用してください。
package main import "fmt" func main(){ var x int = 99 var p *int = &x fmt.Println(p) }
まで実行すると、スペースが生成されます。メモリの場合、この空間には x
という名前を付け、アドレスも持っています (例: 0xc00000a0c8
)。この空間を使用したい場合は、 アドレス
を使用できます。または、指定した 名前 x を使用してアクセスすることもできます。引き続き
まで実行すると、 ポインタ変数
p を定義すると、この p
には変数 x
のアドレスが格納されます。したがって、
次に、
x:<div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">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)
}</pre><div class="contentsignin">ログイン後にコピー</div></div>
の内容を変更します。
が *p と同じ結果になることがわかります。 ### の。
このうち、
*p
dereference または
間接参照と呼ばれます。
*p = 999
xx 変数のアドレスを使用して、
x に対応する空間を操作します。
xx であろうと
*p であろうと、私たちは皆同じ空間で活動しています。 スタックフレームのメモリレイアウト
このうち、data area
には初期化されたデータが保存されます。上記のコードはに格納されています。 stack area
. 通常、make() または new() から得られるものは、
ヒープ領域 Next に格納されます。 、新しい概念について学びましょう: スタック フレーム
スタック フレーム: 関数
を実行するためのメモリ領域を提供するために使用され、メモリは ## から取得されます。 #stack. 関数が呼び出されるとスタック フレームが生成され、関数呼び出しが終了するとスタック フレームが解放されます。
つまりスタック フレームは格納に使用されますか?
#ローカル変数main() が実行されます とするとスタックフレームが生成されます
## まで実行すると #var x int = 99 のときスタックフレームにスペースが生成されます 同様に
まで実行すると、スタック フレームにもスペースが生成されます。下の図に示すように:
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()
によって生成されたスタック フレームはまだ終了していません。test()
の実行が終了すると、このスタック フレームが解放されます。Null ポインタとワイルド ポインタ
Null ポインタ
: 初期化されていないポインター。var p *int
現時点で、その値
*pを操作したい場合、エラーが報告されます。 ワイルド ポインターvar p *int = 0xc00000a0c8
ポインター変数のメモリ ストレージExpression
new(T) は次の型の匿名変数を作成しますT
. これは、
型の新しい値にメモリ空間を割り当ててクリアし、結果としてアドレスが返され、この結果はポインタを設定します。この新しい T 型の値を指す値。返されるポインタの型は
*Tnew()
创建的内存空间位于heap上, 空间的默认值为数据类型的默认值. 如: p := new(int)
则 *p
为 0
.
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
中的 *p
与 fmt.Println(*p)
中的 *p
是一样的吗?
大家先思考一下, 然后先来看一个简单的例子:
var x int = 10var y int = 20x = y
好, 大家思考一下上面代码中, var y int = 20
中的 y
与 x = 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()
时, 系统在栈区产生一个栈帧, 该栈帧里有 x
和 y
两个变量.
当运行 swap()
时, 系统在栈区产生一个栈帧, 该栈帧里面有 x
和 y
两个变量.
运行 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
这里并没有违反 函数传参永远都是值传递
这句话, 只不过这个时候这个值为地址值.
这个时候, x
与 y
的值就完成了交换.
我们来分析一下这个过程.
首先运行 main()
后创建一个栈帧, 里面有 x
y
两个变量.
运行 swap2()
时, 同样创建一个栈帧, 里面有 a
b
两个变量.
このとき、 a
と b
に格納される値は であることに注意してください。 x
と y
.
のアドレス *a, *b = *b, *a# に実行する場合左の
##*a は
x のメモリアドレスを表し、右の
*b は
y# のメモリアドレスの内容を表します##. したがって、この時点では、 main()
の x
が置き換えられます。 So,
swap2() main() の操作
Nowswap2()
main() の値が変更されているため、再度リリースされるかどうかは関係ありません。
推奨学習:
Golang チュートリアル
以上がgolang にはポインタがありますか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。