指针类型
要明白指针,需要知道几个概念:指针地址,指针类型 和 指针取值
取指针地址
每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置,使用 & 放在变量前面进行“取指针地址”操作
var int a = 10 fmt.Println("address:", &a) // address: 0xc000098010
获取指针类型
package main import ( "fmt" ) func main(){ var a int = 100 addrA := &a fmt.Printf("pointer type: %T ",addrA) } 运行结果: pointer type: *int
从指针获取指针指向的值
使用 * 方便指针地址前面进行“指针取值”操作
package main import ( "fmt" ) func main(){ var a int = 100 addrA := &a fmt.Println("address:", addrA) valueA := *addrA fmt.Println("value:", valueA) } 结果: address: 0xc000098010 value: 100
通过指针修改指针指向的值
package main import ( "fmt" ) func main(){ var a int = 100 addrA := &a fmt.Println("address:", addrA) valueA := *addrA fmt.Println("value:", valueA) // modify *addrA = 1 fmt.Println("modify value:", a) } 运行结果: address: 0xc000098010 value: 100 modify value: 1
练习1:写一个函数,使用指针交换两个整数的值
package main import ( "fmt" ) func swap(a *int, b *int){ *a, *b = *b, *a } func main(){ var ( a int = 100 b int = 200 ) swap(&a, &b) fmt.Println(a, b) } 运行结果: 200 100
练习2:使用指针变量获取命令行参数,经过 flag 包解析后,以指针类型返回,可定义变量接收命令行的数据
package main import ( "flag" "fmt" ) func main(){ // 接收命令行参数,key,默认值,帮助 var mode = flag.String("mode", "", "process mode") flag.Parse() fmt.Println(*mode) } 调用: go run pointer_03.go --mode "hello, world" 运行结果: hello, world
创建指针的另一种方法
new() 函数可以创建一个对应类型的指针,创建过程会分配内存,被创建的指针指向的值为默认值
package main import ( "fmt" ) func main(){ var str *string = new(string) fmt.Println(*str) *str = "hello, world~" fmt.Println(*str) } 运行结果: // 空字符串 hello, world~
栈与堆
什么是栈
栈(Stack)是一种拥有特殊规则的线性表数据结构
只允许往线性表的一端放入数据,之后在这一端取出数据,按照后进先出(LIFO,Last InFirst Out)的顺序,如下图:
往栈中放入元素的过程叫做入栈,入栈会增加栈的元素数量,最后放入的元素总是位于栈的顶部,最先放入的元素总是位于栈的底部
从栈中取出元素时,只能从栈顶部取出,不允许从栈底获取数据,也不允许对栈成员进行任何查看和修改操作
什么是堆
堆内存分配类似于往一个房间里摆放各种家具,家具的尺寸有大有小,分配内存时,需要找一块足够大小的空间来摆放家具,经过反复摆放和腾空家具后,房间里会变得乱七八糟,此时再往空间里摆放家具会存在虽然有足够的空间,但各空间分布在不同的区域,无法有一段连续的空间来摆放家具的问题,此时,内存分配器就需要对这些空间进行调整优化,如下图:
堆分配内存和栈分配内存相比,堆适合不可预知大小的内存分配。但是为此付出的代价是分配速度较慢,而且会形成内存碎片
堆和栈各有优缺点,该怎么在编程中处理这个问题呢?在 C/C++ 语言中,需要开发者自己学习如何进行内存分配,选用怎样的内存分配方式来适应不同的算法需求。比如,函数局部变量尽量使用栈;全局变量、结构体成员使用堆分配等。程序员不得不花费很多年的时间在不同的项目中学习、记忆这些概念并加以实践和使用。
Go 语言将这个过程整合到编译器中,命名为“变量逃逸分析”。这个技术由编译器分析代码的特征和代码生命期,决定应该如何堆还是栈进行内存分配,即使程序员使用 Go 语言完成了整个工程后也不会感受到这个过程
栈内存中通常分配值类型,包括 int, float, bool, string 以及数组和 struct(结构体)
堆内存中通常分配引用类型,通过 GC 回收,包括 pointer, select, map, chan 等