关于切片参数传递的问题
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于切片参数传递的问题相关的知识,希望对你有一定的参考价值。
前言:在 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去除。
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)
会导致部分数据失真。
golang中不定参数与数组切片的区别
package main import "fmt" func main() { myfunc1(88, 42, 12, 56) //传递不定数量的参数 myfunc2([]int{88, 42, 12, 56}) //传递一个数组切片 } func myfunc1(args ...int) { //接受不定数量的参数,这些参数的类型全部是int for _, arg := range args { fmt.Println(arg) } } func myfunc2(args []int) { //传递一个数组切片 for _, arg := range args { fmt.Println(arg) } }
参考资料:
《Go语言编程》 2.5.3 不定参数
以上是关于关于切片参数传递的问题的主要内容,如果未能解决你的问题,请参考以下文章