Effective Go 数据(三)

159 阅读2分钟

这是我参与11月更文挑战的第26天,活动详情查看:2021最后一次更文挑战

切片

切片是数组的封装,为实现数据序列类型,提供更通用、更强大、更方便的接口。 除了具有显式维度的项(例如转换矩阵),Go 中的大多数数组编程都是使用切片而不是简单数组完成的。

切片保存对底层数组的引用,如果将一个切片分配给另一个切片,则两者都引用同一个数组。 如果一个函数接受一个切片参数,它对切片元素所做的更改将对调用者可见,类似于传递一个指向底层数组的指针。 因此,Read 函数可以接受切片参数而不是指针和计数; 切片内的长度设置了读取数据量的上限。 下面是os包中File类型的Read方法的签名:

func (file *File) Read(buf []byte) (n int, err error)

该方法返回读取的字节数和错误值(如果有)。 要读入较大缓冲区 buf 的前 32 个字节,请对缓冲区进行切片(此处用作动词)。

n, err := f.Read(buf[0:32])

这种切片的方法常用且高效。若不谈效率,以下片段同样能读取该缓冲区的前 32 个字节。

    var n int
    var err error
    for i := 0; i < 32; i++ {
        nbytes, e := f.Read(buf[i:i+1])  // Read one byte.
        if nbytes == 0 || e != nil {
            err = e
            break
        }
        n += nbytes
    }

只要不超过底层数组的限制;切片的长度可以改变。 只需将其分配给自身的一部分即可。 切片的容量,可通过内置函数 cap 访问,会返回切片可能采用的最大长度。 这是一个将数据附加到切片的函数。 如果数据超过容量,则重新分配切片。 返回结果切片。 当切片是nil的时候使用 len 和 cap 也是合法的,并返回 0。

func Append(slice, data[]byte) []byte {
    l := len(slice)
    if l + len(data) > cap(slice) {  // 重新分配
        // 为了后面的增长,需分配两份。
        newSlice := make([]byte, (l+len(data))*2)
        // copy 函数是预声明的,且可用于任何切片类型。
        copy(newSlice, slice)
        slice = newSlice
    }
    slice = slice[0:l+len(data)]
    for i, c := range data {
        slice[l+i] = c
    }
    return slice
}

必须最后返回切片,因为尽管 Append 可以修改 slice 中元素,但切片自己(其运行时数据结构包含指针、长度和容量)是通过值传递的。

向切片追加东西的想法非常有用,因此有专门的内建函数 append。 理解该函数的设计,我们还需要一些额外的信息,我们稍后再介绍它。