go语言的数组与切片

go语言的数组与切片

如果有编程基础的话,提到数组我们肯定不会陌生,所谓数组,就是有数的元素序列,我们可以认为数组是有限的相同类型数据的集合。

数组长度是固定的,所以这会带来很多局限性。

比如说只接受相同类型的元素,长度固定等等。

那么切片的出现,则为golang解决了以上数组所带来的不便。

切片(slice)是一个引用类型,它是拥有相同类型的元素并且长度可变的序列。

基于数组类型做的一层封装,支持扩容。

切片的内部结构包含地址、长度、容量。它主要用于服务一块数据的集合。

下面我们来看一看数组的声明:

var arr1 [5]int                                     // 声明一个长度为5的数组
var arr2 = [5]int{1, 2, 3, 4, 5}    //声明一个数组,并初始化

然后再看一看切片的声明:

var slice1 []string                 //声明一个字符串切片
var intSlice []int                  //定义一个整型切片
var boolSlice = []bool{true, false} //声明一个布尔切片并初始化

数组的遍历

第一种,for循环

var arr1 = [5]int{1,2,3,4,5}
for i := 0; i < len(arr1); i++ {
    fmt.Println(arr1[i])
}

第二种:for-range

var arr1 = [5]int{1,2,3,4,5}
for i, v := range arr1 {
    fmt.Printf("第%d个元素,值为:%d\n", i, v)
}

数组类型

数组的类型实际上是值类型,所以可以通过new() 来创建数组:

var arr1 = new([5]int)

通过new创建的数组和var arr2 [5]int的区别是什么呢?

arr1的类型是*[5]int, 而arr2的类型则是[5]int

    var arr1 = new([5]int)
    var arr2 = [5]int{1, 2, 3, 4, 5}
    fmt.Printf("arr1 type : %T, arr2 type : %T\n", arr1, arr2)

输出如下:

arr1 type : *[5]int, arr2 type : [5]int

这样的结果是什么呢?就是当把一个数组赋值给另一个数组后,需要再做一次数组内存的拷贝操作。

例如:

arr2 := *arr1
arr2[2] = 100

切片

切片的底层就是数组,所以我们可以基于数组来定义切片。

举个例子:

func main() {
    var ar1 = [5]int{1, 2, 3, 4, 5}
    ar2 := ar1[:3]
    fmt.Printf("ar1 type : %T, ar2 type : %T", ar1, ar2)
}

输出如下:

ar1 type : [5]int, ar2 type : []int

基于切片再得到切片

func main() {
    var ar1 = [5]int{1, 2, 3, 4, 5}
    ar2 := ar1[:3]
    fmt.Println(ar2)
    ar3 := ar2[0:4]
    fmt.Println(ar3)
}

输出:

[1 2 3]
[1 2 3 4]

使用make构造切片

make函数是内置的,格式如下:

make([]T,size, cap)

T就是创建切片的类型,size是切片中元素的数量,cap是切片的容量。

举个例子:

func main() {
    a := make([]int, 2, 10)
    a[1] = 10
    fmt.Println(a) //[0,10]
    fmt.Println(len(a)) //元素数量2
    fmt.Println(cap(a)) //容量是10
}

但注意!虽然切片a的容量是10,但是这并不意味着我们可以随意的给切片a赋值。

比如说:我定义了一个切片a := make([]int, 2, 10)a[1]= 10 但是如果这时我让a[2] = 11则会报错。

panic: runtime error: index out of range [2] with length 2

使用append为切片追加数据

所以这里涉及到如何为切片添加元素,我们可以用系统自带的append函数,来为切片添加元素。

每个切片都会指向一个底层数组,这个数组会容纳一定数量的元素。

当底层数组不能容纳新增的元素时,就会发生扩容,那么这时候切片指向的底层数组就会更换。

下面我们来看一个例子:

func main() {
    a := make([]int, 2, 10)
    a[0] = 1                        //第一个元素为1
    a[1] = 10                       //第二个元素为10
    a = append(a, 11)   //此时我们追加一个新的元素,11
    fmt.Println(a)          //[1,10,11]
}

使用appen()函数即可让切片添加新的元素。

那么问题来了,如果我们不停地追加新的元素,切片指向的数组什么时候会改变呢?下面再看一段代码:

func main() {
    a := make([]int, 2, 5)
    a[0] = 1
    a[1] = 10
    fmt.Printf("切片a循环前的内存地址:%p\n", a)
    for i := 0; i < 10; i++ {
        a = append(a, 100+i)
        fmt.Printf("追加完毕,循环次数:%d, 切片a此时的内存地址:%p, 切片a的容量:%d\n", i, a, cap(a))
    }
    fmt.Println(a)
}

输出结果:

