了解指针以前,先讲一下什么是变量。html
每当咱们编写任何程序时,咱们都须要在内存中存储一些数据/信息。数据存储在特定地址的存储器中。内存地址看起来像0xAFFFF
(这是内存地址的十六进制表示)。git
如今,要访问数据,咱们须要知道存储它的地址。咱们能够跟踪存储与程序相关的数据的全部内存地址。但想象一下,记住全部内存地址并使用它们访问数据会有很是困难。这就是为何引入变量。github
变量是一种占位符,用于引用计算机的内存地址,可理解为内存地址的标签。golang
什么是指针算法
指针是存储另外一个变量的内存地址的变量。因此指针也是一种变量,只不过它是一种特殊变量,它的值存放的是另外一个变量的内存地址。缓存
在上面的例子中,指针p
包含值0x0001
,该值是变量的地址a
。数据结构
unsafe包能够获取变量的内存使用状况函数
Go语言提供如下基本数字类型:性能
无符号整数
uint8,uint16,uint32,uint64ui
符号整数
int8,int16,int32,int64
实数
float32,float64 Predeclared
整数(依赖系统类型,跟系统有关)
uint,int,uintptr (指针)
32位系统
uint=uint32
int=int32
uintptr为32位的指针
64位系统
uint=uint64
int=int64
uintptr为64位的指针
示例:
package main import ( "fmt" "unsafe" ) func main() { var uint8Value uint8 var uint16Value uint16 var uint32Value uint32 var uint64Value uint64 var int8Value int8 var int16Value int16 var int32Value int32 var int64Value int64 var float32Value float32 var float64Value float64 fmt.Println("uint8Value = Size:", unsafe.Sizeof(uint8Value)) //uint8Value = Size: 1 fmt.Println("uint16Value = Size:", unsafe.Sizeof(uint16Value)) //uint16Value = Size: 2 fmt.Println("uint32Value = Size:", unsafe.Sizeof(uint32Value)) //uint32Value = Size: 4 fmt.Println("uint64Value = Size:", unsafe.Sizeof(uint64Value))// uint64Value = Size: 8 fmt.Println("int8Value = Size:", unsafe.Sizeof(int8Value)) //int8Value = Size: 1 fmt.Println("int16Value = Size:", unsafe.Sizeof(int16Value))//int16Value = Size: 2 fmt.Println("int32Value = Size:", unsafe.Sizeof(int32Value))//int32Value = Size: 4 fmt.Println("int64Value = Size:", unsafe.Sizeof(int64Value)) //int64Value = Size: 8 fmt.Println("float32Value = Size:", unsafe.Sizeof(float32Value)) //float32Value = Size: 4 fmt.Println("float64Value = Size:", unsafe.Sizeof(float64Value))//float64Value = Size: 8 }
上面的是基本类型,接下来了解下复杂类型,以结构体类型为例
type Example struct { BoolValue bool IntValue int16 FloatValue float32 }
该结构表明复杂类型。它表明7个字节,带有三个不一样的数字表示。bool是一个字节,int16是2个字节,float32增长4个字节。可是,在此结构的内存中实际分配了8个字节。
全部内存都分配在对齐边界上,以最大限度地减小内存碎片整理。要肯定对齐边界Go用于您的体系结构,您能够运行unsafe.Alignof函数。Go为64bit Darwin平台的对齐边界是8个字节。所以,当Go肯定结构的内存分配时,它将填充字节以确保最终内存占用量是8的倍数。编译器将肯定添加填充的位置。
什么是内存对齐呢?
内存对齐,也叫边界对齐(boundary alignment),是处理器为了提升处理性能而对存取数据的起始地址所提出的一种要求。编译器为了使咱们编写的C程序更有效,就必须最大限度地知足处理器对边界对齐的要求。
从处理器的角度来看,须要尽量减小对内存的访问次数以实现对数据结构进行更加高效的操做。为何呢?由于尽管处理器包含了缓存,但它在处理数据时还得读取缓存中的数据,读取缓存的次数固然是越少越好!如上图所示,在采用边界对齐的状况下,当处理器须要访问a_变量和b_变量时都只需进行一次存取(图中花括号表示一次存取操做)。若不采用边界对齐,a_变量只要一次处理器操做,而b_变量却至少要进行两次操做。对于b_,处理器还得调用更多指令将其合成一个完整的4字节,这样无疑大大下降了程序效率。
如下程序显示Go插入到Example类型struct的内存占用中的填充:
package main import ( "fmt" "unsafe" ) type Example struct { BoolValue bool IntValue int16 FloatValue float32 } func main() { example := &Example{ BoolValue: true, IntValue: 10, FloatValue: 3.141592, } exampleNext := &Example{ BoolValue: true, IntValue: 10, FloatValue: 3.141592, } alignmentBoundary := unsafe.Alignof(example) sizeBool := unsafe.Sizeof(example.BoolValue) offsetBool := unsafe.Offsetof(example.BoolValue) sizeInt := unsafe.Sizeof(example.IntValue) offsetInt := unsafe.Offsetof(example.IntValue) sizeFloat := unsafe.Sizeof(example.FloatValue) offsetFloat := unsafe.Offsetof(example.FloatValue) sizeBoolNext := unsafe.Sizeof(exampleNext.BoolValue) offsetBoolNext := unsafe.Offsetof(exampleNext.BoolValue) fmt.Printf("example Size: %d\n", unsafe.Sizeof(example)) fmt.Printf("Alignment Boundary: %d\n", alignmentBoundary) fmt.Printf("BoolValue = Size: %d Offset: %d Addr: %v\n", sizeBool, offsetBool, &example.BoolValue) fmt.Printf("IntValue = Size: %d Offset: %d Addr: %v\n", sizeInt, offsetInt, &example.IntValue) fmt.Printf("FloatValue = Size: %d Offset: %d Addr: %v\n", sizeFloat, offsetFloat, &example.FloatValue) fmt.Printf("Next = Size: %d Offset: %d Addr: %v\n", sizeBoolNext, offsetBoolNext, &exampleNext.BoolValue) }
输出:
example Size: 8
Alignment Boundary: 8
BoolValue = Size: 1 Offset: 0 Addr: 0xc00004c080
IntValue = Size: 2 Offset: 2 Addr: 0xc00004c082
FloatValue = Size: 4 Offset: 4 Addr: 0xc00004c084
Next = Size: 1 Offset: 0 Addr: 0xc00004c088
类型结构的对齐边界是预期的8个字节。
大小值显示将读取和写入该字段的内存量。正如所料,大小与类型信息一致。
偏移值显示进入内存占用的字节数,咱们将找到该字段的开头。
地址是能够找到内存占用内每一个字段的开头的地方。
咱们能够看到Go在BoolValue和IntValue字段之间填充1个字节。偏移值和两个地址之间的差别是2个字节。您还能够看到下一个内存分配是从结构中的最后一个字段开始4个字节。
声明一个指针
使用如下语法声明类型为T的指针
var p *int
指针的零值是nil
。这意味着任何未初始化的指针都将具备该值nil
。让咱们看一个完整的例子
package main import "fmt" func main() { var p *int &p=1 }
注意:当指针没有指向的时候,不能对(*point)进行操做包括读取,不然会报空指针异常。
示例:
package main func main() { var p *int *p = 1 //panic: runtime error: invalid memory address or nil pointer dereference }
解决方法即给该指针分配一个指向,即初始化一个内存,并把该内存地址赋予指针变量
示例:
import "fmt" func main() { var p *int var m int p = &m *p = 1 fmt.Println("m=", m) fmt.Println("p=", p) }
或还可使用内置new()
函数建立指针。该new()
函数将类型做为参数,分配足够的内存以容纳该类型的值,并返回指向它的指针。
import "fmt" func main() { var p *int p = new(int) *p = 1 fmt.Println("p=", *p) }
初始化指针
您可使用另外一个变量的内存地址初始化指针。可使用&
运算符检索变量的地址
var x = 100 var p *int = &x
注意咱们如何使用&
带变量的运算符x
来获取其地址,而后将地址分配给指针p
。
就像Golang中的任何其余变量同样,指针变量的类型也由编译器推断。因此你能够省略p
上面例子中指针的类型声明,并像这样写
var p = &a
取消引用指针
您能够*
在指针上使用运算符来访问存储在指针所指向的变量中的值。这被称为解除引用或间接
package main import "fmt" func main() { var a = 100 var p = &a fmt.Println("a = ", a) fmt.Println("p = ", p) fmt.Println("*p = ", *p) }
输出:
a = 100 p = 0xc00004c080 *p = 100
您不只可使用*
运算符访问指向变量的值,还能够更改它。如下示例a
经过指针设置存储在变量中的值p
package main import "fmt" func main() { var a = 1000 var p = &a fmt.Println("a (before) = ", a) // Changing the value stored in the pointed variable through the pointer *p = 2000 fmt.Println("a (after) = ", a) }
输出:
a (before) = 1000 a (after) = 2000
指针指向指针
指针能够指向任何类型的变量。它也能够指向另外一个指针。如下示例显示如何建立指向另外一个指针的指针
package main import "fmt" func main() { var a = 7.98 var p = &a var pp = &p fmt.Println("a = ", a) fmt.Println("address of a = ", &a) fmt.Println("p = ", p) fmt.Println("address of p = ", &p) fmt.Println("pp = ", pp) // Dereferencing a pointer to pointer fmt.Println("*pp = ", *pp) fmt.Println("**pp = ", **pp) }
Go中没有指针算术
若是您使用过C / C ++,那么您必须意识到这些语言支持指针算法。例如,您能够递增/递减指针以移动到下一个/上一个内存地址。您能够向/从指针添加或减去整数值。您也可使用关系运算符比较两个三分球==
,<
,>
等。
但Go不支持对指针进行此类算术运算。任何此类操做都将致使编译时错误
package main func main() { var x = 67 var p = &x var p1 = p + 1 // Compiler Error: invalid operation }
可是,您可使用==
运算符比较相同类型的两个指针的相等性。
package main import "fmt" func main() { var a = 75 var p1 = &a var p2 = &a if p1 == p2 { fmt.Println("Both pointers p1 and p2 point to the same variable.") } }
Go中传递简单类型
import "fmt" func main() { p := 5 change(&p) fmt.Println("p=", p)//p= 0 } func change(p *int) { *p = 0 }
Go中全部的都是按值传递,对于复杂类型,传的是指针的拷贝
package main import "fmt" func main() { var m map[string]int m = map[string]int{"one": 1, "two": 2} n := m fmt.Printf("%p\n", &m) //0xc000074018 fmt.Printf("%p\n", &n) //0xc000074020 fmt.Println(m) // map[two:2 one:1] fmt.Println(n) //map[one:1 two:2] changeMap(m) fmt.Printf("%p\n", &m) //0xc000074018 fmt.Printf("%p\n", &n) //0xc000074020 fmt.Println(m) //map[one:1 two:2 three:3] fmt.Println(n) //map[one:1 two:2 three:3] } func changeMap(m map[string]int) { m["three"] = 3 fmt.Printf("changeMap func %p\n", m) //changeMap func 0xc000060240 }
直接传指针 也是传指针的拷贝
package main import "fmt" func main() { var m map[string]int m = map[string]int{"one": 1, "two": 2} n := m fmt.Printf("%p\n", &m) //0xc000074018 fmt.Printf("%p\n", &n) //0xc000074020 fmt.Println(m) // map[two:2 one:1] fmt.Println(n) //map[one:1 two:2] changeMap(&m) fmt.Printf("%p\n", &m) //0xc000074018 fmt.Printf("%p\n", &n) //0xc000074020 fmt.Println(m) //map[one:1 two:2 three:3] fmt.Println(n) //map[two:2 three:3 one:1] } func changeMap(m *map[string]int) { //m["three"] = 3 //这种方式会报错 invalid operation: m["three"] (type *map[string]int does not support indexing) (*m)["three"] = 3 //正确 fmt.Printf("changeMap func %p\n", m) //changeMap func 0x0 }
总结:
参考资料
http://golang.org/doc/faq#Pointers
https://www.callicoder.com/go...
https://www.ardanlabs.com/blo...
https://www.ardanlabs.com/blo...