聊聊Golang语言Method接收者使用值类型和指针类型

01介绍

在 Golang 语言中,function 的参数和 method 的接收者都可以选择使用值传递和指针传递(“引用传递”),需要注意的是,其中指针传递是传递的指针值的副本,而不是指针指向的数据的副本。也就是说 Golang 语言和 C 系的所有语言相同,一切传递都是值传递。本文我们主要介绍 method 的接收者怎么选择使用值类型和指针类型。

成都创新互联公司服务项目包括高明网站建设、高明网站制作、高明网页制作以及高明网络营销策划等。多年来,我们专注于互联网行业,利用自身积累的技术优势、行业经验、深度合作伙伴关系等,向广大中小型企业、政府机构等提供互联网行业的解决方案,高明网站推广取得了明显的社会效益与经济效益。目前,我们服务的客户以成都为中心已经辐射到高明省份的部分城市,未来相信会继续扩大服务区域并继续获得客户的支持与信任!

02method 接收者的类型选择

在使用关键字 type 定义的类型上定义 method,method 的接收者也可以作为 method 的参数,类似于 function 的参数,所以 method 的接收者和 function 参数一样,我们也需要考虑选择使用值类型和指针类型。

关于这个问题,我们通常会从两方面去考虑,一是如果该 method 需要修改接收者,那么接收者必须使用指针类型;二是如果接收者占用的内存大小较大,出于性能考虑,我们也会选择使用指针类型的接收者。

除此之外,我们还需考虑一致性。也就是说,如果该类型的某些 method 必须使用指针类型的接收者,其他 method 也应该使用指针类型的接收者。因此无论如何使用该类型,它的方法集都是一致的。

最后,如果接收者是基本类型,切片和小结构体,他们的值类型的内存占用较低,并且易读。所以,该情况下除非 method 的语义需要必须使用指针类型的接收者,否则,我们可以选择使用值类型的接收者。

 
 
 
 
  1. type User struct {
  2.  name string
  3. }
  4. func (u User) SetNameValueType(str string) {
  5.  fmt.Printf("SetNameValueType() pointer:%p\n", &u) // SetNameValueType() pointer:0xc000096240
  6.  u.name = str
  7. }
  8. func (u *User) SetNamePointerType(str string) {
  9.  fmt.Printf("SetNamePointerType() pointer:%p\n", u) // SetNamePointerType() pointer:0xc000096220
  10.  u.name = str
  11. }
  12. func main () {
  13.  user1 := &User{}
  14.  fmt.Printf("pointer:%p\n", user1) // pointer:0xc000096220
  15.  fmt.Println(user1) // &{}
  16.  user1.SetNameValueType("lucy")
  17.  fmt.Println(user1) // &{}
  18.  user1.SetNamePointerType("lily")
  19.  fmt.Println(user1) // &{lily}
  20. }

阅读上面这段代码,我们可以发现值类型的接收者,调用方拷贝了副本;指针类型的接收者,调用方未拷贝副本。

03复合类型

map 和 slice 值类似于指针:它们是包含指向底层 map 或 slice 数据的指针的描述符。复制 map 或 slice 值不会复制它指向的数据。需要注意的是,如果超过 slice 的容量,运行时会重新分配一个新内存地址。

map 源码:

 
 
 
 
  1. type hmap struct {
  2.  count     int // # live cells == size of map.  Must be first (used by len() builtin)
  3.  flags     uint8
  4.  B         uint8  // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
  5.  noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
  6.  hash0     uint32 // hash seed
  7.  buckets    unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
  8.  oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
  9.  nevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated)
  10.  extra *mapextra // optional fields
  11. }

slice 源码:

 
 
 
 
  1. type slice struct {
  2.  array unsafe.Pointer
  3.  len   int
  4.  cap   int
  5. }

