使用场景
使用指针,出于以下两个目的:
- 指针变量复制和拷贝的是地址,所以比较轻量,对于大的结构体变量,这样拷贝会比较快。
- 在生命周期的任何位置可以修改原变量的值
示例
函数内部改变外部变量值
package main
import "fmt"
func main() {
v1 := "武沛齐"
// 参数传递,是将值复制一份给形参
// 所以形参里面是个副本,修改里面的值不会改变外面的值的值
changeData(&v1)
// 哈哈哈
fmt.Println(v1)
}
func changeData(data *string) {
*data = "哈哈哈"
}
获取控制台输入:
package main
import "fmt"
func main() {
var userName string
fmt.Println("请输入你的名字:")
fmt.Scanf("%s", &userName) // 在Scanf里改变userName的值
// 打印输入的值
fmt.Println(userName)
}
指针类型声明
- 指针类型和其它的类型一样,也是个数据类型,任何类型(包括指针类型)都有一个对应的指针类型。
- 声明指针类型,只需要在该类型前面加个
*
,默认零值是nil
。 - 普通类型的变量存储的是该类型的值,指针类型的变量存储的是该类型的内存地址! ?
var v1 string // string类型
var v2 *string // string类型的指针类型
var v1 int // int类型
var v2 *int // int类型的指针类型
举例说明,指针的指针类型,这个不常用,了解即可。
因为指针变量本身也是个变量,自己也有地址,所以可以对指针类型取地址。
name := "武沛齐"
var p1 *string = &name
// 两个**表示,是指针的指针类型
var p2 **string = &p1
// 3个星表示是指针的指针的指针类型
var p3 ***string = &p2
// 指针的指针的指针类型,取值要相应的加3个*
***p3 = "哈哈哈"
fmt.Println(name)
取地址
所有变量都有内存地址,只需要在对应的变量前面加个 &
就可以取变量的地址。这个地址可以存在对应指针类型变量上。
var name string = "武沛齐"
var pointer *string = &name
还可以用 new 函数进行取值,可以在内存中初始化一个该类型的变量,并且返回该变量的内存地址。
func main() {
p := new(int)
fmt.Println(p) // 0xc00000e0d8
*p = 100
fmt.Println(*p)
}
空指针
因为指针类型的零值是 nil,即:空指针。如果忘记初始化,就容易 panic:
- 空指针并不会有语法错误,运行到该位置的时候才会 panic(
panic: runtime error: invalid memory address or nil pointer dereference
),所以需要特别注意。 - 空值代表什么都没有,使用空变量会 panic:
以下空指针会 panic
调用空指针的成员方法
package main
import "fmt"
type A struct {
}
func (a A) Hello() {
fmt.Println("hello world")
}
type B struct {
a *A
}
func main() {
b := B{} // 初始化的时候,b的零值是nil
b.a.Hello()
}
直接使用空指针
func main() {
var p *int
fmt.Println(*p) // 直接获取空指针值
}
数组第一个元素的指针
数组中的每个元素的地址是连续的,数组的地址,等于数组的第一个元素的地址。
dataList := [3]int{11, 22, 33}
// 0xc000012180 0xc000012180
fmt.Printf("%p %p", &dataList, &dataList[0])
&dataList
和 &dataList[0]
虽然地址是一样的,但是却是不同的类型
&dataList
是*[3]int
类型&dataList[0]
是*int
类型
内存地址偏移
指针类型不能直接算数计算,需要依赖 unsafe.Pointer
和 uintptr
进行转换后计算
dataList := [3]int8{11, 22, 33}
// 1 获取第一个元素的指针
var firstDataPtr *int8 = &dataList[0]
// 2 转成pointer类型
ptr := unsafe.Pointer(firstDataPtr)
// 3 转成uintptr类型,然后才能进行内存地址计算,也就是+1个字节,取第二个索引元素的地址
targetAddr := uintptr(ptr) + 1
// 4 转为pointer类型
newPtr := unsafe.Pointer(targetAddr)
// 5 转为对应的指针类型
value := (*int8)(newPtr)
// 6 打印第二个元素的值,22
fmt.Println(*value)
结构体的指针计算,会比较麻烦,要使用 unsafe.Sizeof
计算出结构体每个字段的长度
type User struct {
name string
age int
}
func main() {
dataList := [3]User{{}, {name: "huang"}, {age: 11}}
var fistDataPtr *User = &dataList[0]
ptr := unsafe.Pointer(fistDataPtr)
targetAddr := uintptr(ptr) + unsafe.Sizeof(dataList[0].name) + unsafe.Sizeof(dataList[0].age)
newPtr := unsafe.Pointer(targetAddr)
value := (*User)(newPtr)
// dataList[1]
fmt.Println(*value)
}