- Go语言为开发人员提供了控制数据结构指针的能力,但并不能进行指针运算。
- Go允许开发人员控制特定集合的数据结构、分配数量、内存访问模式,对构建运行良好的系统非常重要。
- 指针对于性能的影响不言而喻,对系统编程、操作系统、网络应用,指针是不可或缺的一部分。
变量是用来存储数据的,变量的本质是给存储数据的内存地址起了一个好记得别名。指针也是变量,但它是一种特殊的变量,因为指针存储的数据并非普通的值,而是另一个变量的内存地址。
变量与指针Go语言中将指针分拆为两个概念
- 类型指针
- 类型指针允许对指定指针类型的数据进行修改
- 传递数据可直接使用指针,而无需拷贝数据。
- 类型指针不能进行偏移和运算
C语言中指针可进行运算,Go语言中不存在指针操作,进而避免内存溢出。
- 切片
- 切片是由指向起始元素的原始指针、元素数量和容量组成
- 切片比原始指针具备更加强大的特性,更加安全。
- 切片在发生越界时,运行时会报出宕机,并打印出堆栈,原始指针则只会崩溃。
Go语言的指针类型拥有指针高效访问的特定,又不会发生指针偏移,从而避免非法修改关键性数据的问题。同时,垃圾回收也比较容易对不会发生偏移的指针进行索引和回收。
指针
每个变量在运行时都会拥有一个内存地址,内存地址代表变量在内存中的具体位置,指针的值就是变量的内存地址。
基本数据类型中变量保存的是值,因此又称为值类型。指针类型属于引用类型,因为指针类型的变量保存的并非是一个值,而是一个内存地址,内存地址指向的空间保存的才是最终的值。
Golang中使用&
取地址符获取变量的内存地址,使用*
获取指针类型所指向的值。
声明指针
创建变量时会在内存中分配内存空间去存储变量的值,每个内存块都具有一个地址以表示当前位置,通常内存地址以16进制数表示。
var varname *vartype
vartype
表示指针类型,varname
表示指针变量,*
星号用于指定变量作为一个指针。
var ptr *int
fmt.Printf("ptr: value = %v, type = %T, addr = %v\n", ptr, ptr, &ptr)
ptr: value = <nil>, type = *int, addr = 0xc000006028
使用指针前需要定义指针变量,然后为指针变量赋值,进而才能访问指针变量中指向地址的值。
i := 10
fmt.Printf("i = %v, type = %T, addr = %p\n", i, i, &i)
i = 10, type = int, addr = 0xc0000aa058
p := &i
fmt.Printf("p = %v, type = %T, addr = %p, value = %v\n", p, p, &p, *p)
p = 0xc0000aa058, type = *int, addr = 0xc0000d4020, value = 10
每个变量都拥有内存地址,指针的值就是变量的内存地址。对变量使用&
取地址符后会获取变量对应的内存地址,对内存地址使用*
后可以获取该地址对应的值。&
和*
是一对互补的操作符,&
用于取出地址,*
则根据地址取出地址指向的值。
package main
import (
"fmt"
"reflect"
)
func main() {
id := 1
fmt.Printf("id = %d, addr = %p\n", id, &id)//id = 1, addr = 0xc0000100b0
addr := &id
println(addr)//0xc0000100b0
println(reflect.TypeOf(addr))//(0xf6e8e0,0xf2ea80)
val := *addr
println(val)//1
}
创建指针(new)
var p *int
fmt.Printf("p = %v, type = %T\n", p, p) //p = <nil>, type = *int
*p = 100
fmt.Println(*p) //panic: runtime error: invalid memory address or nil pointer dereference
Go语言通过new()
函数对指针类型创建一个指针,即是用new()
函数创建指针变量,new()
函数可以创建一个对应类型的指针,创建过程中会分配内存,被创建的指针指向默认值。
i := new(int)
fmt.Printf("i = %v, type = %T, addr = %p\n", i, i, &i)
i = 0xc0000140a0, type = *int, addr = 0xc000006028
变量是一种使用方便的占位符,用于引用计算机内存地址。变量存储的是一个值,值在内存中拥有一个地址,而指针则保存着变量的内存地址,通过内存地址可以获取变量的值。
Go语言提供的取地址符&
放到变量前即可获取变量的内存地址,通过*
符号可以将指针的值取出。
i := new(int)
*i = 100
fmt.Printf("i = %v, type = %T, addr = %p, value = %v\n", i, i, &i, *i)
i = 0xc000126058, type = *int, addr = 0xc000150018, value = 100
使用注意
- 所有的值类型都具有对应的指针类型,其形式为
*数据类型
。 - 值类型包括基本数据类型中
int
系列、float
系列、bool
、string
、数组、结构体struct
make
函数也可以用于内存分配,与new
函数不同的是,make
只用于slice
、map
、chan
的内存创建,其返回的类型是这三种类型本身,而非其指针类型。因为这三种类型是引用类型,因此没有必要返回指针。
func make(t Type, size ...IntegerType) Type
new
和make
之间的区别在于,new
用来给基本类型申请内存,返回的是指针类型。而make
只能给slice
、map
、chan
申请内存,且返回的是类型本身。
指针变量
指针变量就是保存变量内存地址的变量
- 指针变量通常会缩写为
ptr
- 指针变量指向任何值的内存地址,禁止将值直接赋给指针变量。
var i int = 10
var ptr *int = i//cannot use i (type int) as type *int in assignment
cannot use i (type int) as type *int in assignment
- 指针变量的类型必须和赋值变量的类型保持一致
var i int = 10
var ptr *float32 = &i// cannot use &i (type *int) as type *float32 in assignment
cannot use &i (type *int) as type *float32 in assignment
- 指针变量占用的字节大小与所指向的值的大小无关
- 指针变量指向的内存地址在32位机器上占用4个字节,在64位机器上占用8个字节。
var i int = 10 //基本类型
fmt.Printf("i = %v, addr = %p\n", i, &i) //i = 10, addr = 0xc000126058
var ptr *int = &i //ptr表示指针类型的变量,其类型是指向int类型的指针
fmt.Printf("ptr = %v, addr = %p, value = %v\n", ptr, &ptr, *ptr)
i = 10, addr = 0xc000126058
ptr = 0xc000126058, addr = 0xc000150020, value = 10
- 指针定义后若没有分配给任何变量则默认值为
nil
var p *int//nil
*p = 100
fmt.Println(*p)
//panic: runtime error: invalid memory address or nil pointer dereference
取地址符(&)
Go语言中变量前添加&
操作符(取地址符)来获取变量的内存地址,指针的值是带有0x
十六进制前缀的一组数据。
var v int = 100
fmt.Printf("v address is %p", &v)//v address is 0xc0000100b0
当使用&
取地址操作符对普通变量进行取地址操作时,会得到变量的指针。
var v int = 100
ptr := &v
fmt.Printf("pointer type is %T", ptr)//pointer type is *int
v
表示被取地址的变量,变量v
的地址使用变量ptr
接收,变量ptr
的类型为*T
,称为T
的指针类型,*
表示指针。
指针取值(*)
当使用&
取地址操作符获取变量的指针后,可对指针使用*
指针取值操作符获取对应的值。
var v int = 100
ptr := &v
fmt.Printf("pointer type is %T\n", ptr)//pointer type is *int
fmt.Printf("pointer address is %p\n", ptr)//pointer address is 0xc0000100b0
val := *ptr
fmt.Printf("pointer value type is %T\n", val)//pointer value type is int
fmt.Printf("pointer value is %d\n", val)//pointer value is 100
&
取地址操作符和*
取值操作符是一对互补操作符,&
取出地址,*
根据地址获取地址指向的值。
- 对变量进行取地址操作时使用
&
以获取变量的指针变量 - 指针变量的值是指针地址
- 对指针变量进行取值操作时使用
*
,以获取指针变量指向的原变量的值。
var i int = 10
var ptr *int = &i
*ptr = 100
var j int = 20
ptr = &j
*ptr = 200
fmt.Printf("i = %d, j = %d, *ptr = %d\n", i, j, *ptr)//i = 100, j = 200, *ptr = 200
例如:使用指针实现变量交换
package main
import "fmt"
func swap(x, y *int){
tmp := *x
*x = *y
*y = tmp
}
func main(){
x, y := 1, 2
swap(&x, &y)
fmt.Printf("x = %d, y = %d\n", x, y)//x = 2, y = 1
}
-
*
取值操作符作为右值时表示获取指针的值,作为左值时即放在赋值操作符左边时表示指针指向的变量。 -
*
取值操作符的根本作用是操作指针指向的变量,当操作在右值时获取指向变量的值,当操作在左侧时是将值设置给指向的变量。
例如:使用指针变量获取命令行的输入参数
Go语言内置的flag
包实现了对命令行参数的解析,使用flag.String
注册名为mode
的命令行参数,flag
底层会解析命令行并将值赋给mode *string
指针。解析完毕后直接通过注册的mode
指针获取到最终的值。
package main
import (
"flag"
"fmt"
)
//定义命令行参数:go run --mode = fast
var mode = flag.String("mode", "", "process mode")
func main(){
//解析命令行参数
flag.Parse()
//输出命令行参数
fmt.Printf(*mode)//fast
}
空指针
当指针被定义后没有分配到任何变量此时其值为nil
,nil
指针又称为空指针。nil
在概念上等同于其他编程语言中的null
、None
、nil
、NULL
,都是指代零值或空值。
网友评论