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

原创 2016年08月25日 19:52:05

之前讲到了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中使用的比数组要多。

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

golang 数组切片(二)

多维数组介绍1、多维静态数组 array := [][]byte{}在函数中传递数组是非常昂贵的行为,因为在函数之间传递变量永远是传递值,所以如果变量是数组,那么意味着传递整个数组,即使它很大很大很...

Golang(Go语言)代码技巧之数组(array)和切片(slice)

去掉最后n个元素 line = line[:len(line)-n] 两种遍历方式 for i:=0;ilen(line);i++{ // ...=line[i]}f...

Go语言基础:array、slice、make和new操作、map

array数组声明和赋值go的数组声明跟C语言很相似,除了数组类型放在变量名后面【这点跟变量的声明一样】。 数组的定义格式://一维数组 var 数组名[n] 数组类型 //需要注意的是:'[n]'...
  • uudou
  • uudou
  • 2016年08月16日 19:52
  • 1569

go的学习记录(二)--- 数组与数组切片

在go语言的学习中,少不了接触的就是数组和数组切片,并且这两者的使用也是非常频繁的。因此如果不能进行很好的理解,或许很容易犯错。那么在go中,数组和数组切片都有什么特点,又是怎么声明赋值的,如何使用的...

数组切片

转自:Go语言编程    32页 在前一节里我们已经提过数组的特点:数组的长度在定义之后无法再次修改;数组是值类型,每次传递都将产生一份副本。显然这种数据结构无法完全满足开发者的真实需求。不用失望,...

golang语法总结(十五):数组array与切片slice

数组     数组定义格式示例: var a [2]intfmt.Println(a)b := [2]int{2,3}fmt.Println(b) 输出: [0 0...

go array和slice区别

来源http://www.2cto.com/kf/201401/274536.html

Go语言学习笔记2——数组和Slice

在任何一门语言里,数组应该都是非常基础的类型了,使用率当然也应该是很高的。go不但提供了数组,而且还在数组这个类型之上加了一层包装,这个包装也就是slice。         go的数组变量(也就是...
  • Marcky
  • Marcky
  • 2012年02月29日 22:18
  • 18644

Java创建数组的几种方式

1、一维数组的声明方式: type[] arrayName; 或 type arrayName[];  附:推荐使用第一种格式,因为第一种格式具有更好的可读性,表示type[]是一种引用类型(数组...

go 二维数组

package main import (     "fmt" ) func main() {     s := [...][2]string{{"a", "b"}, {"c", "d"}}...
  • zistxym
  • zistxym
  • 2013年05月30日 19:09
  • 8787
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Go起步:4、复合类型1--数组array和切片slice
举报原因:
原因补充:

(最多只允许输入30个字)