Go数组、切片-DateWhale开源学习

目录

数组Array

数组定义

数组是具有相同类型且长度固定的一组连续数据。在go语言中我们可以使用如下几种方式来定义数组:

// 方式一
var arr1 = [5]int{}
// 方式二
var arr2 = [5]int{1,2,3,4,5}
// 方式三
var arr3 = [5]int{3:10}

输出以上三个变量的值如下所示:

arr1 [0 0 0 0 0]
arr2 [1 2 3 4 5]
arr3 [0 0 0 10 0]
  • 方法一在声明时没有为其指定初值,所以数组内的值被初始化为类型的零值。
  • 方法二使用显示的方式为数组定义初值。
  • 方法三通过下标的方式为下标为3的位置赋上了初值10
    而且在数组的定义是包含其长度的,也就是说[3]int[4]int是两种不同的数据类型。

数组的基本使用

  • 通过下标修改数组:
package main

import "fmt"

func main() {
  var x [5]int
  x[4] = 100
  fmt.Println(x)
}
  • 数组的长度:
var total float64 = 0
for i := 0; i < len(x); i++ {
  total += x[i]
}
// total是float64,len(x)是int,所以需要显示的进行转换:
fmt.Println(total / float64(len(x)))  
  • 数组的中赋值后可以有一个逗号:
package main

import "fmt"

func main() {
    x := [5]float64{
        98,
        93,
        77,
        82,
        83,
   }
   var total float64 = 0
   for _, value := range x {
      total += value
   }
   fmt.Println(total / float64(len(x)))
}
  • 数组可以通过下标进行访问,下标是从0开始,最后一个元素下标是:len-1
    for i := 0; i < len(arr); i++ {
    }
    for index, v := range arr {
    }
  • 访问越界,如果下标在数组合法范围之外,则触发访问越界,会panic
  • 数组是值类型,赋值和传参会复制整个数组,而不是指针。因此改变副本的值,不会改变本身的值
  • 支持 “==”、"!=" 操作符,因为内存总是被初始化过的
  • 指针数组 [n]*T,数组指针 *[n]T
    • 对于指针数组来说,就是:一个数组里面装的都是指针,在go语言中数组默认是值传递的,所以如果我们在函数中修改传递过来的数组对原来的数组是没有影响的。
func main() {
	var a [5]int
	fmt.Println(a)
	test(a)
	fmt.Println(a)
}


func test(a [5]int) {
	a[1] = 2
	fmt.Println(a)
}

输出

[0 0 0 0 0]
[0 2 0 0 0]
[0 0 0 0 0]
  • 数组指针的是指指向数组的指针,在go语言中我们可以如下操作。
func main() {
	var a [5]int
	var aPtr *[5]int
	aPtr = &a
	//这样简短定义也可以aPtr := &a
	fmt.Println(aPtr)
	test(aPtr)
	fmt.Println(aPtr)
}

func test(aPtr *[5]int) {
	aPtr[1] = 5
	fmt.Println(aPtr)
}

首先定义了一个数组a,然后定一个指向数组a的指针aPtr,然后将这个指针传入一个函数,在函数中我们改变了具体的值,程序的输出结果

&[0 0 0 0 0]
&[0 5 0 0 0]
&[0 5 0 0 0]

切片Slice

因为数组是固定长度的,所以在一些场合下就显得不够灵活,所以go语言提供了一种更为便捷的数据类型叫做切片。切片操作与数组类似,但是它的长度是不固定的,可以追加元素,如果以达到当前切片容量的上限会再自动扩容。

切片定义

可以通过以下几种方式定义切片:

	// 方法一
	var s1 = []int{}
	// 方法二
	var s2 = []int{1, 2, 3}
	// 方法三
	var s3 = make([]int, 5)
	// 方法四
	var s4 = make([]int, 5, 10)

方法一声明了一个空切片,方法二声明了一个长度为3的切片,方法三声明了一个长度为5的空切片,方法四声明了一个长度为5容量为10的切片。可以看到切片的定义与数组类似,但是定义切片不需要为其指定长度。

我们可以通过len()cap()这两个函数来获取切片的长度和容量,下面就来依次看下上面各切片的长度以及容量。

s1 [] 0 0
s2 [1 2 3] 3 3
s3 [0 0 0 0 0] 5 5
s4 [0 0 0 0 0] 5 10

如果我们通过这种方式定义一个切片,那么他会被赋予切片的空值nil

	var s5 []int

切片的基本使用

我们可以通过如下的方式在数组和切片上继续获取切片

func main() {
	arr := [5]int{1, 2, 3, 4, 5}
	s := []int{6, 7, 8, 9, 10}

	s1 := arr[2:4]
	s2 := arr[:3]
	s3 := arr[2:]
	s4 := s[1:3]

	fmt.Println("s1:", s1)
	fmt.Println("s2:", s2)
	fmt.Println("s3:", s3)
	fmt.Println("s4:", s4)
}

程序的输出结果如下:

s1: [3 4]
s2: [1 2 3]
s3: [3 4 5]
s4: [7 8]

切片的扩充与拼接

我们之前介绍了切片的定义以及一些切片的操作,下面就来看一下如何对切片进行扩充。

func main() {
	a := []int{1, 2, 3}
	b := a[1:3]

	b = append(b, 4)
	b = append(b, 5)
	b = append(b, 6)
	b = append(b, 7)
	fmt.Println(a)
	fmt.Println(b)
}

