切片与数组的关系
Go入门(7)——切片
Go语言中切片是对数组的抽象。Go数组的长度不可改变,在特定场景中可能显得比较笨重。Go提供了一种灵活的、功能强悍的内置类型切片,可理解为动态数组,其长度不固定且可以追加元素。追加元素可能导致切片容量增大。
切片是对数组中一个连续片段的引用(这里所谓的数组被称为“相关数组”,通常是匿名的),所以切片是一个引用类型(类似于C/C++的数组类型、Python的list类型)。这个切片可以是整个数组,或者是由起始和终止索引标识的一些项形成的子集(终止索引标识的项并不包括在切片内)。切片提供了一个相关数组的动态窗口。
定义
可以声明未指定大小的数组来定义切片,切片不需要说明长度。
var identifier []type
或者使用make()函数来创建切片:
var slice1 []type = make([]type, len) // 也可以简写成: slice1 := make([]type, len)
也可以指定容量,其中capacity为可选参数:
make([]T, length, capacity)
将切片传递给函数
如果有函数需要对数组进行操作,那这个数组基本上要设定为切片。如下计算数组元素和:
func sum(a []int) int { s := 0 for i:=0; i<len(a); i++ { s += a[i] } return s } func main() { var arr = [5]int{0,1,2,3,4} sum(arr[:]) }
用make()创建切片
当相关数组还没有定义时,可以使用make()函数来创建一个切片,同时创建好相关数组:
var slice1 []type = make([]type, len)
也可以简写为:
slice1 := make([]type, len)
这里的len是数组的长度,也是slice的初始长度。
若定义s2 := make([]int, 10),则有cap(s2) == len(s2) == 10。
make接受2个参数:元素的类型以及切片的元素个数。
如果需要创建一个slice1,但是不占用整个数组,而是占用len个项,则应:
slice1 := make([]type, len, cap)
make的使用方式即:
func make([]T, len, cap)
其中cap为可选参数。
以下两种方式可以生成相同的切片:
make([]int, 50, 100) new([100]int)[0:50]
以下代码描述了使用make产生的切片的内存结构:
// make_slice.go package main import "fmt" func main() { var slice1 []int = make([]int, 10) // 加载数组/切片 for i:=0; i<len(slice1); i++ { slice1[i] = i * 5 } // 打印切片 for i:=0; i<len(slice1); i++ { fmt.Printf("Slice at %d is %d \n", i, slice1[i]) } fmt.Printf("\nThe length of slice1 is %d\n", len(slice1)) fmt.Printf("The capacity of slice1 is %d\n", cap(slice1)) }
输出为:
Slice at 0 is 0 Slice at 1 is 5 Slice at 2 is 10 Slice at 3 is 15 Slice at 4 is 20 Slice at 5 is 25 Slice at 6 is 30 Slice at 7 is 35 Slice at 8 is 40 Slice at 9 is 45 The length of slice1 is 10 The capacity of slice1 is 10
另外,因为字符串是纯粹不可变的字节数组,所以也可以被切分为切片。
new()和make()的区别
二者都在堆上分配内存,但是行为不同,适用类型不同。
new(T) | make(T) |
---|---|
为每个新的类型T分配内存,初始化为0,并且返回类型为*T的内存地址。这种方法返回一个指向类型为T,值为0的地址的指针,适用于值类型(数组、结构体),相当于&T{}。 | 返回一个类型为T的初始值,只适用于3中内建的引用类型:切片(slice)、集合(map)、通道(channel)。 |
即:new函数分配内存,make函数初始化。
var p *[]int = new ([]int) // *p == nil; with len and cap 0 p := new([]int)
p := make([]int, 0)意即切片已被初始化,但指向数组为空。
多维切片
和数组一样,切片通常也是一维的,但是可以通过组合成为高维。通过切片的分片(或者切片的数组),长度可以任意动态变化。内层的切片必须单独分配(通过make函数)。
bytes包
类型[]byte的切片十分常见,Go中有个专门的bytes包来解决这种类型的操作问题。
bytes包和字符串包十分类似,且包含一个十分有用的类型Buffer。
import "bytes" type Buffer struct { // ... }
这是一个长度可变的bytes的Buffer,提供Read和Write方法,因为读写长度未知的bytes最好使用Buffer。
Buffer可以使用如下的方式定义:
var buffer bytes.Buffer // 或者使用new获得一个指针 var r *bytes.Buffer = new(bytes.Buffer) // 或者通过函数创建Buffer并用buf初始化 // NewBuffer最好在从buf读取时使用 func NewBuffer(buf []byte) *Buffer
通过Buffer串联字符串
类似于Java的StringBuilder类。
下面的代码中,创建一个Buffer,通过buffer.WriteString(s)方法将字符串s追加到最后面,最后通过buffer.String()方法转换为string:
var buffer bytes.Buffer for { if s, ok := getNextString(); ok { // getNextString()方法 buffer.WriteString(s) } else { break } } fmt.Print(buffer.String(), "\n")
切片和数组的区别
- 切片不是数组,但是切片底层指向数组。
- 切片本身长度不确定,不可以参与比较;但是数组可以。
- 切片是变长数组的替代方案,可以关联到指向的底层数组的局部或者全部。
- 切片是引用传递(传递指针地址);数组是值传递(拷贝值)。
- 切片可以直接创建,引用其它切片或数组创建。
- 如果多个切片指向相同的底层数组,其中一个值的修改会影响所有的切片。
Append/Copy切片
数组长度是固定的;切片是动态的。使用append可以将新元素追加到切片上。append的定义是:
func append (s[]T, x ... T) []T
append可以直接在切片尾部追加元素,也可以将一个切片追加到另一个切片尾部。
package main import "fmt" func main() { var numbers []int printSlice(numbers) // 允许追加空切片 numbers = append(numbers, 0) printSlice(numbers) // 向切片添加一个元素 numbers = append(numbers, 1) printSlice(numbers) // 同时添加多个元素 numbers = append(numbers, 2, 3, 4) printSlice(numbers) // 创建切片numbers1,是之前切片的两倍容量 numbers1 := make([]int, len(numbers), (cap(numbers))*2) // 拷贝numbers的内容到numbers1 copy(numbers1, numbers) printSlice(numbers1) } func printSlice(x []int){ fmt.Printf("len=%d cap=%d slice=%v \n", len(x), cap(x), x) }
输出为:
len=0 cap=0 slice=[] len=1 cap=1 slice=[0] len=2 cap=2 slice=[0 1] len=5 cap=6 slice=[0 1 2 3 4] len=5 cap=12 slice=[0 1 2 3 4]
有疑问加站长微信联系(非本文作者)