欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 时评 > go语言中的指针详解

go语言中的指针详解

2024/11/29 22:49:20 来源:https://blog.csdn.net/2302_79993788/article/details/142530831  浏览:    关键词:go语言中的指针详解

1.概念

指针是什么?

Go语言中,如果我们要存储一个整数,我们会使用整型(int),如果要存储一个字符串,我们会使用string类型,如何我们想存储一个内存地址呢,要用什么数据类型呢?

答案是指针

指针就是一个存储了其他数据项地址的数据项,或者说指针是一个存储了其他变量地址的变量。

在代码中,我们会经常存储或者读取各种数据,这些数据的数据类型可能是字符串、数字类型或结构体等,数据存在内存某个指定位置上,每个内存位置有自己的地址,指针就是专门用存储变量地址的变量,如下图所示:

从上面的示意图中可以看出一个指针类型的变量本身也有自己的内存地址。 

2.指针类型

  • 指针地址(&a)
  • 指针取值(*&a)
  • 指针类型(&a) —> *int 改变数据传指针

可以总结为:在编程语言中,指针是一种数据类型,用来存储一个内存地址该地址指向存储在该内存中的对象。这个对象可以是字符串、整数、函数或者你自定义的结构体。 

小技巧:你也可以简单地把指针理解为内存地址。 

小提示:内存地址通常为 16 进制的数字表示,比如 0x45b876。  

  • 表示一个指向字符串类型的指针类型:
*string
  • 表示一个指向整型64位的指针:
*int64
  • 要指向一个复杂的数据结构,比如要存储一个如下所定义的结构体的指针:
type Student struct{ID    stringName  stringGrade string
}

指向该结构的指针类型如下:

*Student

3.认识指针地址和指针类型

每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。Go 语言中使用&作符放在变量前面对变量进行“取地址”操作。

格式如下:

ptr := &v    // v的类型为T

其中 v 代表被取地址的变量,被取地址的 v 使用 ptr 变量进行接收,ptr 的类型就为*T,称做 T 的指针类型。*代表指针。

指针实际用法,通过下面的例子了解:

package main
import ("fmt"
)
func main() {var cat int = 1var str string = "banana"fmt.Printf("%p %p", &cat, &str)
}
运行结果:
0xc042052088 0xc0420461b0

 从指针获取指针指向的值

