published on
tags: Go tech

Go 语言中的 Slice

按之前的笔记中许诺的,这回专门来讨论一下Go语言中的Slice。

构造

首先,我们可以看看这个东西是怎么实现的: 用C++来表示,它应该就是这样一个struct:

template <typename T>
struct Slice {
    T* buf;
    size_t size;
    size_t capacity; 
}

其中buf指向了一个数组,size是当前Slice的大小,capacity是指向的数组有多长。

相应的就有两个重要的函数lencap,分别返回sizecapacity

如果你了解C++的标准库,会不会觉得这个结构很眼熟?对了,C++中的vector就是这么实现的。当然在Go中,得益于gc机制,Slice就可以放开手做更多的事情了。

首先看Slice的构造, 两种方式:

  1. 为buf新申请一段空间: make([]T, length) or make([]T, length, capacity)
  2. buf指向已有的数组: a[low:high]

vector不同的是,可以Slice指向已有的数组,而vector不能。

代替vector

我们先看最基础的功能,使用Slice来代替C++中的vector。

此时所需要的两个最核心的功能就是push_backpop_back,使用Slice的时候可以使用这两个函数的强化版本:

push_back对应到append,在Go里面可以使用append添加一个或者多个元素到Slice的结尾。由于从文档中看不出来会不会使用capacity*2的策略,所以如有需要可以自己写一个push_back如果需要扩展capacity就乘2好了。

pop_back就很简单了,直接用Slices功能,从现有的里面切出来一段新的即可。

由于上面说到自己实现append,那么就需要一个copy函数,把旧的Slice中的内容复制到新的中,这个函数语言中也已经提供了~

当然上面说了这么多,因为对于这些函数来说,可能会通过指向一个新的Slice来完成扩展的操作,所以不会和vector的接口做的完全一样。不过为了使用方便,我们当然可以把它做得完全一样,只要自己写一个struct将Slice包一下就好了。

表示已有数组区间

有时候,我们并不是需要动态改变Slice的大小,只不过是想要表示一个现有数组的一段。你有没有在用C/C++时在程序中出现过类似下面的函数签名:

void func(T* arr, int left, int right)
void func(T* arr, int size)
void func(T* begin, T* end)

现在好了,只要一个Slice,里面这些信息都包含了。看到那里面的beginend会不会让你想到很多的函数呢,是的,现在他们可以只需要一个参数了:)

少量用到数组区间的算法的例子:

  • 快速排序
  • 线段树

小结

这里简单的讨论了go语言中Slice的内部结构和一些使用场景。许多别的语言中要实现一个类似的玩意也不难,在没有gc的语言中如果要使用需要留意,用起来可以带来一些便利性。