切片a循环前的内存地址:0xc0000a8030
追加完毕,循环次数:0, 切片a此时的内存地址:0xc0000a8030, 切片a的容量:5
追加完毕,循环次数:1, 切片a此时的内存地址:0xc0000a8030, 切片a的容量:5
追加完毕,循环次数:2, 切片a此时的内存地址:0xc0000a8030, 切片a的容量:5
追加完毕,循环次数:3, 切片a此时的内存地址:0xc0000ac000, 切片a的容量:10
追加完毕,循环次数:4, 切片a此时的内存地址:0xc0000ac000, 切片a的容量:10
追加完毕,循环次数:5, 切片a此时的内存地址:0xc0000ac000, 切片a的容量:10
追加完毕,循环次数:6, 切片a此时的内存地址:0xc0000ac000, 切片a的容量:10
追加完毕,循环次数:7, 切片a此时的内存地址:0xc0000ac000, 切片a的容量:10
追加完毕,循环次数:8, 切片a此时的内存地址:0xc0000ae000, 切片a的容量:20
追加完毕,循环次数:9, 切片a此时的内存地址:0xc0000ae000, 切片a的容量:20
[1 10 100 101 102 103 104 105 106 107 108 109]
切片a此时的内存地址:0xc0000ae000

我们在切片初始化的时候指定容量为5,由上面运行结果可以知道,在i循环到2后,切片本身的元素数量已经达到了5,也就是说,下一次再添加元素的时候就会发生扩容,然后底层指向的数组会改变。

所以,在循环到3点时候,a的容量由5变为10,此时内存地址也发生改变。每次扩容都是上一次的2倍大。

追加多个元素

append()函数会将元素添加到切片最后,并返回该切片,同时也支持追加多个元素。

下面代码示例:

func main() {
    a := make([]int, 2, 5)
    a = append(a, 1, 2, 3, 4, 5) //追加多个元素
    b := []int{6, 6, 6}          //我们再定义一个切片
    a = append(a, b...)          //追加切片
    fmt.Print(a)
}

输出如下:

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

由此我们可以看到,前两个元素0,0是切片a本身定义的元素,由于初始化没有赋值,所以默认是0,接着是1,2,3,4,5,最后三个6则是切片b追加到最后。

切片的扩容策略

可以通过查看$GOROOT/src/runtime/slice.go源码,其中有个函数叫做growslice

那么在这个函数中,源码上面就写了很多的注释,如下所示:

// growslice handles slice growth during append.

// It is passed the slice element type, the old slice, and the desired new minimum capacity,

// and it returns a new slice with at least that capacity, with the old data

// copied into it.

// The new slice‘s length is set to the old slice‘s length,

// NOT to the new requested capacity.

// This is for codegen convenience. The old slice‘s length is used immediately

// to calculate where to write new values during an append.

// TODO: When the old backend is gone, reconsider this decision.

// The SSA backend might prefer the new length or to return only ptr/cap and save stack space.

那它究竟是怎么扩容的呢?我们往下看:

newcap := old.cap
    doublecap := newcap + newcap
    if cap > doublecap {
        newcap = cap
    } else {
        if old.len < 1024 {
            newcap = doublecap
        } else {
            // Check 0 < newcap to detect overflow
            // and prevent an infinite loop.
            for 0 < newcap && newcap < cap {
                newcap += newcap / 4
            }
            // Set newcap to the requested cap when
            // the newcap calculation overflowed.
            if newcap <= 0 {
                newcap = cap
            }
        }
    }

由代码我们可以得知,每次扩容时的条件是什么,并不是每次扩容都会扩大到2倍,如果旧切片长度小于1024,那最终容量就是old cap的两倍,否则就会增加原来的四分之一,直到newcap >= cap.

需要注意的是,切片扩容还会根据切片中元素的类型不同而做不同的处理,比如intstring类型的处理方式就不一样。

使用copy()函数

切片是引用类型,所以如果我们定义了一个切片a,然后切片 b = a,那此时,a和b都是指向了同一块内存地址,修改a的时候也会修改b。

下面我们来看一段代码:

func main() {
    a := []int{1, 2, 3, 4}
    b := a
    fmt.Printf("a : %d, b: %d\n", a, b)
    a[2] = 999
    fmt.Printf("a : %d, b: %d\n", a, b)
}

a : [1 2 3 4], b: [1 2 3 4] 修改之后 a : [1 2 999 4], b: [1 2 999 4]

那如何避免这种情况发生呢?这时候我们就要用到go内置的copy()函数了。

Go语言内建的copy()函数可以迅速地将一个切片的数据复制到另外一个切片空间中,copy()函数的使用格式如下:

copy(destSlice, srcSlice []T)
func main() {
    a := []int{1, 2, 3, 4}
    b := make([]int, 4, 4)
    fmt.Printf("修改之前 a : %d, b: %d\n", a, b)
    copy(b, a)
    a[2] = 999
    fmt.Printf("修改之后 a : %d, b: %d\n", a, b)
}

输出如下:

修改之前 a : [1 2 3 4], b: [0 0 0 0]

修改之后 a : [1 2 999 4], b: [1 2 3 4]