示例代码:

 
 
 
 
  1. func main () {
  2.  user1 := &User{}
  3.  fmt.Printf("pointer:%p\n", user1) // pointer:0xc000096220
  4.  fmt.Println(user1) // &{}
  5.  user1.SetNameValueType("lucy")
  6.  fmt.Println(user1) // &{}
  7.  user1.SetNamePointerType("lily")
  8.  fmt.Println(user1) // &{lily}
  9.  // m := make(map[int]int)
  10.  m := map[int]int{}
  11.  fmt.Printf("map pointer:%p\n", m) // map pointer:0xc000100180
  12.  m[0] = 1
  13.  fmt.Printf("map pointer:%p\n", m) // map pointer:0xc000100180
  14.  m[1] = 2
  15.  s := make([]int, 0, 1)
  16.  fmt.Printf("slice pointer:%p\n", s) // slice pointer:0xc00001c0a0
  17.  s = append(s, 1)
  18.  fmt.Printf("slice pointer:%p\n", s) // slice pointer:0xc00001c0a0
  19.  s = append(s, 2)
  20.  fmt.Printf("slice pointer:%p\n", s) // slice pointer:0xc00001c0b0
  21. }

阅读上面这段代码,我们可以发现 map 类型未分配新内存地址,使用 append 函数向 slice 中追加元素,当元素个数未超出其容量之前,slice 也未分配新内存地址。

关于接口类型,复制接口值将复制存储在接口值中的对象。如果接口值持有一个结构体,则复制接口值会复制该结构体。如果接口值持有指针,则复制接口值会复制指针,但不会复制它指向的数据。

04值类型怎么避免拷贝副本

阅读到这里,读者朋友可能会简单认为使用值类型会拷贝副本,使用指针类型不会拷贝副本。实际上,我们可以通过优化代码,在不改变语义的前提下,实现使用值类型也不会拷贝副本。

示例代码:

 
 
 
 
  1. type User struct {
  2.  name string
  3. }
  4. func (u User) SetNameValueType(str string) {
  5.  fmt.Printf("SetNameValueType() pointer:%p\n", &u) // SetNameValueType() pointer:0xc000096240
  6.  u.name = str
  7. }
  8. func (u User) ValueSetName(str string) User {
  9.  u.name = str
  10.  return u
  11. }
  12. func main () {
  13.  user2 := &User{}
  14.  fmt.Printf("user2 pointer:%p\n", user2) // user2 pointer:0xc000010290
  15.  user2.SetNameValueType("tom") // SetNameValueType() pointer:0xc0000102a0
  16.  user3 := &User{}
  17.  fmt.Printf("user3 pointer:%p\n", user3) // user3 pointer:0xc0000102b0
  18.  user3.ValueSetName("bob")
  19.  fmt.Printf("pointer:%p\n", user3) // pointer:0xc0000102b0
  20. }

阅读上面这段代码,我们发现 User 的 SetNameValueType 方法和 ValueSetName 方法,二者都是值传递,但是 SetNameValueType 方法会拷贝副本,ValueSetName 方法不会拷贝副本。原因是我们给 ValueSetName 方法定义了一个 User 类型的返回值,从而避免了 ValueSetName 方法拷贝副本。

05总结

本文我们主要介绍了 method 的接收者使用值传递和指针传递的区别,并且讲述了选择使用值传递和指针传递需要考虑的决定因素,也指出了复合类型与值类型的区别。最后,使用一个简单示例演示了通过优化代码,在不改变语义的前提下,怎么实现使用值类型也不会拷贝副本。

本文转载自微信公众号「Golang语言开发栈」,可以通过以下二维码关注。转载本文请联系Golang语言开发栈公众号。

分享题目:聊聊Golang语言Method接收者使用值类型和指针类型
文章起源:http://www.mswzjz.cn/qtweb/news3/505853.html

攀枝花网站建设、攀枝花网站运维推广公司-贝锐智能,是专注品牌与效果的网络营销公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 贝锐智能