文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2024-12-03 04:36

关注

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} 

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

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.  
  8.  buckets    unsafe.Pointer // array of 2^B Buckets. may be nil if count==0. 
  9.  oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing 
  10.  nevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated) 
  11.  
  12.  extra *mapextra // optional fields 

slice 源码:

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

示例代码:

  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.  
  10.  // m := make(map[int]int
  11.  m := map[int]int{} 
  12.  fmt.Printf("map pointer:%p\n", m) // map pointer:0xc000100180 
  13.  m[0] = 1 
  14.  fmt.Printf("map pointer:%p\n", m) // map pointer:0xc000100180 
  15.  m[1] = 2 
  16.  
  17.  s := make([]int, 0, 1) 
  18.  fmt.Printf("slice pointer:%p\n", s) // slice pointer:0xc00001c0a0 
  19.  s = append(s, 1) 
  20.  fmt.Printf("slice pointer:%p\n", s) // slice pointer:0xc00001c0a0 
  21.  s = append(s, 2) 
  22.  fmt.Printf("slice pointer:%p\n", s) // slice pointer:0xc00001c0b0 

阅读上面这段代码,我们可以发现 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.  
  17.  user3 := &User{} 
  18.  fmt.Printf("user3 pointer:%p\n", user3) // user3 pointer:0xc0000102b0 
  19.  user3.ValueSetName("bob"
  20.  fmt.Printf("pointer:%p\n", user3) // pointer:0xc0000102b0 

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

05总结

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

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

 

来源:Golang语言开发栈内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