package mainimport "fmt"func main() {var house ="malibu Point 10800 90265"//对字符串取地址,ptr类型为*stringptr := &house//打印ptr的类型fmt.Printf("ptr type :%T\n",ptr)//打印ptr的指针地址fmt.Printf("address :%p\n",ptr)//怼指针进行取值操作value := *ptr//取值后的类型fmt.Printf("value type:%T\n",value)//指针取值后就是指向变量的值fmt.Printf("value :%s\n",value)}

运行结果:

ptr type :*string
address :0xc0000200a0
value type:string
value :malibu Point 10800 90265

4.指针类型变量

package main  // 定义包名为mainimport "fmt"  // 导入fmt包,用于格式化输入输出// 定义一个Student结构体,包含ID,Name和Grade三个字段
type Student struct {ID    stringName  stringGrade string
}func main() {  // 程序的入口函数var n int = 2  // 定义一个整型变量n并初始化为2p := &n  // 定义一个整型指针p,并让它指向n*p = 10  // 通过指针p修改它指向的变量n的值为10fmt.Println(*p) // 打印p指向的值,即变量n的值,输出为10 // 修改了这里,使用*p来解引用指针// 创建一个指向Student结构体的指针,并初始化它的字段stu := &Student{ID: "001", Name: "test", Grade: "A"} fmt.Println(stu) // 打印stu指向的结构体的值,输出为Student的详细信息 // 修改了这里,直接打印结构体指针stu
}

 &*操作符的关系如下图所示:

5.指针的零值 

任何指针变量的零值都是nil:

package mainimport "fmt"type Student struct {ID    stringName  stringGrade string
}func main() {var p *intfmt.Println(p) //nilvar s *stringfmt.Println(s) //nilvar stu *Studentfmt.Println(stu) //nil
}

6.slice和指针

slice也是引用类型,因此当把slice作为参数传给函数时,对slice变量的修改会生效:

package mainimport "fmt"func ChangeFirstItem(lgB []string) {lgB[0] = "C"
}func main() {lgA := []string{"C++", "JavaScript", "Python", "PHP"}fmt.Println("修改前:", lgA)ChangeFirstItem(lgA)fmt.Println("修改后:", lgA)
}

运行结果:

修改前: [C++ JavaScript Python PHP]
修改后: [C JavaScript Python PHP]

可以看出,在函数内对slice类型变量的修改生效了。

接下来的对同一个slice,我们往slice里添加元素:

package mainimport "fmt"func AddItem(lgB []string) {lgB = append(lgB, "Rust")fmt.Println("Add函数内:", lgB)
}func main() {lgA := []string{"C++", "JavaScript", "Python", "PHP"}fmt.Println("添加前:", lgA)AddItem(lgA)fmt.Println("添加后:", lgA)
}

我们看到,上面程序运行过程中,slice变量lgB在函数AddItem被修改了,但外面的slice变量lgA却没有变化。

为什么同样把slice变量作为函数的参数,ChangeFirstItem函数可以对slice变量lgB修改后,lgA也被修改了,而AddItem函数就不可以呢?

其实,当我们把一个slice变量lgA作为实参传给函数的形参时,实参与形参就是两个不同的slice变量(发生了复制),只不过这两个slice变量引用了同一个底层数组,如下图所示:

调用ChangeFirstItem函数只是修改了slice的第一个元素,也就是修改了底层数组的第一个元素,函数执行后,两个slice变量仍然是引用同一个底层数组。

而调用AddItem函数时,此时会向底层数组的尾部插入一个元素,但由于底层数组已没有容量了,Go会复制一个新的底层数组,把容量扩充一倍,因此执行AddItem函数后,AddItem的形参指向的是一个新的底层数组,而实参仍然指向旧的底层数组,如下图所示:

 7.struct与指针

指向结构体的指针变量,不需要在前面星号*就可以直接访问指向的结构体: 

package mainimport "fmt"type User struct {ID   intName string
}func main() {var n = 10p := &nfmt.Println(n)*p = 20// p=20 是错误的fmt.Println(n)u := &User{ID: 1, Name: "A"}fmt.Println(u.Name)//Au.Name = "B"fmt.Println(u.Name) //B 
}

上面示例中,可以看到,访问指向整型的指针变量时,需要在前面加上*

而访问指向结构体的指针变量则不需要加上*

上面的语句相当于:

u.Name = "B"
(*u).Name = "B"

8.方法的指针接收器 

过方法对自身的值或属性,那么其接收器必须是指针类型的:
go 代码解读复制代码package mainimport "fmt"type Student struct {ID   stringAge  uint8Name string
}func (s *Student) Rename(newName string) {s.Name = newName
}func (s Student) Rename2(newName string) {s.Name = newName
}func main() {s := Student{ID: "001", Age: 18, Name: "小张"}s.Rename("小明")fmt.Println(s.Name) //小明s.Rename2("小华")fmt.Println(s.Name)//小明
}
  1. Rename 方法的接收器是 *Student 类型,即 Student 的指针。这意味着在 Rename 方法内部,可以通过接收器指针直接修改 Student 实例的属性。因此,在 main 函数中调用 s.Rename("小明") 后,s.Name 的值被成功修改为 "小明"。

  2. Rename2 方法的接收器是 Student 类型,即非指针类型。这意味着在 Rename2 方法内部,接收器 sStudent 实例的一个副本,而不是原始实例的引用。在 Rename2 方法内部对 s 的任何修改都不会影响到原始实例。因此,在 main 函数中调用 s.Rename2("小华") 后,s.Name 的值仍然是 "小明",因为 Rename2 方法修改的是 s 的副本,而不是原始的 Student 实例。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com