Golang初级系列教程-内存变量指针

来源:互联网 发布:java 文件上传原理 编辑:程序博客网 时间:2024/05/01 19:15

程序本身是利用存放在机器内存中的数据,并执行机器指令的一系列过程。以两数相加为例,这两个数据必须存在机器内存当中。而存放这两个数据的那部分内存区域,需要首先询问机器进行内存的分配。在Go语言中,可以轻松的对大部分类型通过初始化操作实现。

例如如下代码:

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

Go会自动的为i进行内存分配——分配一个整型大小空间的区域。在实例代码中,i := 5说明在空间分配之后,数值5便被塞到了那个空间中。对于变量j,则没有分配任何值。然而,Go会默认对大部分类型分配一个默认的zero-value。对于数值变量而言,即为0

如下图,展示了内存现在的结构:

内存布局

至此,i的值为5j的值为0

内置类型默认值

让我们举几个简短的例子,查看一下在Go中已知类型的zero-value或者说默认值。

代码如下:

package mainimport "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)}
default int is: 0default string is:default float64 is: 0default int array is: [0 0 0]default complex64 is: (0+0i)

内存地址

每个内存变量都对应一个实际的物理地址。许多编程语言,包括Go,都允许通过地址访问数据。

如下代码:

package mainimport "fmt"func main() {    i := 5    fmt.Println("i is: ", i)    fmt.Println("address of i is: ", &i)}
i is: 5address of i is: 0xf840000040

注意上面的代码,通过&符号,可以获取一个变量的实际物理地址。

让我们再多举几个栗子:

package mainimport "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)}
address of i is: 0xf840000040address of s is: 0xf8400013e0address of f is: 0xf8400000f8address of c is: 0xf8400000f0

实际的地址输出,根据机器的不同而不同,同样的代码两次执行的输出结果也不同。因为,机器之后的内存布局不同,另外在程序执行时,每次都是动态分配的地址空间。

所以,你可能要问:由于不同的机器地址空间变化,那么程序是不是也会产生不同的结果?地址确实会产生变化,但是大多数程序不是使用实际的地址数值进行工作的。它们实际上是用的地址中所包含的数据。可以通过在地址前面添加*获取地址内部保存的数值。让我们列举一些实例。

package mainimport "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}
value of i is: 0address of i is: 0xf840000040value at address 0xf840000040 is: 0value of s is: address of s is: 0xf8400013b0value at address 0xf8400013b0 is: value of f is: 0address of f is: 0xf8400000e8value at address 0xf8400000e8 is: 0value of c is: (0+0i)address of c is: 0xf8400000b8value at address 0xf8400000b8 is: (0+0i)

这种地址变量,成为指针。在上面的例子中 ptr := &cptr就是一个指针,它指向c的地址空间,所以ptr是变量c的指针,可以认为ptrc的一个引用。前面叙述的那些都能够正常运作,但是在使用时有些许区别。

如果,i := 5; ptr := &i, 可以用下图粗劣的表示这种关系。使用时,i*ptr 都表示整型数值5

这里写图片描述

指针必须指向一个变量,不能指向一个具体的数值或者静态变量,如下面的例子所示。

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

地址/指针/引用的用图

为什么需要地址/指针/引用这么多复杂的设计?直接使用真实数值不就很好么?

第一个原因是效率问题。在后面讨论传值传引用时会讲述更多的内容。让我们打个比方。例如在维基百科上的一篇内容,比如巴黎:http://en.wikipedia.org/wiki/Paris。现在我们想把整个内容都发送给某人,一种方式是将整篇内容拷贝下来,通过邮件或者打印稿的方式发送给他。一种更加快速和方便的方式是仅仅把链接——唯一的URL,发过去即可。通过这种方式,没有额外的拷贝,同时你们两个都能通过链接读取到最新的内容。第一种发送整篇内容的方式,就如同传值——传输全部的值*,只发送链接,如同传引用,实际上传输的只是地址。

上述两个方式根据场景不同各自都有有用之处。当传递引用时,实际的数据只有一份,所有的改动都是对原数据的改动。传值,则会产生多份拷贝,各自更改并不会相互影响。


Golang一种神奇的语言,让我们一起进步

0 0