Array和Slice

Array和Slice

因为个人原因,博客好久没更新了,今天更新一篇关于Array和Slice的。接下来我将会写一些简单算法的东西,其实我一直想写一些跟网络有关的东西,目前来看是没有机会了,只能安排到明年的时间了。如果内容有不正确之处,欢迎斧正,谢谢!

Array

Array可以看作一个有长度的变量,每个单元都可以存放一个元素,例如:
var buffer [100]int

在内存中buffer类似如下:
buffer: int int int ... 100 times ... int int int

Array中的长度也是类型的一部分,例如[200]int和[100]int是不同类型的:

testArray(arr [100]int) {
  // ...
}

var a1 [100]int
var a2 [200]int

testArray(a1)  // OK
testArray(a2)  // Wrong

Array可以用来表示矩阵,但是通常是作为slice的存储基础

Slice Header

Slice可以看作是Array的部分片段,而不是Array(A Slice is not an an array,a slice is piece of an array)

slice := array[10:20]

slice也可以对slice进行“切片”:

s2 := slice[5:10]  //新定义一个变量s2,是对slice片段的描述
slice := slice[5:10]  //对自己切片并将变量付给自身

可以将Slice看成一个包含长度和起始地址的数据结构:

type sliceHeader struct {
  Length    int
  ZeroElement *int  //不一定是*int,元素是什么类型就是什么类型的指针
}

例如上面的slice变量可以看成如下:

slice := sliceHeader {
  Length: 10,
  ZeroElement: &array[10],
}

Slice作为函数参数传递

slice是一个包含长度和一个指针的结构体的值,而不是一个指向一个结构体的指针,函数参数中的slice在传进去的时候是一个struct值的复制值,修改slice中的元素值就是修改其所持有Array的元素值,例如:

func AddSlice(slice []int) {  
  for i := range slice {  
    slice[i]++  
  }  
} 
func main() {  
  slice := []int{1,2,3,4,5,6,7,8,9,10}  
  fmt.Println("before:",slice)  
  AddSlice(slice)  
  fmt.Println("after:",slice) 
}

pirint:

before: [1,2,3,4,5,6,7,8,9,10]
after: [2,3,4,5,6,7,8,9,10,11]

函数传递进去时候slice只是一个复制值,可以通过如下例子看出:

func SubSlice(slice []int) []int {
  slice := slice[0:len(slice)-1]
  return slice
}

func main() {
  slice := []int{1,2,3,4,5,6,7,8,9,10}  
  fmt.Println("before: len(slice) =",slice)  
  newSlice := SubSlice(slice)  
  fmt.Println("after: len(slice) =",slice)
  fmt.Println("after: len(newSlice) =",newSlice) 
}

print:

before: len(slice) = 10
after: len(slice) = 10
after: len(newSlice) = 9

指向Slice的指针

如果想修改slice header的内容,可以传递一个指向slice的指针,例如:

func PtrSubSlice(slicePtr *[]int) {
  newSlice := *slicePtr
  *slicePtr = newSlice[0:len(newSlice)-1]
}
func main() {
  fmt.Println("before: len(slice) =", len(slice))
  PtrSubSlice(&slice)
  fmt.Println("after: len(slice) =", len(slice))
}

print:

before: len(slice) = 10
after: len(slice) = 9

作为方法的接收者,可以修改这个slice:

type path []byte

func (p *path) TruncateAtFinalSlash() {
  i := bytes.LastIndex(*p,[]byte("/"))
  if i >= 0 {
    *p = (*p)[0:i]
  }
}

//ASCII letters to Upper
func (p path) ToUpper() {
  for i,b := rang p {
    if 'a' <= b && b <= 'z' {
      p[i] = b + 'A' - 'a'
    }
  }
}

func main() {
  pathName := path("/usr/bin/tso")
  pathName.TruncateAtFinalSlash()
  fmt.Printf("%s\n",pathName)
  pathName.ToUpper()
  fmt.Printf("%s\n",pathName)
}

print:

/usr/bin
/USR/BIN

容量(Capacity)

如何扩展slice的长度呢?

func Extend(slice []int, element int) []int {
  n := len(slice)
  slice = slice[0:n+1]
  slice[n] = element
  return slice  //why return the modified slice?
}

func main() {
  var array [10]int
  slice := array[0:0] // slice:[]
  for i:=0; i<20; i++ {
    slice = Extend(slice,i)
    fmt.Println(slice)
  }
}

这里运行的时候会报错,只会打印出前面10个数字,因为超过10的话已经超过了slice底层的array的长度,所以slice header还包含它的容量,表示其能表示的最大长度:

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

当执行了slice := array[0:0]的时候,slice header如下:

