Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Arrays, slices (and strings): The mechanics of 'append' #13

Open
wisecsj opened this issue Feb 2, 2019 · 1 comment
Open

Arrays, slices (and strings): The mechanics of 'append' #13

wisecsj opened this issue Feb 2, 2019 · 1 comment

Comments

@wisecsj
Copy link
Owner

wisecsj commented Feb 2, 2019

Go blog里的文章:https://blog.golang.org/slices

下面是部分翻译(实际上更是笔记吧...)

@wisecsj
Copy link
Owner Author

wisecsj commented Feb 2, 2019

Arrays, slices (and strings): The mechanics of 'append'

Introduction

编程语言中最为广泛的一个概念就是array。Arrays似乎看起来很简单,但当将它加入到一门语言中时却会有诸多问题:

  • 固定大小还是可变大小
  • size是否是type的一个属性
  • 多维数组如何实现
  • 空数组的意义

.......

Arrays

数组是GO语言中一个非常重要的组成部分,但是像建筑的基础一样会被可见的其他组件所掩盖。所以在讨论更强大的slice之前,我们得先简短的介绍下array。

......

数组有它自己的作用——比如,它们是变换矩阵的很好的表示——但是在GO语言中,它们最主要的作用为slice维护数据存储

Slices: The slice header

切片是描述某数组(与切片变量本身分开存储)一个连续部分的数据结构。一个切片不是数组,它描述了数组的一部分。

对某数组进行切片得到slice
slice := buffer[100:150]
可简单认为slice内部的数据结构长这样:

type sliceHeader struct {
    Length        int
    ZerothElement *byte
}

slice := sliceHeader{
    Length:        50,
    ZerothElement: &buffer[100],
}

对slice进行slice

slice2 := slice[5:10] 新生成的slice2内部指向的底层数组与原来的slice指向的是同一个

reslice

对某个slice进行切片,并把结果赋值给它本身

Passing slices to functions

记住在GO里,Slice是一个结构体(数据结构),所以传参时,如果你需要改变实参,那么你得传 *Slice

Capacity

type sliceHeader struct {
    Length        int
    Capacity      int
    ZerothElement *byte
}

Capacity字段记录了所引用数组的大小:它意味着Length所能达到的最大值

Make

我们能不能往Slice里不断添加元素,甚至超过它的capacity呢?并不能,但你可以曲线救国。通过在内存分配一个更大容量的数组,然后把原切片的数据复制过去,最后修改SliceHeader数据结构里对应的字段即可。

一种方案是自己new一个数组,然后手动对它创建切片。但使用make更简单,它会自动帮我们完成这两步,all at once。

示例代码:

    slice := make([]int, 10, 15)
    fmt.Printf("len: %d, cap: %d\n", len(slice), cap(slice))
    newSlice := make([]int, len(slice), 2*cap(slice))
    for i := range slice {
        newSlice[i] = slice[i]
    }
    slice = newSlice
    fmt.Printf("len: %d, cap: %d\n", len(slice), cap(slice))

对于make slice的shorthand形式:
gophers := make([]Gopher, 10) gophers的length和capacity都为10

Copy

在之前,我们double了切片的capacity,然后手动写了个循环逐一把数据复制到新的切片。Go里提供了一个内置函数 copy来帮我们完成这些操作,返回复制元素的个数

    newSlice := make([]int, len(slice), 2*cap(slice))
    copy(newSlice, slice)

并且,copy在两个切片参数重叠时也能正确工作.下面的代码展示了如何使用Copy在一个切片中间插入新的元素:

// Insert inserts the value into the slice at the specified index,
// which must be in range.
// The slice must have room for the new element.
func Insert(slice []int, index, value int) []int {
    // Grow the slice by one element.
    slice = slice[0 : len(slice)+1]
    // Use copy to move the upper part of the slice out of the way and open a hole.
    copy(slice[index+1:], slice[index:])
    // Store the new value.
    slice[index] = value
    // Return the result.
    return slice
}

Append: An example

之前我们自己写了个Extend函数用来对Slice进行单个元素的添加,那是有bug的,因为当超出slice capacity的时候会报错。现在我们尝试修复那个bug,利用copy和make:

func Extend(slice []int, element int) []int {
    n := len(slice)
    if n == cap(slice) {
        // Slice is full; must grow.
        // We double its size and add 1, so if the size is zero we still grow.
        newSlice := make([]int, len(slice), 2*len(slice)+1)
        copy(newSlice, slice)
        slice = newSlice
    }
    slice = slice[0 : n+1]
    slice[n] = element
    return slice
}

当发现容量不够时,直接分配一个新的更大的数组,然后利用copy进行切片复制。

Append 也可以用于添加切片,使用...记号,append(slice,slice2...)。

Append: The built-in function

之前的Append是我们自己实现的一个例子,接下来要讲的是GO内置的append函数。它不仅仅适用于特定的某一切片类型。

GO的一个缺点是所有的泛型操作都需要通过运行时提供。将来某天,这一点可能会改变吧。

Nil

当我们声明一个切片时: var s []int ,等同于 s := []int(nil) 。切片的内部数据结构如下:
sliceHeader{ Length: 0, Capacity: 0, ZerothElement: nil, }

因为它指向的数组指针是nil,所以不能向一个nil切片添加元素

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant