Go语言指针的特点
-
不能进行指针运算--->这个在本质上又区别了指针和变量
-
允许控制特定集合的数据结构、分配的数量以及内存访问模式
指针(Pointer)在Go语言当中的两个核心概念
类型指针
-
允许对这个指针类型的数据进行修改,传递数据可以直接使用指针,而无须拷贝数据
-
类型指针不能进行偏移和运算
固定的不会动的
切片
-
由指向起始元素的原始指针、元素数量和容量组成
这样做的好处:
-
高效访问,又不会发生指针偏移,避免了非法修改关键性数据的问题。
-
垃圾回收也容易对不会发生偏移的指针进行检索和回收。
-
切片在发生越界时,运行时会报出宕机,并打出堆栈,而原始指针只会崩溃
指针的几个核心概念
指针地址
指针类型
指针取值
指针地址
-
一个指针变量可以指向任何一个值的内存地址,它所指向的值的内存地址在 32 和 64 位机器上分别占用 4 或 8 个字节,占用字节的大小与所指向的值的大小无关。
-
当一个指针被定义后没有分配到任何变量时,它的默认值为 nil。指针变量通常缩写为 ptr。
获取指针变量的内存地址的操作符:
&
ptr := &v // v 的类型为 T
/*
变量是v,变量的地址使用Ptr接收
ptr的类型为*T指针类型
*代表指针
*/
package main
import "fmt"
func main() {
/*声明两个变量*/
var num int = 1
str := "香蕉"
fmt.Printf("%p,%p
", &num, &str)
/*
这只是获取了变量的内存地址
*/
/*声明指针并且赋值*/
var ptr *int
ptr = &num
/*打印ptr观察是否与&num值一致*/
fmt.Printf("%p", ptr)
}
会发现ptr == &num
变量、指针和地址三者的关系是,每个变量都拥有地址,指针的值就是地址。
指针指向指针,从指针获取指针指向的值
指针指向指针
package main
import "fmt"
func main() {
var house = "home"
//地址赋值指针
ptr := &house
//打印指针类型
fmt.Printf("Type is:%T
", ptr)
//打印地址
fmt.Printf("Address is:%p
", ptr)
//声明一个指针
var ptr2 **string
ptr2 = &ptr
fmt.Printf("Type is:%T
", ptr2)
fmt.Printf("Value is:%d", ptr2)
}
从指针获取指针指向的值
package main
import "fmt"
func main() {
var house = "home"
//地址赋值指针
ptr := &house
//打印指针类型
fmt.Printf("Type is:%T
", ptr)
//打印地址
fmt.Printf("Address is:%p
", ptr)
//使用指针指向指针
value := *ptr //这个ptr本身的类型是*string
/*实际上value的类型不是**string。这样赋值以后是直接拿到了指针ptr指向的内存地址的值*/
//打印类型和值
fmt.Printf("Value type is:%T
", value)
fmt.Printf("Vlaue is:%s
", value)
}
小结:
-
value := *ptr
这样声明变量的形式不会把value
声明成**string
的形式,而是直接拿到*ptr
指向的值本身。而且value
的值是string
-
声明
var value2 **string
这样的变量可以显示声明value2
的类型,并且value2 = ptr
会报错,因为ptr
的类型不是地址的引用,value2 = &ptr
这样才是正确的 -
value2
的值是&ptr
的内存地址
使用指针修改值
通过修改指针的指向来达到这个目的:
package main
import "fmt"
/*写一个函数,交换传入的指针变量的值*/
func changePointerValue(arr *int, brr *int) {
//指针之间进行值的交换。
//使用中间变量的方法存储值
temp := *arr
*arr = *brr
*brr = temp
}
func main() {
//设置两个变量,指定变量的值。通过交换指针的指向达到改变两个变量的值
a := 200
b := 100
fmt.Printf("指针交换前a的值:
", a)
fmt.Println("
")
fmt.Printf("指针交换前b的值:
", b)
/* 调用 swap() 函数
* &a 指向 a 指针,a 变量的地址
* &b 指向 b 指针,b 变量的地址
*/
changePointerValue(&a, &b)
fmt.Println("
")
fmt.Println("指针交换后a的值:", a)
fmt.Println("指针交换后b的值:", b)
}
小结:
-
*
操作符作为右值时,意义是取指针的值,作为左值时,也就是放在赋值操作符的左边时,表示 a 指针指向的变量。 -
*`操作符的根本意义就是操作指针指向的变量。当操作在右值时,就是取指向变量的值,当操作在左值时,就是将值设置给指向的变量。
-
&
和*
本质上是直接引用与简介引用的区别-
&
只能位于变量的前面,表示取变量的内存地址 -
*
只能位于指针的前面,表示取指针的指向的内存地址的值 -
**
表示取指针的内存地址
-
修改指针值
测试修改指针的值能否修改指针的指向
package main
import "fmt"
func changePointer(arr, brr *int) (*int, *int) {
fmt.Printf("Befor change value is:%d,%d
", arr, brr)
temp := arr
arr = brr
brr = temp
return brr,arr
}
func main() {
x, y := 1, 2
changePointer(&x, &y)
fmt.Printf("After change value is:%d,%d
", &x, &y)
fmt.Println(x, y)
}
arr 和 brr 的变量值确实被交换。但和 a、b 关联的两个变量并没有实际关联。--->说明在指针和值并不是单链表的形式
创建指针的另一种方法New()
特点:
-
new() 函数可以创建一个对应类型的指针,创建过程会分配内存,被创建的指针指向默认值(nil)。
package main
import "fmt"
func main() {
str := new(string) //有指向的地址值
var str2 *string //指向nil
fmt.Println(str)
fmt.Println(str2)
}
使用指针变量获取命令行输入信息
Go语言内置的 flag 包实现了对命令行参数的解析。在运行时输入对应的参数,经过 flag 包的解析后即可获取命令行的数据
示例代码:
package main
import (
"flag"
"fmt"
)
var mode = flag.String("mode", "", "process mode")
/*
通过 flag.String,定义一个 mode 变量,这个变量的类型是 *string。后面 3 个参数分别如下:
参数名称:在命令行输入参数时,使用这个名称。
参数值的默认值:与 flag 所使用的函数创建变量类型对应,String 对应字符串、Int 对应整型、Bool 对应布尔型等。
参数说明:使用 -help 时,会出现在说明中。
*/
func main() {
//解析命令行参数
flag.Parse() //解析命令行参数,并将结果写入到变量 mode 中。
//输出命令行参数
fmt.Println(*mode) //打印 mode 指针所指向的变量。
}
/*
之前已经使用 flag.String 注册了一个名为 mode 的命令行参数,flag 底层知道怎么解析命令行,并且将值赋给 mode*string 指针
在 Parse 调用完毕后,无须从 flag 获取值,而是通过自己注册的这个 mode 指针获取到最终的值。
*/
底层调用的图解: