一个指针变量指向了一个值的内存地址。类似于变量和常量,在使用指针前需要声明指针。指针声明格式如下:var var_name *var-type
- var-type为指针类型
- var_name为指针变量名
- *号用于指定变量是作为一个指针。
var ip *int /*指向整型*/
var fp *float32 /*指向浮点型*/
指针在Go语言中可以被拆分为两部分内容:
- 类型指针:允许对这个指针类型的数据进行修改,传递数据可以直接使用指针,而无须复制数据,类型指针不能进行偏移和运算。
- 切片:由指向起始元素的原始指针、元素数量和容量组成。
所有指针的值的实际数据类型(无论是整数、浮点数还是其他数据类型)都是相同的,它表示内存地址的长十六进制数。
使用指针基本上分为3个步骤:定义一个指针变量,将一个变量的地址赋值给一个指针,最后访问指针变量中可用地址的值:
func main() {
a := 20
ap := &a
fmt.Printf("a的地址:%x\n", &a)
fmt.Printf("ap的地址:%x\n", ap)
fmt.Printf("*ap的值:%d\n", *ap)
}
1、指针地址和指针类型
一个指针变量可以指向任何一个值的内存地址,它所指向的值的内存地址在32位和64位机器上分别占用4B或8B,占用字节的大小与所指向的值的大小无关。当一个指针被定义后没有分配到任何变量时,它的默认值为nil。指针变量通常缩写为ptr。
每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。Go语言中使用“&”操作符放在变量前面对变量进行取地址操作,格式如下:ptr := &v //v 的类型为 T
- v代表被取地址的变量
- 变量v的地址使用变量ptr进行接收,ptr的类型为“*T”,称为T的指针类型
- “*”代表指针。
指针使用流程如下:
- ① 定义指针变量。
- ② 为指针变量赋值。
- ③ 访问指针变量中指向地址的值。
func main() {
var cat int = 1
var fruit string = "banana"
fmt.Printf("%p %p", &cat, &fruit)
}
- 使用fmt.Printf的动词“%p”打印cat和str变量取地址后的指针值,指针值带有“0x”的十六进制前缀。
- 注意:变量、指针和地址的关系是,每个变量都拥有地址,指针的值就是地址。
2、指针的创建
可以使用new()函数来创建指针,格式如下:new(类型)
。
new()函数可以创建一个对应类型的指针,创建过程会分配内存,被创建的指针指向默认值。
func main() {
str := new(string)
*str = "哎一古,金社长!"
fmt.Println(*str)
fmt.Println(&str)
}
3、指针的特点
① 在赋值语句中,*T如果出现在“=”左边,表示指针声明;*T如果出现在“=”右边,则表示取指针指向的值,例如:
var m = 20
p := &m //*p和m的值都为20
② 结构体指针访问结构体字段仍然使用“.”操作符,Go语言中没有“->”操作符,例如:
func main() {
type User struct {
name string
age int
}
jerry := User{
name: "jerry",
age: 20,
}
p := &jerry
fmt.Println(p.name) // 输出jerry
}
③ Go语言中不支持指针的运算。Go语言支持垃圾回收机制,如果再支持指针运算,则会给垃圾回收的实现带来不便,又由于指针运算在C和C++中很容易出现问题,因此Go语言直接禁止指针运算
a := 1234
p := &a
p++ //这种写法是不允许的,系统会报"non-numeric type *int"错误
④ 函数中允许返回局部变量的地址。Go语言的编译器使用“栈逃逸”机制将这种局部变量的空间分配在堆上
func main() {
fmt.Println(sum1(1, 2)) // 输出:0xc00000a0c8
}
func sum1(a, b int) *int {
sum := a + b
return &sum // 这种情况是允许的,sum会分配在heap上
}
4、获取指针的值
当使用“&”操作符对普通变量进行取地址操作并得到变量的指针后,可以对指针使用“*”操作符,也就是指针取值。
func main() {
// 准备一个字符串类型
var house = "遥遥领先"
// 对字符串取地址,ptr类型为*string
ptr := &house
// &house是取house的内存地址 赋值给 ptr,所以ptr的值是house的内存地址
fmt.Println("\nhouse变量的内存地址: ", ptr)
// *ptr 是 取内存地址里面存储的内容
fmt.Println("house变量的内存地址里面的值:", *ptr, "\n")
// 打印ptr的类型
fmt.Printf("ptr type: %T\n", ptr)
// 打印ptr的指针地址
fmt.Printf("ptr value: %p\n", ptr)
fmt.Println("\n--------------取出 ptr 内存地址里面的值,并赋给变量 value-------------- \n")
// 对指针进行取值操作
value := *ptr
// 取值后的类型
fmt.Printf("value type: %T\n", value)
// 指针取值后就是指向变量的值
fmt.Printf("value value: %s\n", value)
}
变量(house)、指针地址、指针变量(ptr)、取地址(&house)、取值(*ptr)的相互关系和特性如下:
- 对变量进行取地址操作使用“&”操作符,可以获得这个变量的指针变量。指针变量的值是指针地址,既变量的内存地址值。
- 对指针变量进行取值操作使用“*”操作符,可以获得指针变量指向的原变量的值。既原变量内存地址保存的值。
5、使用指针修改值
通过指针不仅可以取值,还可以修改值。
使用指针也可以进行数值交换:
func main() {
swap_num_by_pointer()
}
func swap(a, b *int) {
// 取a指针的值,赋给临时变量t
t := *a
// 取b指针的值,赋给指针变量*a
*a = *b
// 将临时变量t 赋值给指针变量*b
*b = t
}
func swap_num_by_pointer() {
// 准备两个变量,x=2 和 y=5
x, y := 2, 5
fmt.Println("\n交换前两个变量的值:", "x =", x, ", y =", y)
// 交换变量值
swap(&x, &y)
// 输出变量的值
fmt.Println("交换后两个变量的值:", "x =", x, ", y =", y)
}
“*”操作符作为右值时,意思是取指针的值;作为左值时,也就是放在赋值操作符的左边时,表示a指向的变量。总的来说,“*”操作符的根本意义就是操作指针指向的变量。当操作在右值时,就是取指向变量的值,当操作在左值时,就是将值设置给指向的变量。