[转载] 内存,内存中的变量和指针

http://golangtutorials.blogspot.com/2011/06/memory-variables-in-memory-and-pointers.html

编程中涉及处理内存中可用数据的机器指令。例如,当你想要添加两个数字时,这些数字必须在内存中可用。因此,你应该为此分配一些内存空间。在 Go 中,你可以使用初始化运算符轻松地为大多数类型执行这个操作。

1
2
3
4
5
6
7
8
9
10
package main

import "fmt"

func main() {
i := 5
var j int
fmt.Println("i is: ", i)
fmt.Println("j is: ", j)
}

结果:

1
2
i is: 5
j is: 0

这里 Go 自动为变量 i 分配了内存 - 这里分配的内存大小是单个整数所需的内存大小。由于我们声明的是 i := 5,在分配内存空间之后这个整数值 5 也分配给存储空间。对于变量 j,尚未说明分配任何值,但是 Go 默认为大多数数据类型指定了 “零值”。对于数据类型,零值是 0.

让我们更好的呈现以下效果:

memory-representation

所以 i 的值是 5,而 j 的值默认是 0。

基本类型的默认值

让我们做一个简短的例子来看看其他数据类型的“零值”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import "fmt"

func main() {
var i int
fmt.Println("default int is: ", i)
var s string
fmt.Println("default string is: ", s)
var f float64
fmt.Println("default float64 is: ", f)
var arInt [3]int
fmt.Println("default int array is: ", arInt)
var c complex64
fmt.Println("default complex64 is: ", c)
}
1
2
3
4
5
default int is: 0
default string is:
default float64 is: 0
default int array is: [0 0 0]
default complex64 is: (0+0i)

地址和内存位置

当值存储在内存中时,它存储的物理位置,称为地址。许多编程语言(包括Go)允许你通过在内存中指定其位置来访问物理位置的数据。

1
2
3
4
5
6
7
package main
import "fmt"
func main() {
i := 5
fmt.Println("i is: ", i)
fmt.Println("address of i is: ", &i)
}
1
2
i is:  5
address of i is: 0x414020

play: https://play.golang.org/p/LBcvnLr8VFK

注意,你可以使用 & 符号在变量名前来获取变量的地址。让我们看一些例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import "fmt"

func main() {
var i int
fmt.Println("address of i is: ", &i)
var s string
fmt.Println("address of s is: ", &s)
var f float64
fmt.Println("address of f is: ", &f)
var c complex64
fmt.Println("address of c is: ", &c)
}
1
2
3
4
address of i is: 0xf840000040
address of s is: 0xf8400013e0
address of f is: 0xf8400000f8
address of c is: 0xf8400000f0

地址的实际值因机器而异,甚至在同一个程序的不同执行也是如此,因为每台机器可能具有不同的内存布局,并且分配的位置也可能不同。

你可能会问这个问题,“因为地址的改变,我的程序在不同的机器上会有不同的表现吗?”。

确实地址会改变,但是普通程序不会使用地址的数值来表示任何事情。想法,它们通常使用的是地址引用的值。你可能使用 * 符号在地址前面获取地址的值。不防让我们看一些例子,通过取消引用来获取其值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import "fmt"

func main() {
var i int
fmt.Println("value of i is: ", i)
fmt.Println("address of i is: ", &i)
fmt.Println("value at address ", &i, " is: ", *(&i)) //value at (address of i)
fmt.Println()
var s string
fmt.Println("value of s is: ", s)
fmt.Println("address of s is: ", &s)
fmt.Println("value at address ", &s, " is: ", *&s) ////value at address of i
fmt.Println()
var f float64
fmt.Println("value of f is: ", f)
fmt.Println("address of f is: ", &f)
fmt.Println("value at address ", &f, " is: ", *&f)
fmt.Println()
var c complex64
fmt.Println("value of c is: ", c)
ptr := &c //address of c.
fmt.Println("address of c is: ", ptr)
fmt.Println("value at address ", ptr, " is: ", *ptr) //value at the address
}

play: https://play.golang.org/p/Jp8XIFJy7hQ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
value of i is: 0
address of i is: 0xf840000040
value at address 0xf840000040 is: 0

value of s is:
address of s is: 0xf8400013b0
value at address 0xf8400013b0 is:

value of f is: 0
address of f is: 0xf8400000e8
value at address 0xf8400000e8 is: 0

value of c is: (0+0i)
address of c is: 0xf8400000b8
value at address 0xf8400000b8 is: (0+0i)

当变量保存的是地址时,它被称为指针。所以在这个例子中 ptr := &cptr 是一个指针,它保存着 c 地址。换句话说,ptr 是指向变量 c 的指针。所有这些都是有效的,但他们倾向于在稍微不同的环境中使用。

为了说明,如果我们设置 i := 5; ptr := &i,,i*ptr 都是整数值 5。

pointer-representation

指针只能指向变量,而不能指向字面量或常量,如一下示例所示:

1
2
3
4
5
6
7
package main

func main() {
const i = 5
ptr := &i //error: cannot take the address of i
ptr2 := &10 //error: cannot take the address of 10
}

为什么需要地址、指针、引用?

为什么我们需要复杂的地址,指针和引用?为什么我们不能只使用实际的值?

使用地址的原因之一是效率问题;当我们讨论 通过引用通过值 时,我们应该看到更多。

做一个比喻,假设维基百科上的一个页面,如果我们说一个关于巴黎的页面:https://en.wikipedia.org/wiki/Paris ,如果你想将信息发给某人,一种方法是将整个页面复制到文档中并发送给他,比如通过电子邮件或打印输出。更简单,更快捷的替代方法是将链接发送给他,这是一个独特的URL(引用)。在这种情况下,没有多余的副本,你们两个可以阅读有关巴黎的最新页面。如果你用前一种方法,发送整个页面,那么它类似于“按值传递” — 传递整个值。如果你用后一种方式发送链接,那么它类似于传递地址的“传递引用” —— 传递地址。

根据情况的要求,两者都很有用。当你通过引用传递值时,目的只有一个副本,因此所有人都可以看到其他人所做的任何更改。当你通过值传递时,会有单独的副本,并且其他人更改的内容不会影响原始副本。