指针
指针是什么?
指针是一个变量,指针保存了一个变量的地址 , 所以说指针指向变量 , 可以通过指针找到变量。
指针-内存的储存方式
func main() {
var x int
var y bool
px := &x
py := &y
var z string
var pz *string
fmt.Printf("x变量的值:%v,px指针的值:%v\n", x, px)
fmt.Printf("x变量的内存地址:%v,px指针解引用的值:%v\n", &x, *px)
fmt.Printf("pz指针的值:%v,pz指针的地址%v", pz, &pz)
fmt.Println(unsafe.Sizeof(x))
fmt.Println(unsafe.Sizeof(y))
fmt.Println(unsafe.Sizeof(z))
fmt.Println(unsafe.Sizeof(px))
fmt.Println(unsafe.Sizeof(py))
fmt.Println(unsafe.Sizeof(pz))
}
指针的占用内存?指针和变量值的关系?
在Go语言中,指针无论是什么类型指针占用的内存都是64位八个字节。
变量的地址赋值给指针,指针的值就是变量的地址,变量的值就是指针解引用。
指针-声明赋值
func main() {
var ps *string
s := "张三"
ps = &s
fmt.Println(ps, &s)
}
指针如何声明赋值?
在Go语言中,在变量前使用&取地址符返回没错地址。指针类型可以基于任何类型,在前面加*符号。
指针-初始化
func main() {
pi := new(int)
fmt.Println(pi, *pi)
}
指针如何初始化?
在Go语言中,指针初始化可以把变量地址赋值给变量、使用new初始化指针 , 会连带创建指针指向的变量 , 指向的变量是默认值
func main() {
var s string
ps := &s
fmt.Printf("%T\n", ps)
fmt.Println(ps, s)
}
指针-解引用
func main() {
s := "张三"
ps := &s
fmt.Println(*ps)
}
func main() {
var ps *string
fmt.Println(ps == nil)
fmt.Println(*ps)
}
/*
panic: runtime error: invalid memory address or nil pointer dereference
*/
指针如何解引用?
在Go语言中,指针的解引用操作对指针取*号获取指针指向的变量。
如果对未初始化的指针变量进行解引用操作会panic
指针-赋值新变量
func main() {
a := "张三"
b := "李四"
pa := &a
pb := pa
fmt.Printf("pa的地址:%v,pa指向变量a的地址:%v\n", &pa, pa)
fmt.Printf("pb的地址:%v,pb指向变量b的地址:%v\n", &pb, pb)
pb = &b
fmt.Printf("pa的地址:%v,pa指向变量a的地址:%v\n", &pa, pa)
fmt.Printf("pb的地址:%v,pb指向变量b的地址:%v\n", &pb, pb)
*pb = "王五"
}
指针-比较
func main() {
var ps *string
fmt.Println(ps == nil)
s := "张三"
s1 := "李四"
ps = &s
ps1 := &s1
fmt.Println(ps1 == ps)
ps2 := &s
fmt.Println(ps2 == ps)
}
指针的比较?
在Go语言中,未初始的指针等与nil,如果两个指针指向相同的变量则是true,指向不同则为false
指针-函数传递
/*
指针-函数传递
*/
func f(fpi *int) {
fmt.Printf("fpi指针的地址:%v\n", &fpi)
fmt.Printf("fpi指针的指向变量的地址(指针的值):%v\n", fpi)
fmt.Printf("fpi指针的指向变量的值:%v\n", *fpi)
*fpi = 1
fmt.Printf("修改fpi指针的指向变量的值:%v\n", *fpi)
}
func main() {
pi := new(int)
fmt.Printf("pi指针的地址:%v\n", &pi)
fmt.Printf("pi指针的指向变量的地址(指针的值):%v\n", pi)
fmt.Printf("pi指针的指向变量的值:%v\n", *pi)
f(pi)
}
/*
pi指针的地址:0xc00014c018
pi指针的指向变量的地址(指针的值):0xc000128058
pi指针的指向变量的值:0
fpi指针的地址:0xc00014c028
fpi指针的指向变量的地址(指针的值):0xc000128058
fpi指针的指向变量的值:0
修改fpi指针的指向变量的值:1
*/
func f(fpi *int) {
fmt.Printf("fpi指针的地址:%v\n", &fpi)
fmt.Printf("fpi指针的指向变量的地址(指针的值):%v\n", fpi)
fmt.Printf("fpi指针的指向变量的值:%v\n", *fpi)
i := 10
fpi = &i
fmt.Printf("修改后fpi指针的地址:%v\n", &fpi)
fmt.Printf("修改后fpi指针的指向变量的地址(指针的值):%v\n", fpi)
fmt.Printf("修改后fpi指针的指向变量的值:%v\n", *fpi)
}
func main() {
pi := new(int)
fmt.Printf("pi指针的地址:%v\n", &pi)
fmt.Printf("pi指针的指向变量的地址(指针的值):%v\n", pi)
fmt.Printf("pi指针的指向变量的值:%v\n", *pi)
f(pi)
fmt.Println(pi, *pi)
}
/*
fpi指针的地址:0xc000006038
fpi指针的指向变量的地址(指针的值):0xc0000160a8
fpi指针的指向变量的值:0
修改后fpi指针的地址:0xc000006038
修改后fpi指针的指向变量的地址(指针的值):0xc0000160d0
修改后fpi指针的指向变量的值:10
0xc0000160a8 0
*/
指针在函数中传递的现象?
在Go语言中,Go语言是值传递语言,传递函数的值是副本,
对于基本类型、结构体、数组等非指针类型,调用的函数不会修改原始值。
如果指针、切片、字典传递给给函数。如果对参数修改,会影响传入的变量,如果对参数中心赋值,不会影响,如果传入的参数中是nil,不会影响。
尽管当指针给传递到了函数 , 函数获得是指针的副本(也就是说,指针的地址不一致)
在函数f中的i参数fpi是指针变量pi的副本 , 所以 给fpi 赋值了一个新变量的地址 fpi指向的变量改变了, 不和pi指向的变量一样了,不会影响到 pi 指针变量
指针-创建结构体指针实例
type User struct {
Name string
Age int
}
func main() {
u := &User{}
u1 := new(User)
fmt.Println(u, u1)
}
结构体指针如何创建实例?
在Go语言中,可以在结构体前使用&创建指针实例 , 但无法在基本字面量前使用&
里面包含指针类型基础类型
type User struct {
Name string
Age *int
}
func main() {
u := &User{}
fmt.Println(u.Age) // 输出:nil
u.Age = 3
/*
u.Age = 3 编译错误
cannot use 3 (untyped int constant) as *int value in assignment
不能在赋值中使用3(未类型化int常量)作为*int值
*/
u.Age = &3
/*
u.Age = &3 编译错误
不能对常量取地址
*/
}
type User struct {
Name string
Age *int
}
func main() {
u := &User{}
fmt.Println(u.Age) // 输出:nil
var i = 10
u.Age = &i
fmt.Println(u.Age, *u.Age)
}
type User struct {
Name string
Age *int
}
func main() {
u := &User{}
fmt.Println(u.Age) // 输出:nil
u.Age = new(int)
*u.Age = 10
fmt.Println(u.Age, *u.Age)
}
使用辅助函数
type User struct {
Name *string
Age int
}
func stingP(s string) *string {
return &s
}
func main() {
u := User{
Name: stingP("张三"),
Age: 0,
}
fmt.Println(u)
}
指针-作为第二选择
type User struct {
Name string
Age int
}
//不推荐
func MakeUser(user *User) error {
user.Name = "张三"
user.Age = 5
return nil
}
//推荐
func MakeUser1() (User, error) {
return User{
Name: "张三",
Age: 5,
}, nil
}
函数修改一个结构体最佳实践?
在Go语言中,函数修改一个结构体实例,不如直接返回结构体实例。
指针传递修改会给垃圾回收增加额外工作,因为会复制变量的指针,函数返回后会回收复制的指针,不如让函数实例化返回结构
指针-指针传递优化性能
函数中传递类型的最佳实践?
在Go语言中,函数中传递兆字节或更大数据,尽量考虑是用指针传递。
大多数情况下,使用指针和数值在函数中传递性能差异不大,但是如果在函数之间传递兆字节或者更大的数据,考虑使用指针,即使数据不会改变。
指针-空值和零值
// getStudentByID 模拟从数据库或其他数据源中根据学生 ID 获取学生信息的函数
func getStudentByID(id int) *string {
// 模拟数据库查询
a := "张三"
b := "李四"
if id == 1 {
return &a
} else if id == 2 {
return &b
} else {
return nil
}
}
func main() {
// 尝试获取学生信息
studentName := getStudentByID(1)
fmt.Println(*studentName)
studentName1 := getStudentByID(2)
fmt.Println(*studentName1)
if nil == getStudentByID(3) {
fmt.Println("没找到学生")
}
}
// getStudentByID 模拟从数据库或其他数据源中根据学生 ID 获取学生信息的函数
func getStudentByID(id int) (string,bool) {
// 模拟数据库查询
if id == 1 {
return "张三", true
} else if id == 2 {
return "", true
} else {
return "", false
}
}
func main() {
// 尝试获取学生信息
studentName, found := getStudentByID(3)
// 判断是否找到学生
if found {
fmt.Println("Student found:", studentName)
} else {
fmt.Println("Student not found")
}
}
如何判断零值和空值的最佳实践?
在Go语言中,尽量避免返回nil指针的情
况,建议使用映射中介绍的逗号,ok模式。nil解引用会导致panic。
指针-映射和切片的区别
映射切片和指针的关系?
在Go语言中,映射切片被实现一个指向结构的指针,将它们赋值新变量和函数中传递意味这赋值了一个新指针。
映射在函数中使用最佳实践?
在Go语言中,尤其是在公共API中应该避免映射作为输入参数和返回值。
切片在函数中使用最佳实践?
在Go语言中,把切片传递到函数中修改,不能改变它的长度和容量,但可以改变其内容。
指针-切片用作缓冲区
如何从数据源获取数据的最佳实践?
在Go语言惯例中,从数据源获取数据中,应该一次性创建一个字节切片作为其缓冲区,而不是每次从数据源中读取数据都进行一次内存分配,减少不必要的内存分配。
指针-优化垃圾回收
垃圾是什么?垃圾回收是什么?如何优化垃圾回收?
在Go语言中,垃圾是不在有指针指向的数据。垃圾回收自动检测未使用的内存,并将其回收。
栈是什么?栈的工作原理是什么?栈上储存内容的条件?
在Go语言中,栈是一个连续的内存块,执行线程中的每个函数调用都共享一个栈。
在栈上储存内容,必须在编译中知道多大。基础类型、数组、结构、指针类型
指针指向数据,必须是一个局部变量,且数据大小在编译是一致的,不能从函数中返回。
栈、堆储存什么数据?栈、堆数据什么时候释放?
在Go语言中,栈储存函数的参数和局部变量的值
堆储存。栈由编译器在函数退出后自动分配和销毁,堆一般由程序员手动释放。
在Go语言中,虽然变量申请在堆空间上,但是他有自动回收垃圾的功能,所以这些堆地址空间也无需手动回收,系统会在需要释放的时刻自动进行垃圾回收。
内存逃逸是什么情况出现的?
在Go语言中,如果指针变量被返回,当函数退出时,指针指向的数据不在有效,数据不能储存在栈中,指针指向的数据在栈上逃逸,然后把编译器将数据储存在堆中
栈数据逃到堆上
// 返回一个指向局部变量的指针
func createPerson(name string, age int) *Person {
// 在函数内部创建一个 Person 对象,并返回其指针
p := Person{Name: name, Age: age}
return &p // 返回局部变量的指针,可能逃逸到堆上
}
func main() {
// 调用 createPerson 函数,返回一个指向堆上数据的指针
personPtr := createPerson("Alice", 25)
// 在 main 函数中,personPtr 指向的数据可能已经逃逸到堆上
fmt.Println(personPtr.Name, personPtr.Age)
}
// 定义一个 Person 结构体
type Person struct {
Name string
Age int
}
分配到堆上
var p *int
func f() {
var i int
i = 1
p = &i
fmt.Println(p)
}
func main() {
fmt.Println(p)
f()
fmt.Println(p)
}
分配到栈上
fun f(){
p:=new(int)
*p=1
}