设为首页 加入收藏

TOP

细说Go语言切片(二)
2018-11-29 12:08:33 】 浏览:375
Tags:细说 语言 切片
建了一个长度为5的数组并且创建了一个相关切片。

我们可以分析一下,上图中的切片在内存中结构

切片在内存中的组织方式实际上是一个有 3 个域的结构体:指向相关数组的指针,切片长度以及切片容量。

注意 绝对不要用指针指向 slice。切片本身已经是一个引用类型,所以它本身就是一个指针!!

正因为这样,所以我们数组首地址需要取地址符(&),但是打印一个切片的地址的时候就不要加取地址符了。

func main() {

    arr := [...]int{1, 2, 3, 4 ,5}
    slice := []int{1, 2, 3, 4, 5}

    fmt.Printf("arr的首地址为: %p\n", &arr)
    fmt.Printf("slice的首地址为: %p\n", slice)

}

使用切片

使用切片,和使用数组一样,通过索引就可以获取切片对应元素的值,同样也可以修改对应元素的值。

slice := []int{1, 2, 3, 4, 5}
fmt.Println(slice[2]) //获取值
slice[2] = 10 //修改值
fmt.Println(slice[2]) //输出10

切片只能访问到其长度内的元素,访问超过长度外的元素,会导致运行时异常,与切片容量关联的元素只能用于切片增长。

我们前面讲了,切片算是一个动态数组,所以它可以按需增长,我们使用内置append函数即可。append函数可以为一个切片追加一个元素,至于如何增加、返回的是原切片还是一个新切片、长度和容量如何改变这些细节,append函数都会帮我们自动处理。

slice := []int{1, 2, 3, 4, 5}
newSlice := slice[1:3]
    
newSlice=append(newSlice,10)
fmt.Println(newSlice)
fmt.Println(slice)

//Output
[2 3 10]
[1 2 3 10 5]

例子中,通过append函数为新创建的切片newSlice,追加了一个元素10,我们发现打印的输出,原切片slice的第4个值也被改变了,变成了10。引起这种结果的原因是因为newSlice有可用的容量,不会创建新的切片来满足追加,所以直接在newSlice后追加了一个元素10,因为newSliceslice切片共用一个底层数组,所以切片slice的对应的元素值也被改变了。

这里newSlice新追加的第3个元素,其实对应的是slice的第4个元素,所以这里的追加其实是把底层数组的第4个元素修改为10,然后把newSlice长度调整为3。

如果切片的底层数组,没有足够的容量时,就会新建一个底层数组,把原来数组的值复制到新底层数组里,再追加新值,这时候就不会影响原来的底层数组了。

所以一般我们在创建新切片的时候,最好要让新切片的长度和容量一样,这样我们在追加操作的时候就会生成新的底层数组,和原有数组分离,就不会因为共用底层数组而引起奇怪问题,因为共用数组的时候修改内容,会影响多个切片。

append函数会智能的增长底层数组的容量,目前的算法是:容量小于1000个时,总是成倍的增长,一旦容量超过1000个,增长因子设为1.25,也就是说每次会增加25%的容量。

内置的append也是一个可变参数的函数,所以我们可以同时追加好几个值。

newSlice=append(newSlice,10,20,30)

此外,我们还可以通过...操作符,把一个切片追加到另一个切片里。

slice := []int{1, 2, 3, 4, 5}
newSlice := slice[1:2:3]

newSlice=append(newSlice,slice...)
fmt.Println(newSlice)
fmt.Println(slice)

迭代切片

切片是一个集合,我们可以使用 for range 循环来迭代它,打印其中的每个元素以及对应的索引。

    slice := []int{1, 2, 3, 4, 5}
    for i,v:=range slice{
        fmt.Printf("索引:%d,值:%d\n",i,v)
    }

如果我们不想要索引,可以使用_来忽略它,这是Go语言的用法,很多不需要的函数等返回值,都可以忽略。

    slice := []int{1, 2, 3, 4, 5}
    for _,v:=range slice{
        fmt.Printf("值:%d\n",v)
    }

这里需要说明的是range返回的是切片元素的复制,而不是元素的引用,所以这里我们修改v的值并不会改变slice切片里的值。

除了for range循环外,我们也可以使用传统的for循环,配合内置的len函数进行迭代。

    slice := []int{1, 2, 3, 4, 5}
    for i := 0; i < len(slice); i++ {
        fmt.Printf("值:%d\n", slice[i])
    }

切片重组

我们已经知道切片创建的时候通常比相关数组小,例如:

slice1 := make([]type, start_length, capacity)

其中 start_length 作为切片初始长度而 capacity 作为相关数组的长度。

这么做的好处是我们的切片在达到容量上限后可以扩容。改变切片长度的过程称之为切片重组 reslicing,做法如下: slice1 = slice1[0:end] ,其中 end 是新的末尾索引(即长度)。

将切片扩展 1 位可以这么做:

slice = slice[0:len(slice)+1]

切片可以反复扩展直到占据整个相关数组。

在函数间传递切片

其实无论是值类型还是引用类型,函数间的传递都是值传递,只不过值类型的数据传递是传递的是变量的值,而引用类型在函数间的传递的是变量的地址,然而这个地址其实也是一个值。

package main

import "fmt"

func main() {
    slice := []int{1, 2, 3, 4, 5}
    fmt.Printf("%p\n", slice)
    modify(slice)
    fmt.Println(slice)
}

func modify(slice []int) {
    fmt.Printf("%p\n", slice)
    slice[1] = 10
}

打印的输出如下:

0xc0000180c0
0xc0000180c0
[1 10 3 4 5]

仔细看,这两个切片的地址是一样的,所以这两个切片指向同一个内存地址。因此我们修改一个索引的值后,发现原切片的值也被修改了,说明它们共用一个底层数组。

new()和make()的区别

看起来二者没有什么区别,都在堆上分配内存,但是它们的行为不同,适用于不同的类型。

  • new(T) 为每个新的类型T分配一片内存,初始化为 0 并且返回类型为*T的内存地址:这种方法 返回一个指向类型为 T,值为 0 的地址的指针,它适用于值类型如数组和结构体;它相当于 &T{}。
  • make(T) 返回一个类型为 T 的初始值,它只适用于3种内建的引用类型:切片、map 和 channel

字符串、数组和切片的应用

从字符串生成字节切片

假设 s 是一个字

首页 上一页 1 2 3 下一页 尾页 2/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Go 语言实践(一) 下一篇Go标准库:深入剖析Go template

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目