Go指针剖析~
思维导图
指针的概念
在 Go 中,指针是一种存储变量内存地址的变量。在指针类型变量中存储的是另一个变量的地址,而不是该变量的值本身。
Go指针类型允许对这个指针类型指向的数据进行修改,传递数据可以通过传递指针来实现,无须拷贝数据,并且指针类型不能够进行偏移和运算,因此Go中的指针类型变量拥有指针高效访问的特点,且不会发生指针偏移,避免了非法修改指针从而导致非法内存访问和指针溢出等问题,提高代码安全性。同时,垃圾回收
也比较容易对不会发生偏移的指针进行检索和回收。
当声明并初始化一个变量时:
var num int = 10
上述声明并初始化时,在内存中开辟了一块空间,该空间存放着数值10,此时该空间有一个唯一的地址来进行标识,此时指向这个地址的变量称为指针变量
(指针)。
当一个指针变量被定义后没有指向到任何变量
时,它的默认值为 nil
。
func main() {
var num *int
fmt.Println(num)
}
// 执行结果
<nil>
指针地址
Go程序中,每个变量在运行时都拥有一个在内存分配的地址来表示变量在内存中的位置。
Go语言中使用&
字符放在变量前面对变量进行“取地址”操作。
取变量指针的语法如下:
ptr := &v // v的类型为T
v
: 代表被取地址的变量,类型为T
ptr
: 用于接收地址的变量,ptr的类型就为*T
,其中T为指针的类型。*
代表指针。
func main() {
num := 10
address := &num
fmt.Printf("nums: %d ptr: %p\n", num, &num)
fmt.Printf("address: %p type: %T\n", address, address)
fmt.Println(&address)
}
// 执行结果
nums: 10 ptr: 0xc0000aa058
address: 0xc0000aa058 type: *int
0xc0000ce018
指针类型
Go语言中的值类型(int、float、bool、string、array、struct)都有对应的指针类型,如:*int
、*int64
、*string
等。
package main
import "fmt"
type Person struct {
Name string
Age int
}
func main() {
var x int = 255
var ptrInt *int = &x
fmt.Printf("int pointer: %T\n", ptrInt)
var str string = "hello"
var ptrStr *string = &str
fmt.Printf("string pointer: %T\n", ptrStr)
var b bool = true
var ptrBool *bool = &b
fmt.Printf("bool pointer: %T\n", ptrBool)
var f float64 = 3.14
var ptrFloat64 *float64 = &f
fmt.Printf("float64 pointer: %T\n", ptrFloat64)
var arr [3]int = [3]int{1, 2, 3}
var ptrArr *[3]int = &arr
fmt.Printf("[3]int pointer: %T\n", ptrArr)
var p Person = Person{"Tom", 20}
var ptrP *Person = &p
fmt.Printf("Person pointer: %T\n", ptrP)
}
// 执行结果
int pointer: *int
string pointer: *string
bool pointer: *bool
float64 pointer: *float64
[3]int pointer: *[3]int
Person pointer: *main.Person
指针取值
使用&
操作符对变量进行取地址操作后会得到该变量的指针,对指针使用*
操作,可以获得该指针所指向的值,即指针取值
。
func main() {
num := 256
addr := &num
fmt.Printf("type of addr: %T\n", addr)
value := *addr
fmt.Printf("type of value: %T\n", value)
fmt.Printf("value: %v\n", value)
}
// 执行结果
type of addr: *int
type of value: int
value: 256
通过取地址操作符&
获取到变量的地址,通过取值操作符*
可以获取到地址指向的值。
指针使用
使用指针在函数或者方法传参时:
- 使用指针,可以在函数内部修改实参的值;
- 使用指针,可以避免参数副本的内存消耗;
在 Go 函数中,函数参数的传递方式是值传递
(Pass by Value),会将参数值(变量值)进行拷贝并传入到函数中,即传递的是参数值的副本。
在函数内部,对参数副本(形参)的修改并不会影响到函数外的实参值。
func main() {
num := 255
fmt.Printf("modify before main() num address: %p\n", &num)
fmt.Printf("modify before main() num value: %d\n", num)
modify(num)
fmt.Printf("modify after main() num address: %p\n", &num)
fmt.Printf("modify after main() num value: %d\n", num)
}
func modify(num int) {
num = 255 * 2
fmt.Printf("modify() num address: %p\n", &num)
fmt.Printf("modify() num value: %d\n", num)
}
// 执行结果
modify before main() num address: 0xc00001c098
modify before main() num value: 255
modify() num address: 0xc00001c0c0
modify() num value: 510
modify after main() num address: 0xc00001c098
modify after main() num value: 255
若使用指针传递,则可以实现函数内修改实参的值。
func main() {
num := 255
fmt.Printf("modify before main() num address: %p\n", &num)
fmt.Printf("modify before main() num value: %d\n", num)
modify(&num)
fmt.Printf("modify after main() num address: %p\n", &num)
fmt.Printf("modify after main() num value: %d\n", num)
}
func modify(ptr *int) {
*ptr = 255 * 2
fmt.Printf("modify() ptr address: %p\n", ptr)
fmt.Printf("modify() ptr address: %d\n", *ptr)
}
// 执行结果
modify before main() num address: 0xc00001c098
modify before main() num value: 255
modify() ptr address: 0xc00001c098
modify() ptr address: 510
modify after main() num address: 0xc00001c098
modify after main() num value: 510
上述代码中,将变量num
通过取地址操作符&
将num
的地址传入函数modify
,该函数的参数为一个指针形参ptr
,传入函数时,会将变量num
地址拷贝一份到函数modify
的ptr
指针变量中,此时modify
的指针变量ptr
指向main
中的num
变量,在modify
函数中,*ptr = 255 * 2
修改了指针变量指向的值,进而对函数外的变量(原始参数)进行修改。
另外,当函数需要对较大的数据结构进行操作时,使用指针类型参数可以避免因实参拷贝到函数的形参而导致内存消耗。
new与make
new
new
是Go中的一个内置函数,函数签名如下:
func new(Type) *Type
上述函数签名中,参数Type
表示类型,new函数
接收一个类型参数。返回值为*Type
,表示返回一个指向该类型的内存地址的指针,即Type
类型的指针。
func main() {
num := new(int)
b := new(bool)
fmt.Printf("%T\n", num) // *int
fmt.Printf("%T\n", b) // *bool
fmt.Println(*num) // 0
fmt.Println(*b) // false
}
上述代码中,使用new
函数获得传入参数中的类型的指针,并且该指针对应的值为该类型的初始值。与var num *int
的不同之处在于使用var num *int
只是声明了指针变量num
但并没有初始化,指针作为引用类型需要初始化后才会拥有内存空间,才可以给它赋值。而使用内置的new函数
返回的指针变量则进行了初始化,可以正常对其赋值。
make
make
函数同样由于内存分配,不同于new
,make
函数用于slice
、map
、channel
类型的内存创建,并且返回的类型就是其本身,不是指针类型,因为slice
、map
、channel
类型就是引用类型,无须返回其类型的指针。
make函数签名
func make(t Type, size ...IntegerType) Type
在go中,使用slice
、map
以及channel
的时候,都需要使用make
进行初始化,然后才可以对它们进行操作。
func main() {
var m map[string]int
m = make(map[string]int, 10)
m["学习"] = 100
fmt.Println(m) // map[学习:100]
}
new与make的区别
- 二者都用于内存分配;
make
只用于slice
、map
、channel
的初始化,并且这三者本身是引用类型,因此返回这三者本身;new
用于类型的内存分配,返回指定类型的指针,并在内存初始化对应的类型零值;
转载自:https://juejin.cn/post/7303789777789419561