前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go语言的引用类型

Go语言的引用类型

作者头像
mazhen
发布2023-11-24 14:04:21
1700
发布2023-11-24 14:04:21
举报
文章被收录于专栏:mazhen.techmazhen.tech

Go中的引用类型不是指针,而是对指针的包装,在它的内部通过指针引用底层数据结构。每一种引用类型也包含一些其他的field,用来管理底层的数据结构。

看一个例子比较直观:

代码语言:javascript
复制
s := []string{"a","b", "c"}
fmt.Printf("%#v \n", (*reflect.SliceHeader)(unsafe.Pointer(&s)))

简单解释一下这段代码。先初始化一个slice,然后使用unsafe.Pointer(&s)slice的指针转换为通用指针PointerPointer是可以代表任何数据类型的指针。最后把Pointer强制转换为*reflect.SliceHeaderSliceHeader代表的是slice运行时数据结构,定义如下:

代码语言:javascript
复制
type SliceHeader struct {
 Data uintptr
 Len  int
 Cap  int
}

可以看到,SliceHeader内部有一个用来指向底层数组的指针Data,另外还有两个属性LenCap用来保存slice的内部状态。

上面的代码运行结果如下:

&reflect.SliceHeader{Data:0xc420078180, Len:3, Cap:3}

slice可以自动扩容,当底层数组容量不够时,会自动创建一个新的数组替换。让我们做个实验:

代码语言:javascript
复制
s := []string{"a","b", "c"}
fmt.Printf("%#v \n", s)
fmt.Printf("%#v \n", (*reflect.SliceHeader)(unsafe.Pointer(&s)))
fmt.Println()

s = append(s, "d")
fmt.Printf("%#v \n", s)
fmt.Printf("%#v \n", (*reflect.SliceHeader)(unsafe.Pointer(&s)))
fmt.Println()

s = append(s, "e")
fmt.Printf("%#v \n", s)
fmt.Printf("%#v \n", (*reflect.SliceHeader)(unsafe.Pointer(&s)))
fmt.Println()

运行结果如下:

对于初始化容量为3的slice,在向这个slice append 新元素时,底层会创建一个容量翻倍的新数组,并将原先的内容复制过来,再将新元素append到最后。我们可以看到这个slice内部保存底层数组的指针在第一次append后,指向了新的地址。当再向它append新元素时,由于底层数组还有空间,内部指针保持不变,只是更新Len属性为5。

在Go中进行函数调用时,参数都是按值传递的。对于引用类型也是按值传递,会复制引用本身,但不会复制引用指向的底层数据结构。还是看代码:

代码语言:javascript
复制
func foo(s []string) {
 fmt.Println("======= func foo =======")
 s = append(s, "f")
 fmt.Printf("%#v \n", s)
 fmt.Printf("%#v \n", (*reflect.SliceHeader)(unsafe.Pointer(&s)))
 fmt.Printf("&s: %p \n", &s)
 fmt.Println("========================\n")
}

func main() {

   ......
   
 s = append(s, "e")
 fmt.Printf("%#v \n", s)
 fmt.Printf("%#v \n", (*reflect.SliceHeader)(unsafe.Pointer(&s)))
 fmt.Printf("&s: %p \n", &s)
 fmt.Println()

 foo(s)

 fmt.Printf("%#v \n", s)
 fmt.Printf("%#v \n", (*reflect.SliceHeader)(unsafe.Pointer(&s)))
 fmt.Printf("&s: %p \n", &s)
}

运行结果为:

在函数调用时,传递给函数的slice进行了复制,函数的参数是一个新的slice,但slice内部指针指向的底层数组还是同一个。

完整的示例代码在https://play.golang.org/p/qwwSuskLfCa,可以在Playground中直接运行。

Go语言的引用类型有slice, map, channel, interfacefunction。技术上,string也是引用类型:

代码语言:javascript
复制
type StringHeader struct {
        Data uintptr
        Len  int
}

有时候为了性能优化,可以利用[]bytestring头部结构的“部分相同”,以非安全的指针类型转换来实现类型变更,避免底层数组的复制。例如 TiDB 中就使用了这个技巧

代码语言:javascript
复制
// String converts slice to string without copy.
// Use at your own risk.
func String(b []byte) (s string) {
 if len(b) == 0 {
  return ""
 }
 pbytes := (*reflect.SliceHeader)(unsafe.Pointer(&b))
 pstring := (*reflect.StringHeader)(unsafe.Pointer(&s))
 pstring.Data = pbytes.Data
 pstring.Len = pbytes.Len
 return
}

// Slice converts string to slice without copy.
// Use at your own risk.
func Slice(s string) (b []byte) {
 pbytes := (*reflect.SliceHeader)(unsafe.Pointer(&b))
 pstring := (*reflect.StringHeader)(unsafe.Pointer(&s))
 pbytes.Data = pstring.Data
 pbytes.Len = pstring.Len
 pbytes.Cap = pstring.Len
 return
}

上面两个函数实现了[]bytestring的互相转换,不需要底层数组的copy。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018-03-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档