slice := sliceHeader {
  Length: 0,
  Capacity: 10,
  ZeroElement: &array[0],
}

我们可以通过内置的cap函数获得slice的容量:

if cap(slice) == len(slice) {
  fmt.Println("slice is full")
}

Make

上面的例子中我们可以看到在要扩张slice的时候,这个时候我们就可以用内置的make函数来扩展我们的slice:

slice := make([]int, 10, 15)
fmt.Printf("len:%d, cap:%d\n",len(slice),cap(slice))

print:

len:10, cap:15

我们可以让我们的slice扩展两倍:

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))

pirnt:

len:10, cap:15
len:10, cap:30

make也可以只传递两个值,这样它的len和cap就是一样的了:

slice := make([]int, 10)

slice的len和cap都是10

make的时候是创建一个新的数组作为slice的存储,下面例子可以看出:

s1 := make([]int,5,10)
s2 := make([]int,len(s2),2*cap(s1))
fmt.Printf("s1.addr:%v,s2.addr:%v\n",&s2[1],&s8[0])

print:

s2.addr:0x11701540,s8.addr:0x1170155c

Copy

在扩展的时候,我们可能需要复制一个slice到另一个slice:

func Insert(slice []int, index,value int) []int {
  slice := slice[0:len(slice)+1]
  copy(slice[index+1:],slice[index:])
  slice[index] = value
  return slice
}

slice[i:]和slice[i:len(slice)]一样,slice[:]和slice一样

Append

自己创建一个Append来扩展slice:

func Append(slice []int, elements ...int) []int {
  n := len(slice)
  total := n + len(elements)  //elements看以看作是一个slice,可以其计算长度
  if total > cap(slice) {
    newSlice := make([]int,total,total*3/2)
copy(newSlice,slice)
    slice = newSlice 
  }
  slcie = slice[:total]
  copy(slice[n:],elements)
  return slice
}

func main() {
  s1 := []int{0,1,2,3,4}
  s2 := []int{5,6,7}
  fmt.Println(s1)
  s1 = Append(s1,s2...)  //'...'是必须的
  fmt.Println(s1)
}

print:

[0,1,2,3,4]
[0,1,2,3,4,5,6,7]

内建的append

上面是自己写的一个Append的函数,其实go中实现了一个内建的append,而且这个append对于任何类型的slice都可以append:

// Create a couple of starter slices.
slice = []int{1, 2, 3}
slice2 := []int{55, 66, 77}
fmt.Println("Start slice: ", slice)
fmt.Println("Start slice2:", slice2)
fmt.Printf("slice[0]:%p,slice2[0]:%p\n",&slice[0],&slice2[0])

// Add an item to a slice.
slice = append(slice, 4)
fmt.Println("Add one item:", slice)
fmt.Printf("slice[0]:%p\n",&slice[0])

// Add one slice to another.
slice = append(slice, slice2...)
fmt.Println("Add one slice:", slice)
fmt.Printf("slice[0]:%p\n",&slice[0])

// Make a copy of a slice (of int).
slice3 := append([]int(nil), slice...)
fmt.Println("Copy a slice:", slice3)
fmt.Printf("slice3[0]:%p\n",&slice3[0])

// Copy a slice to the end of itself.
fmt.Println("Before append to self:", slice)
slice = append(slice, slice...)
fmt.Println("After append to self:", slice)
fmt.Printf("slice[0]:%p\n",&slice[0])

通过地址打印,我们会看到每次append之后返回的slice的地址都是不一样的,所以内建的append是会重新生成一个底层的array来持有数据的。

什么是nil

go中如果一个slice是nil的,那么他的长度和容量都是0,并且它的起始地址是nil的:

sliceHeader {
  Length: 0,
  Capacity: 0,
  ZeroElement: nil,
}

或者

sliceHeader{}

需要注意的是它的起始地址为nil,如果一个slice是这样的:

slice = array[0:0]

那么这个slice不是nil的,因为它有起始地址,nil的slice是没有地方给它存储元素的并且是不可以增长,但是nil的slice是可以append的,因为append会重新分配空间

字符串(Strings)

字符串可以看作是一个只读的字节(bytes)slice:

slash := '/usr/bin'[0]  //value is '/'
usr := '/usr/bin'[0:4] //value is '/usr'

我们还可以直接将byte slice强制转换成字符串

str := string(slice)

或者反过来:

slice := []byte(str)

当然,对于字符串还有很多,这只是一个简单的概述

最后,我们了解了slice和array的基本结构,尤其是slice的实现原理,对于我们使用slice是非常有益的,尤其是内建的copy和append函数


参考资料:http://blog.golang.org/go-slices-usage-and-internals

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值