Go起步:4、复合类型1--数组array和切片slice

Mungo · · 1273 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

之前讲到了Go的基础数据类型,除此之外,Go还支持很多复合类型的数据结构。

数组(array)

数组就是指一系列同一类型数据 的集合。
Go语言中,类型 [n]T 表示拥有 n 个 T 类型的值的数组。如:

var a [3]int

表示变量 a 声明为拥有有 3个整数的数组。声明语法上与java的区别是[]是写在类型前面的。
当然,也可以让编译器统计数组字面值中元素的数目:

a := [...]int{1, 23}

这两种写法, a 都是对应长度为3的int数组。
数组的长度是其类型的一部分,因此数组不能改变大小。 可以用内置函数len()取得数组长度。

package main

import "fmt"

func main() {
    var a [2]string //定义一个长度为2的字符串数组
    a[0] = "Hello"  //下标1赋值为Hello
    a[1] = "World"
    fmt.Println(a[0], a[1]) //按下标取值
    fmt.Println(a)          //打印数组

    primes := [...]int{2, 3, 5, 7, 11, 13} //定义一个长度为6的int数组,并初始化
    for i := 0; i < len(primes); i++ {
        fmt.Println(primes[i])
    }
}

这里写图片描述
从上面可以看出,数组访问和赋值可以用下标的方式,下标从0开始,这点和其他大部分编程语言一致。
Go的数组也支持多维数组。定义方式如下:

var arrayName [ x ][ y ] variable_type
package main

import "fmt"

func main() {
    a := [3][4]int{{0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}}
    fmt.Println(a)
    for i := 0; i < 3; i++ {
        for j := 0; j < 4; j++ {
            fmt.Printf("a[%d][%d] = %d\n", i, j, a[i][j])
        }
    }
}

这里写图片描述
上面展示了二维数组的定义初始化和取值。
特别需要说明的一点是,初始化时的最后两个引号不能分行写,否则编译会不过,Go编译器不知为何做这种限制。如下写法是错误的。

a := [3][4]int{{0, 1, 2, 3}, {4, 5, 6, 7}, 
    {8, 9, 10, 11}
}

切片(slice)

前面说过,数组的长度是不可变的,这在操作上带来了很大不便,但是Go给出了很好的解决方案,就是切片(slice)。
Go的切片是对数组的抽象。Go数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片(“动态数组”),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

定义

可以通过声明一个未指定大小的数组来定义切片,类型 []T 表示一个元素类型为 T 的切片。从这个角度来说,切片可以视为动态大小的数组。
但是,切片并不存储任何数据, 它只是描述了底层数组中的一段。更改切片的元素会修改其底层数组中对应的元素。与它共享底层数组的切片都会观测到这些修改

var s []type

除此之外,可以使用make()函数来创建切片:

var slice1 []type = make([]type, length ,capacity)

其中type是切片的类型,length是切片的初始化长度,capacity是可选参数,指切片容量。
make 函数会分配一个元素为零值的数组并返回一个引用了它的切片。

a := make([]int, 5)     // len(a)=5, cap(a)=5
b := make([]int, 0, 5)  // len(b)=0, cap(b)=5

len()函数可以返回切片的长度,cap()函数返回切片的容量。

初始化

切片初始化是很灵活的,方法也有很多种。
1、直接初始化切片,[]表示是切片类型,{1,2,3}初始化值依次是1,2,3.其cap=len=3

s :=[] int {1,2,3 } 

2、初始化切片s,是数组arr的引用

s := arr[:] 

3、将arr中从下标startIndex到endIndex-1 下的元素创建为一个新的切片,arr可以是数组也可以是一个切片,这是定义的切片就是切片的切片。

s := arr[startIndex:endIndex] 

4、缺省endIndex时将表示一直到arr的最后一个元素

s := arr[startIndex:] 

5、缺省startIndex时将表示从arr的第一个元素开始

s := arr[:endIndex] 

6、通过内置函数make()初始化切片s,[]int 标识为其元素类型为int的切片

s :=make([]int,len,cap) 
package main

import "fmt"

func main() {
    //1、直接初始化切片
    var s1 = []int{1, 2, 3, 4, 5}
    s11 := []int{1, 2, 3, 4, 5}
    //2、初始化切片s,是数组arr的引用
    var arr = []int{1, 2, 3, 4, 5}
    s2 := arr[:]
    //3、从下标startIndex到endIndex-1 下的元素创建为一个新的切片
    s3 := arr[1:3]
    s31 := s1[1:3]
    //4、缺省endIndex时将表示一直到arr的最后一个元素
    s4 := arr[3:]
    s41 := s1[3:]
    //5、缺省startIndex时将表示从arr的第一个元素开始
    s5 := arr[:4]
    s51 := s1[:4]
    //6、通过内置函数make()初始化切片s,[]int 标识为其元素类型为int的切片
    s6 := make([]string, 4, 50)
    s6[0] = "a"
    s6[1] = "b"
    s6[2] = "c"
    s6[3] = "d"

    s61 := make([]string, 4)

    fmt.Println("s1:", s1)
    fmt.Println("s11:", s11)
    fmt.Println("s2:", s2)
    fmt.Println("s3:", s3)
    fmt.Println("s31:", s31)
    fmt.Println("s4:", s4)
    fmt.Println("s41:", s41)
    fmt.Println("s5:", s5)
    fmt.Println("s51:", s51)
    fmt.Println("s6:", s6)
    fmt.Println("len(s6):", len(s6))
    fmt.Println("cap(s6)", cap(s6))
    fmt.Println("s61:", s61)
    fmt.Println("len(s61):", len(s61))
    fmt.Println("cap(s61)", cap(s61))
}