切片删除元素

go语言并没有删除切片元素的专有方法,但是可以通过索引来删除切片中的元素。

func main() {
    // 从切片中删除元素
    a := []int{30, 31, 32, 33, 34, 35, 36, 37}
    // 要删除索引为1,2,3的元素
    a = append(a[:1], a[4:]...)
    fmt.Println(a)
}

[30 34 35 36 37]

去掉最后一个元素:

slice1 = slice1[:len(slice1)-1]

原文地址:https://www.cnblogs.com/changfangxing/p/12423098.html

时间: 2024-08-26 22:12:20

go语言的数组与切片的相关文章

Go语言入门——数组、切片和映射

按照以往开一些专题的风格,第一篇一般都是“从HelloWorld开始” 但是对于Go,思来想去,感觉真的从“HelloWorld”说起,压根撑不住一篇的篇幅,因为Go的HelloWorld太简单了. 1.简介 Go是什么? Go(又称Golang)是Google开发的一种静态强类型.编译型.并发型,并具有垃圾回收功能的编程语言.——摘自百度百科 Github地址 https://github.com/golang/go 官网地址 https://golang.org 中文网社区 https://

go 语言学习 - 数组和切片

package main import "fmt" func main(){ //数组 var a = [3]int{}//相当于[3]int{0,0,0} a[0] = 1 changeArray(a) fmt.Println(a) b := [...]int{1,2,3}//省略号符号表示让编译器根据后面初始化情况自动计算数组的长度,但这个长度是编译时确定的 fmt.Println(b) c := new([3]int) //new 返回的是地址,但是一样可以用 pointName

GO语言总结(3)——数组和切片

上篇博文简单介绍了一下Go语言的基本类型——GO语言总结(2)——基本类型,本篇博文开始介绍Go语言的数组和切片. 一.数组 与其他大多数语言类似,Go语言的数组也是一个元素类型相同的定长的序列. (1)数组的创建. 数组有3种创建方式:[length]Type .[N]Type{value1, value2, ... , valueN}.[...]Type{value1, value2, ... , valueN} 如下: func test5() { var iarray1 [5]int32

go语言数组与切片比较

一.数组 与其他大多数语言类似,Go语言的数组也是一个元素类型相同的定长的序列. (1)数组的创建. 数组有3种创建方式:[length]Type .[N]Type{value1, value2, ... , valueN}.[...]Type{value1, value2, ... , valueN} 如下: 复制代码 代码如下: func test5() {    var iarray1 [5]int32    var iarray2 [5]int32 = [5]int32{1, 2, 3,

Go - 数组 和 切片

一.数组 与其他大多数语言类似,Go语言的数组也是一个元素类型相同的定长的序列. (1)数组的创建 数组有 3 种创建方式: 1) [length]Type 2) [length]Type{value1, value2, ... , valueN} 3) [...]Type{value1, value2, ... , valueN} 如下: func test5() { var arr1 [5]int32 var arr2 [5]int32 = [5]int32{1, 2, 3, 4, 5} a

Golang理解-数组和切片

数组 数组在Go中定义及特点 数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成. 因为数组的长度是固定的,因此在Go语言中很少直接使用数组. 和数组对应的类型是Slice(切片),它是可以增长和收缩动态序列,slice功能也更灵活,但是要理解slice工作原理的话需要先理解数组. 默认情况下,数组的每个元素都被初始化为元素类型对应的零值,对于数字类型来说就是0.我们也可以使用数组字面值语法用一组值来初始化数组: var q [3]int = [3]int{1, 2,

Go 数组与切片

#### Go 数组与切片***渡风渡尘缘,修行亦修心***##### 数组数组是可以存放多个同一类型的数据的集合,数组也是一种数据类型,它是值类型; 数组如何定义? var 数组名 [数组大小]数据类型: var a [5]int package main import "fmt" func main(){ var intArr [3]int intArr[0] = 10 intArr[1] = 20 intArr[2] = 30 fmt.Println(intArr) } 数组在内

go 的数组和切片

什么是数组? 数组 数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成 数组定义的方法? 方式一 package main import "fmt" func arraytest() { var x [3] int fmt.Println(x) } // 输出 [0 0 0] func main() { arraytest() } 使用快速声明数组 x3 :=[3] int {112,78} fmt.Println(x3) 输出 [112 78 0] //

Go 系列教程 —— 11. 数组和切片

数组 数组是同一类型元素的集合.例如,整数集合 5,8,9,79,76 形成一个数组.Go 语言中不允许混合不同类型的元素,例如包含字符串和整数的数组.(译者注:当然,如果是 interface{} 类型数组,可以包含任意类型) 数组的声明 一个数组的表示形式为 [n]T.n 表示数组中元素的数量,T 代表每个元素的类型.元素的数量 n 也是该类型的一部分(稍后我们将详细讨论这一点). 可以使用不同的方式来声明数组,让我们一个一个的来看. package main import ( "fmt&q