程序输出结果如下:

[1 2 3]
[2 3 4 5 6 7]
  • 想要将两个切片进行拼接可以使用如下这种方式:
func main() {
	a := []int{1, 2, 3}
	b := a[1:3]

	fmt.Println(b)

	a = append(a, b...)
	fmt.Println(a)
}
  • 超出原 slice.cap限制,就会重新分配底层数组,即便原数组并未填满。
package main

import (
    "fmt"
)

func main() {

    data := [...]int{0, 1, 2, 3, 4, 10: 0}
    s := data[:2:3]

    s = append(s, 100, 200) // 一次 append 两个值,超出 s.cap 限制。

    fmt.Println(s, data)         // 重新分配底层数组,与原数组无关。
    fmt.Println(&s[0], &data[0]) // 比对底层数组起始指针。

}

输出结果:

	[0 1 100 200] [0 1 2 3 4 0 0 0 0 0 0]
    0xc4200160f0 0xc420070060

从输出结果可以看出,append后的s重新分配了底层数组,并复制数据。如果只追加一个值,则不会超过s.cap 限制,也就不会重新分配。 通常以2倍容量重新分配底层数组。在大批量添加数据时,建议一次性分配足够大的空间,以减少内存分配和数据复制开销。或初始化足够长的len属性,改用索引号进行操作。及时释放不再使用的slice对象,避免持有过期数组,造成 GC无法回收。

  • 如果我们想要将一个切片的值复制给另一个切片,go语言也提供了非常简便的方式
func main() {
	a := []int{1, 2, 3}
	b := make([]int, 3)
	copy(b, a)
	fmt.Println(a)
	fmt.Println(b)
}

几个问题:

  • 声明b切片时,其长度比a切片长,复制结果是怎么样的?
    剩下的长度会0填充
  • 声明b切片时,其长度比a切片短,复制结果是怎么样的?
    只会copy切片长度内的数值
  • 声明b切片时,其长度被定义为0,那么调用copy函数会报错吗?
    不会报错,会返回空数组
  • 删除处于索引i的元素
    Go语言并没有对删除切片元素提供专用的语法或者接口,需要使用切片本身的特性来删除元素,根据要删除元素的位置有三种情况,分别是从开头位置删除、从中间位置删除和从尾部删除,其中删除切片尾部的元素速度最快。
    对于删除中间的元素,需要对剩余的元素进行一次整体挪动,同样可以用appendcopy 原地完成:
s = []int{1, 2, 3, ...}
s = append(s[:i], s[i+1:]...) // 删除中间1个元素
s = append(a[:i], s[i+N:]...) // 删除中间N个元素
s = s[:i+copy(s[i:], s[i+1:])] // 删除中间1个元素
s = s[:i+copy(s[i:], s[i+N:])] // 删除中间N个元素
  • 在索引i的位置插入元素&在索引i的位置插入长度为j的新切片
var s []int
s = append(s[:i], append([]int{x}, s[i:]...)...) // 在第i个位置插入x
s = append(s[:i], append([]int{1,2,3}, s[i:]...)...) // 在第i个位置插入切片

slice遍历

package main

import (
    "fmt"
)

func main() {

    data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    slice := data[:]
    for index, value := range slice {
        fmt.Printf("inde : %v , value : %v\n", index, value)
    }

}

切片resize(调整大小)

package main

import (
    "fmt"
)

func main() {
    var a = []int{1, 3, 4, 5}
    fmt.Printf("slice a : %v , len(a) : %v\n", a, len(a))
    b := a[1:2]
    fmt.Printf("slice b : %v , len(b) : %v\n", b, len(b))
    c := b[0:3]
    fmt.Printf("slice c : %v , len(c) : %v\n", c, len(c))
}

输出结果:

    slice a : [1 3 4 5] , len(a) : 4
    slice b : [3] , len(b) : 1
    slice c : [3 4 5] , len(c) : 3

切片与数组的关系

对于任何一个切片来说,其都有一个底层数组与之对应,我们可以将切片看作是一个窗口,透过这个窗口可以看到底层数组的一部分元素,对应关系如下图所示。
在这里插入图片描述
切片是引用底层数组的,需要注意的就是小切片引用大数组的问题,如果底层的大数组一直有切片进行引用,那么垃圾回收机制就不会将其收回,造成内存的浪费,最有效的做法是copy需要的数据后再进行操作

字符串和切片(string and slice)

string底层就是一个byte的数组,因此,也可以进行切片操作。

package main

import (
    "fmt"
)

func main() {
    str := "hello world"
    s1 := str[0:5]
    fmt.Println(s1)

    s2 := str[6:]
    fmt.Println(s2)
}

输出结果:

    hello
    world

string本身是不可变的,因此要改变string中字符。需要如下操作: 英文字符串:

package main

import (
    "fmt"
)

func main() {
    str := "Hello world"
    s := []byte(str) //中文字符需要用[]rune(str)
    s[6] = 'G'
    s = s[:8]
    s = append(s, '!')
    str = string(s)
    fmt.Println(str)
}

输出结果:

    Hello Go!

数组or切片转字符串

    strings.Replace(strings.Trim(fmt.Sprint(array_or_slice), "[]"), " ", ",", -1)   
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值