输出为:
这里写图片描述

空(nil)切片

上面是对于切面的初始化,在一个切片在未初始化之前默认为 nil,长度为 0且没有底层数组。。nil是Go的一个关键字。

package main

import "fmt"

func main() {
    var s []int
    fmt.Println(s, len(s), cap(s))
    if s == nil {
        fmt.Println("nil!")
    }
}

这里写图片描述
可以看出,切片s长度和容量都是0,值是nil。即切片是空的。

切片的内幕

一个切片是一个数组片段的描述。它包含了指向数组的指针,片段的长度, 和容量(片段的最大长度)。
切片操作并不复制切片指向的元素。它创建一个新的切片并复用原来切片的底层数组。 这使得切片操作和数组索引一样高效。因此,通过一个新切片修改元素会影响到原始切片的对应元素。

package main

import "fmt"

func main() {

    s1 := [...]int{1, 2, 3, 4, 5}
    s2 := s1[2:]
    fmt.Println("修改前s1:", s1)
    fmt.Println("修改前s2:", s2)
    s2[2] = 10
    fmt.Println("修改后s2:", s2)
    fmt.Println("修改后s1:", s1)
}

这里写图片描述

切片的增长

前面说过,切片可以看成是动态数组,所以他的长度是可变的。只要切片不超出底层数组的限制,它的长度就是可变的,只需将它赋予其自身的切片即可。

package main

import "fmt"

func main() {
    s := make([]int, 5, 10)
    fmt.Println("修改后s:", len(s))
    s = s[:cap(s)]
    fmt.Println("修改后s:", len(s))
}

这里写图片描述
上面就是把切片s的长度修改成他的最大长度。如果超过他的最大长度,则会报错–“panic: runtime error: slice bounds out of range”。

s = s[:12]

这里写图片描述
如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。整个技术是一些支持动态数组语言的常见实现。

package main

import "fmt"

func main() {
    s := make([]int, 5, 10)
    t := make([]int, len(s), cap(s)*2) // 扩大s的容量
    for i := range s {
        s[i] = i
        t[i] = s[i]
    }
    fmt.Println("修改前s:", s)
    fmt.Println("修改前len(s):", len(s))
    fmt.Println("修改前cap(s):", cap(s))
    s = t
    fmt.Println("修改后s:", s)
    fmt.Println("修改后len(s):", len(s))
    fmt.Println("修改后cap(s):", cap(s))
}

这里写图片描述
上面把一个切片的容量扩大了2倍。
对于循环中复制的操作Go提供了可copy内置函数。copy函数可以将源切片的元素复制到目的切片。copy函数支持不同长度的切片之间的复制(它只复制较短切片的长度个元素)。此外, copy 函数可以正确处理源和目的切片有重叠的情况。
使用copy函数可以直接替换上面的for循环。

copy(t, s)

除此之外,Go还提供了一个为切片追加新的元素操作的方法– append()。
append 的第一个参数 s 是一个元素类型为 T 的切片, 其余类型为 T 的值将会追加到该切片的末尾。append 的结果是一个包含原切片所有元素加上新添加元素的切片。

package main

import "fmt"

func main() {
    var s []int
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)

    s = append(s, 0)
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)

    s = append(s, 1)
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)

    s = append(s, 2, 3, 4)
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)

    s2 := []int{5, 6, 7}
    s = append(s, s2...)
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

这里写图片描述
上面的程序,先创建了一个nil切片,然后不断往里面添加新的数据。s = append(s, s2…)这个写法是把后面的s2切片打散传给append,相当于是s = append(s, 5, 6, 7),这也是Go支持的语法。
可以看出切片的长度和容量是不断增加的。通过我的观察,append增加容量是按照如果容量不够把之前切片的容量乘以2,如果乘以2还不够就之前容量+1乘以2来递增的。不过这个以后还得看看源码确认下,今天一直没找到在哪。

通过到目前的了解,切片应该在Go中使用的比数组要多。


有疑问加站长微信联系(非本文作者)

本文来自:CSDN博客

感谢作者:Mungo

查看原文:Go起步:4、复合类型1--数组array和切片slice

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1273 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传