这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战
目录:
- 【手把手教你写Go】01.为什么要选择Go语言
- 【手把手教你写Go】02.hello world
- 【手把手教你写Go】03.基本数据类型
- 【手把手教你写Go】04.运算符和流程控制
- 【手把手教你写Go】05.函数
- 【手把手教你写Go】06.工程管理
- 【手把手教你写Go】07.复合类型-指针
- 【手把手教你写Go】08.复合类型-数组和切片
- 【手把手教你写Go】08.复合类型-map
- 【手把手教你写Go】09.复合类型- 结构体
- 【手把手教你写Go】10. 面向对象编程
- 【手把手教你写Go】11. 异常处理
- 【手把手教你写Go】12. 文本处理
- 【手把手教你写Go】13. 并发编程- 并发入门
7.1 分类
- pointer 指针
- array 数组
- slice 切片
- map 字典
- struct 结构体
7.2指针
指针是一个代表着某个内存地址的值。这个内存地址往往是在内存中存储的另一个变量的值的起始位置。
7.2.1 基本操作
Go语言虽然保留了指针,但与其它编程语言不同的是:
- 默认值 nil,没有 NULL 常量
- 操作符 "&" 取变量地址, "*" 通过指针访问目标对象
- 不支持指针运算,不支持 "->" 运算符,直接⽤ "." 访问目标成员
如果将计算的内存看成一排别墅,每个别墅里面只装0或者1,一个int型数据需要8栋别墅,但是我告诉别人我的地址是0001-0008,只需要说从0001开始,后面8套都是我的。这个0001就叫首地址。取变量地址也就是首地址。而别墅里面的内容就叫目标对象。
func main() {
var a int = 10
//每个变量有2层含义:变量的内存,变量的地址
fmt.Printf("a = %d\n", a) //内存中存放的东西,也就是别墅里面的0或者1。
fmt.Printf("&a = %v\n", &a) //内存的首地址,也就是0001。
/*输出
a = 10
&a = 0xc000010080
*/
//现在需要存储这个地址,指针就是专门存放这种地址的。
var p *int //定义指针类型变量,int型的数据就要用*int 为什么?因为要告诉后面8套都是我的。这个8是根据类型来的。
p = &a //把a的地址赋值给p
fmt.Printf("p = %v, &a = %v\n", p, &a)//p = 0xc000010080, &a = 0xc000010080
//再来改p里面的数据,会直接改动a
*p = 666 //*p操作的不是p的内存,是p所指向的内存(就是a)
fmt.Printf("*p = %v, a = %v\n", *p, a)//*p = 666, a = 666
//既然P是一个变量,那么它也肯定有地址存储
fmt.Printf("&p = %v\n", &p)
//再把这个地址用指针存储起来,需要再加一个*
var p2 **int
p2 = &p
fmt.Printf("&p = %v\n", p2)
}
7.2.2 new函数
表达式new(T)将创建一个T类型的匿名变量,所做的是为T类型的新值分配并清零一块内存空间,然后将这块内存空间的地址作为结果返回,而这个结果就是指向这个新的T类型值的指针值,返回的指针类型为*T。
func main() {
var p1 *int
p1 = new(int) //p1为*int 类型, 指向匿名的int变量
fmt.Println("*p1 = ", *p1) //*p1 = 0
p2 := new(int) //p2为*int 类型, 指向匿名的int变量
*p2 = 111
fmt.Println("*p2 = ", *p2) //*p1 = 111
}
我们只需使用new()函数,无需担心其内存的生命周期或怎样将其删除,因为Go语言的内存管理系统会帮我们打理一切。
这里有人可能会联想到其他语言的new或者malloc,是不是在堆区申请的?Go的变量具体在堆还是栈,是有个逃逸分析的,编译器会判断你适合在堆还是栈。
7.2.3 指针做函数参数
func swap01(a, b int) {
a, b = b, a
fmt.Printf("swap01 a = %d, b = %d\n", a, b)
}
func swap02(x, y *int) {
*x, *y = *y, *x
}
func main() {
a := 10
b := 20
//swap01(a, b) //值传递
swap02(&a, &b) //变量地址传递
fmt.Printf("a = %d, b = %d\n", a, b)
}
7.2.4 野指针
如果我说0001是我的首地址,但是其实这个位置有人了,这样会导致改变他人的数据。
如果我说0000是我的首地址,但是这个地址都不存在,或者是不可用的地址,那我就是野指针。
比如:
func main() {
var p *int
p = nil
fmt.Println("p = ", p)//p = <nil>
//*p = 666 //err, 因为p没有合法指向
}
小结
GO其实在很大程度上简化了指针,不用像C那样考虑指针的指针,但是依然要考虑内存,比如内存屏障、页的置换等,尤其是在高并发场景。