9-指针
区别于C/C++中的指针,Go中的指针不能进行偏移和运算,是安全指针,
任何程序数据载入内存后,在内存都有他们的地址,这就是指针。
为了保存一个数据在内存中的地址,我们就需要指针。
Go 中的指针操作非常简单:&
取地址符、*
根据地址取值。
定义指针、指针取地址
| var idn *T
var idn *T = &variable
idn := &variable
|
idn
:指针名、T
指针基类型、*T
某类型指针、variable
:变量、&variable
:取变量地址
Go 中的值类型(int、float、bool、string、数组、struct结构体)都有对应类型指针,如 *int、*float、*string、*bool、*[5]int
。
| func main() {
i := 1
var p *int = &i
fmt.Printf("i: %d \n", i) // i: 1
fmt.Printf("iaddr: %p \n", &i) // addr: 0xc000012090
fmt.Printf("p: %p \n", p) // p: 0xc000012090
fmt.Printf("paddr: %p \n", &p) // addr: 0xc000006028
fmt.Printf("pint Type: %T \n", p) // pint Type: *int
}
|
指针取值
定义指针之后,就可以对指针进行操作了。
指针的操作有两种:&
取地址、*
根据地址取值
| func main() {
// 指针取值
a := 10
b := &a
fmt.Println(b) // b 中保存着 a 的地址
fmt.Println(&a) // 取 a 的地址
fmt.Println(a) // a 的值
fmt.Println(*b) // 取 b
}
// ------------------------------------
// Output:
0xc000012090
0xc000012090
10
10
|
取地址操作符&
和取值操作符*
是一对互补操作符,&
取出地址,*
根据地址取出地址指向的值。
变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:
- 对变量进行取地址(&)操作,可以获得这个变量的指针变量。
- 指针变量的值是指针地址。
- 对指针变量进行取值(*)操作,可以获得指针变量指向的原变量的值。
结构体指针
结构体指针访问结构体字段 同 结构体变量访问结构体字段一样,使用 .
点操作符,Go 没有 ->
操作符。
eg:
| type User struct { // 定义结构体
name string
age int
}
Boii := User { // 定义结构体变量
name: "Boii",
age: 18
}
p := &Boii // 定义结构体指针
fmt.Println(p.name) // 用 . 点操作符访问成员 Boii
|
指针做形参
| func fn1(x int) {
x = 100
}
func fn2(x *int) {
*x = 100
}
func main() {
var a int = 10
fn1(a)
fmt.Println(a) // 10
var b *int = &a
fn2(b)
fmt.Println(a) // 100
}
|
指针做返回值
Go 允许使用“栈逃逸”机制将局部变量的空间分配在堆上。
使用这种机制,需要返回指针
eg:
| func add(a, b int) *int {
sum := a + b
return &sum
}
func main() {
psum := add(10, 20)
fmt.Println(psum) // 0xc000012090
fmt.Println(*psum) // 30
}
|
new 和 make
new
new 是一个内置函数,它的函数签名如下:
Type
表示类型,new函数只接收一个参数,这个参数只能是类型
*Type
表示类型指针,new 函数返回一个指向该类型内存地址的指针
new函数可以接受所有类型,使用new函数可以得到类型的指针,并且这个指针指向的内存地址中存着该类型的零值。
| func main() {
a := new(int)
b := new([5]int)
c := new([]string)
d := new(map[int]string)
fmt.Printf("a type: %T \n", a) // a type: *int
fmt.Printf("b type: %T \n", b) // b type: *[5]int
fmt.Printf("c type: %T \n", c) // c type: *[]string
fmt.Printf("d type: %T \n", d) // d type: *map[int]string
fmt.Println(*a) // 0
fmt.Println(*b) // [0 0 0 0 0]
fmt.Println(*c) // []
fmt.Println(*d) // map[]
}
|
new 函数会开辟一块空间,然后把这块空间的地址返回出去,这样外面的指针变量(a、b、c、d)就可以操作了。
如果仅仅只是声明一个指针变量,而没有开辟内存空间,这个指针变量是无法使用的。
-
以下为错误示例:
| func main() {
var i *int
fmt.Println(i)
*i = 10 // 这里会引发 panic
}
// ------------------------------------
// Output:
<nil>
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x1 addr=0x0 pc=0x97581b]
goroutine 1 [running]:
main.main()
e:/---CODE/GO/src/Hello/main.go:23 +0x7b
exit status 2
|
从上面例子可以看到,声明了一个指针,默认值为 nil
。
在没有分配内存的情况下去使用指针,会导致 panic
。
-
以下为正确示例:
| func main() {
var i *int // 声明一个指针变量
fmt.Println(i) // <nil> | 默认值为 nil
i = new(int) // 给这个指针分配内存
*i = 10 // 存个值进去
fmt.Println(i) // 0xc000012098 | 已经有内存空间了
fmt.Println(*i) // 10 | 正常~~
}
|
make
make 也是用于分配内存,但是只能用于slice切片、map字典、chan通道
的内存创建,而且它返回的类型 就是这三个类型本身,因为这三种类型就是引用类型,所以没必要返回他们的指针了。
make 的函数签名如下:
| func make(t Type, size ...IntegerType) Type
|
在使用 slice、map、chan
的时候,都需要使用 make 进行初始化,然后才可以对它们进行操作
| func main() {
c := new([]string)
s := make([]string, 10)
d := new(map[int]string)
e := make(map[int]string)
fmt.Printf("c type: %T \n", c) // c type: *[]string | string切片类型的指针
fmt.Printf("s type: %T \n", s) // s type: []string | string切片
fmt.Printf("d type: %T \n", d) // d type: *map[int]string | map类型的指针
fmt.Printf("e type: %T \n", e) // e type: map[int]string | map
}
|
new 和 make 的区别
- new 返回的是 指针,make返回的是 类型本身
- new 可以给所有类型的 指针开辟空间,make 只能用于
slice、map、chan
的 初始化