前言:在 Golang 中函数之间传递变量时总是以值的方式传递的,无论是 int,string,bool,array 这样的内置类型(或者说原始的类型),还是 slice,channel,map 这样的引用类型,在函数间传递变量时,都是以值的方式传递,也就是说传递的都是值的副本。
在使用ioutil的ReadAll方法时查看了其内部实现如下,这让我很痛苦,不明白为什么要这样写。下面我们就来一探究竟。
func ReadAll(r Reader) ([]byte, error) {
b := make([]byte, 0, 512)
for {
if len(b) == cap(b) {
// Add more capacity (let append pick how much).
b = append(b, 0)[:len(b)]
}
n, err := r.Read(b[len(b):cap(b)])
b = b[:len(b)+n]
if err != nil {
if err == EOF {
err = nil
}
return b, err
}
}
}
讨论这个问题之前先看一下标准库中切片的内部结构
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
由切片的结构定义可知,切片的结构由三个信息组成:
- 指针Data,指向底层数组中切片指定的开始位置
- 长度Len,即切片的长度
- 容量Cap,也就是最大长度,即切片开始位置到数组的最后位置的长度
最开始我想将一个文件内容读取到内存,我想到的操作是这样的
func f1() {
f, _ := os.Open("F:\\hello.txt")
b := make([]byte, 0, 512)
read, err := f.Read(b)
if err != nil {
return
}
fmt.Println(read)//打印0
fmt.Println(b)//打印[]
}
为什么会出现这种情况呢?我们点开f.Read方法看到 Read reads up to len(b) bytes from the File.
读取len(b) 长度byte的数据到b,那现在len(b)=0就一个字节都不会读取了。这时候你就会明白为什么上面标准库中ReadAll参数为什么要用b[len(b):cap(b)]
(对切片的任何操作都会复制一个切片b[len(b):cap(b)]
操作对b切片结构体进行了复制,产生了新的切片并且新切片的len=cap=512,这也就解释了为什么数据能读入b[len(b):cap(b)]
了)。观察下面代码:
func f2() {
f, _ := os.Open("F:\\hello.txt")
b := make([]byte, 0, 512)
//
c := b[len(b):cap(b)]
fmt.Println(len(c))//512
fmt.Println(cap(c))//512
read, err := f.Read(c)
if err != nil {
return
}
fmt.Println(read)//512
fmt.Println(b)//[]
fmt.Println(b[:cap(b)])//[...] 打印出了数据
fmt.Println(c)//[...]打印出了和上面相同的数据
}
这就奇怪了不是说是引用传递吗,为什么现在c作为参数传进Read方法后值被改变了。这就需要看切片的内部结构了,切片本身并不承载数据。它只是一个有三个属性的结构体,传递时,就会把这个结构体的三个属性复制一份进行传递,而且复制后头指针指向相同的地址。另外还有一个重要的概念:对切片的任何操作都会复制一个切片(并不是复制切片数据,二十切片的结构体,他们指向的内存区域还是一样的),也就是复制上面说的三个属性。读取切片类型数据的另一个重要属性就是len,len是多少那就会读多少数据,虽然由b衍生出的其他结构体他们的头指针的地址是一样的,后面的数据也是一样的,但是如果你的len是0那头指针后面的数据一个byte也不属于你,也就读不出来,你有多少的len那么头指针后就有多少数据属于你。
这也就解释了为什么b始终是空的了,虽然你的头指针后面有数据被填充了,但是你的len始终是0那么数据都与你无关也是就是空了。c切片的头指针与b相同但是len和cap不同都是512。所以就能读取出头指针后512bytes的数据了。
另外还要讨论切片的扩容机制,当切片的len=cap时使用append方法会触发内置的扩容机制cap会扩大。我就有些疑问为什么是b = append(b, 0)[:len(b)]
,因为使用append函数仅仅是为了触发扩容,添加进去的0是无意义的,原来len=512现在就变成了513,再往后填充数据就会导致与原数据不一致的问题,因此要把添加的byte去除。文章来源:https://www.toymoban.com/news/detail-463022.html
func f3() {
f, _ := os.Open("F:\\hello.txt")
b := make([]byte, 0, 512)
for {
if len(b) == cap(b) {
// Add more capacity (let append pick how much).
//b = append(b, 0)[:len(b)]
b = append(b, 0)
}
n, err := f.Read(b[len(b):cap(b)])
b = b[:len(b)+n]
if err != nil {
if err == io.EOF {
err = nil
}
break
}
}
fmt.Println(string(b))
}
可以看到使用b = append(b, 0)
会导致部分数据失真。
文章来源地址https://www.toymoban.com/news/detail-463022.html
到了这里,关于关于切片参数传递的问题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!