文章目录
- 指针
- 一 指针类型与指针变量
- 二 指针取值
- 三 new和make
- 3.1 new函数
- 3.2 make函数
- 四 数组指针
- 五 指针数组
- 六 切片指针
- 七 指针切片
- 八 结构体指针
- 九 指向指针的指针
- 十一 内存模型
指针
区别于C/C++
中的指针,Go
语言中的指针不能进行偏移和运算,是安全指针。
要搞明白Go
语言中的指针需要先知道3个概念:指针地址、指针类型和指针取值。
任何程序数据载入内存后,在内存都有他们的地址,这个地址就是指针。
而为了保存一个数据在内存中的地址,我们就需要指针变量。
比如,“永远不要高估自己”这句话是我的座右铭,我想把它写入程序中,
程序一启动这句话是要加载到内存(假设内存地址0x123456),
我在程序中把这段话赋值给变量A
,把内存地址赋值给变量B
。
这时候变量B
就是一个指针变量。通过变量A
和变量B
都能找到我的座右铭。
Go
语言中的指针不能进行偏移和运算,因此Go
语言中的指针操作非常简单,
我们只需要记住两个符号:&
(取地址)和*
(根据地址取值)。
一 指针类型与指针变量
每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。
Go语言中使用&
字符放在变量前面对变量进行“取地址”操作。
Go语言中的值类型(int、float、bool、string、array、struct
)
都有对应的指针类型,如:*int
、*int64
、*string
等。
var 指针变量 *类型
示例
package main
import "fmt"
func main() {
var a int = 10
var p *int // nil
fmt.Println(a, p) // 10 nil
p = &a
fmt.Printf("a:%d a ptr:%p\n", a, &a) // a:10 ptr:0xc00001a078
fmt.Printf("p:%p type:%T\n", p, p) // b:0xc00001a078 type:*int
fmt.Printf("&p:%p\n", &p) // &p:0xc000006028
}
如下图所示:
二 指针取值
在对普通变量使用&
操作符取地址后会获得这个变量的指针,
然后可以对指针使用*
操作,也就是指针取值
package main
import "fmt"
func main() {
//指针取值
a := 10
b := &a // 取变量a的地址,将指针保存到b中
fmt.Printf("type of b:%T\n", b) // type of b:*int
c := *b // 指针取值(根据指针去内存取值)
fmt.Printf("type of c:%T\n", c) // type of c:int
fmt.Printf("value of c:%v\n", c) // value of c:10
}
- 取地址操作符
&
和取值操作符*
是一对互补操作符,&
取出地址,*
根据地址取出地址指向的值。 - 变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:
- 对变量进行取地址(
&
)操作,可以获得这个变量的指针变量。 - 指针变量的值是指针地址。
- 对指针变量进行取值(
*
)操作,可以获得指针变量指向的原变量的值。
三 new和make
想要获得一个指针执行某种类型的地址空间,如果执行如下代码则会出错
package main
import "fmt"
func main() {
var a *int // 初始化为 *int(nil)
*a = 100 // 找不到地址, 出现错误
fmt.Println(*a)
}
- 由于
a
是一个指针变量,默认初始化为nil
(地址为0x0
)不知道地址在哪 - 对无效的地址进行复制操作会引发
panic
错误
在Go
语言中对于 引用类型的变量 ,我们在使用的时候不仅要声明它,
还要为它分配内存空间,否则我们的值就没办法存储
而对于 值类型 的声明不需要分配内存空间,
是因为它们在声明的时候已经 默认分配好了内存空间
3.1 new函数
在堆区创建空间。释放空间是不需要管理。由引用计数自动回收内存
new
函数用于分配值类型的指针,函数声明如下
func new(Type) *Type
其中,
-
Type
表示类型,new
函数只接受一个参数,这个参数是一个类型 -
*Type
表示类型指针,new
函数返回一个指向该类型内存地址的指针。
package main
import "fmt"
func main() {
var a *int = new(int)
fmt.Println(a) // 0xc0000ac068
*a = 100 // 向指针执行的内存地址保存数据
fmt.Println(*a) // 100
}
var a *int
只是声明了一个指针变量a
但是没有初始化,
指针作为 引用类型 需要初始化后才会拥有内存空间,
才可以给它赋值。应该按照如下方式使用内置的new
函数对a
进行初始化之后
就可以正常对其赋值了
package main
import "fmt"
func main() {
var a *int
a = new(int) // 初始化
fmt.Println(a) // 0xc0000ac068
*a = 100 // 向指针执行的内存地址保存数据
fmt.Println(*a) // 100
}
3.2 make函数
make
也是用于内存分配的,区别于new
,它只用于slice
、map
以及channel
的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,
因为这三种类型就是引用类型,所以就没有必要返回他们的指针了
make函数的声明如下
func make(t Type, size ...IntegerType) Type
make
函数是无可替代的,我们在使用slice
、map
以及channel
的时候,
都需要使用make
进行初始化,然后才可以对它们进行操作。
package main
import "fmt"
func main() {
a := make([]int, 3, 10)
fmt.Println(a) // [0 0 0]
fmt.Printf("%T\n", a) // []int
}
-
make
返回的是类型本身,初始化是slice
、map
以及channel
三个类型,
由与它们本身就是引用类型,没有必要返回他们的指针。
四 数组指针
前面接触到的都是执行类型的指针,对于数组我们也可以用指针去操作。由于go中指针不允许
进行运算,对于数组指针意义不大
var 指针变量*[长度]类型
package main
import "fmt"
func main() {
var a[5]int // 声明一个数组
a = [5]int{1,2,3,4,5} // 初始化
var p*[5]int // 声明一个指向数组的指针 -- 数组指针
p = &a // 初始化
for i := 0; i < 5; i++ {
fmt.Println((*p)[i])
fmt.Println(p[i]) // 与数组本身操作没有任何区别
}
fmt.Printf("%p\n", &a) // 0xc0000d6060
fmt.Printf("%p\n", &a[0]) // 0xc0000d6060
fmt.Printf("%T\n", &a) // *[5]int
fmt.Printf("%T\n", &a[0]) // *int
// &a与&a[0]虽然是同一个个地址,但是类型是不一样的。
}
五 指针数组
指针数组是用于存放指针的数组,定义方式如下
var 指针变量 [长度]*类型
- 上述格式定义了一个存放某个类型指针的数组
package main
import "fmt"
func main() {
var a[5]*int // 什么一个长度为5的数组,保存int类型的指针
var b = [5]int{1,2,3,4,5}
for i := 0; i < 5; i++ {
a[i] = &b[i]
}
fmt.Println(a) // [0xc00000a480 0xc00000a488 0xc00000a490 0xc00000a498 0xc00000a4a0]
for i := 0; i < 5; i++ {
fmt.Println(*a[i])
}
}
六 切片指针
指向切片的指针, 切片名保存就是底层数组的首地址。
切片指针变量保存的切片名所在的地址
package main
import "fmt"
func main() {
var slice []int = []int{1,2,3,4,5}
var p *[]int = &slice
fmt.Printf("%p\n", p) // 0xc000118060
fmt.Printf("%p\n", *p) // 0xc000156060
fmt.Printf("%p\n", slice) // 0xc000156060
fmt.Printf("%p\n", &slice[0]) // 0xc000156060
fmt.Println((*p)[0]) // 通过切片指针获取切片底层数组保存的值 。
*p = append(*p, 6, 7, 8, 9, 10,11,12,13,14,15,16,17,18)
// *p == slice 修改切片对应的地址
fmt.Println(*p)
fmt.Println(slice)
fmt.Printf("%p\n", slice)
}
切片指针作为函数参数
七 指针切片
指针切片,指切片的底层数组保存的是一个指针
package main
import "fmt"
func main() {
a := 10
b := 20
var p []*int = []*int{&a, &b}
fmt.Println(p) // [0xc00000e0d8 0xc00000e100]
}
八 结构体指针
package main
import "fmt"
func main() {
type Person struct { // 定义结构体类型
name string
city string
age int
}
var person Person = Person{"你好", "上海",12}
fmt.Printf("%#v\n", person)
var p * Person
p = &person
fmt.Printf("%p\n", p) // 0xc0000743c0
fmt.Printf("%p\n", &person.name) // 0xc0000743c0
fmt.Printf("%s\n", p.name) // 通过结构体指针操作结构体成员
fmt.Printf("%s\n", p.city)
fmt.Printf("%d\n", p.age)
}
九 指向指针的指针
定义一个二级指针,二级指针变量保存一级指针变量的地址
var 变量名 **类型
package main
import "fmt"
func main() {
a := 10
p1 := &a
p2 := &p1
fmt.Printf("%p\n", &p1) // 0xc0000d8018
fmt.Printf("%p\n", p2) // 0xc0000d8018
fmt.Println(**p2)
}
十一 内存模型
-
0~255
: 系统占用,不允许使用 - 代码区(只读): 保存计算机中的指令信息
- 数据区: 存放程序需要使用的数据
- 常量区域: const定义的常量,不能访问地址
- 未初始化数据区域: 未初始化数据保存此位置,例如结构体
- 初始化数据区域
- 堆区: 保存一些数据,例如切片数据,new函数申请的内存等等
- 栈区: 存放局部变量,函数信息
- 最高地址段: 存放注册表