golang之数组切片的具体用法

目录
  • 数组
  • 切片
    • 切片的创建
      • 直接声明
      • new方式初始化
      • 字面量
      • make方式
      • 截取方式
      • s[:]
      • s[i:]
      • s[:j]
      • s[i:j]
      • s[i:j:x]
      • 看个例子
    • 切片的扩容
      • 内存对齐
    • 空切片和nil切片
    • 数组是值传递,切片是引用传递?
    • 数组和slice能不能比较
      • 只有长度相同,类型也相同的数组才能比较
      • slice只能和nil做比较,其余的都不能比较

数组

go开发者在日常的工作中slice算是用的比较多的了,在介绍slice之前,我们先了解下数组,数组相信大家都不陌生,数组的数据结构比较简单,它在内存中是连续的。以一个存了10个数字的数组为例来说:

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

它在内存中大概是这样的:

得益于连续性,所以数组的特点就是:

  • 大小固定
  • 访问快,复杂度为O(1);
  • 插入和删除元素因为要移动元素,所以相比查询会慢。 当我们要访问一个越界的元素的元素时,go甚至编辑都不通过:
a := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Println(a[10])
// invalid array index 10 (out of bounds for 10-element array)

切片

相比数组,go的slice(切片)要相对灵活些,比较大的不同点就是slice的长度可以不固定,创建的时候不用指明长度,在go中slice是一种设计过的数据结构:

type slice struct {
   array unsafe.Pointer //指针
   len   int //长度
   cap   int //容量
}

slice的底层其实还是数组,通过指针指向它底层的数组,len是slice的长度,cap是slice的容量,slice添加元素时,且cap容量不足时,会根据策略扩容。

切片的创建

直接声明

var s []int

通过直接声明的slice,它是个nil slice,它的长度和容量都是0,且不指向任何底层数组,nil切片和空切片是不一样的,接下来会介绍。

new方式初始化

s:=*new([]int)

new的方式和直接声明的方式区别不大,最终产出的都是一个nil的slice。

字面量

s1 := []int{0, 1, 2}
s2 := []int{0, 1, 2, 4: 4}
s3 := []int{0, 1, 2, 4: 4, 5, 6, 9: 9}
fmt.Println(s1, len(s1), cap(s1)) //[0 1 2] 3 3
fmt.Println(s2, len(s2), cap(s2)) //[0 1 2 0 4] 5 5
fmt.Println(s3, len(s3), cap(s3)) //[0 1 2 0 4 5 6 0 0 9] 10 10

字面量创建的slice,默认长度和容量是相等的,需要注意的是如果我们单独指明了某个索引的值,那么在这个索引值前面的元素如果未声明的话,就会是slice的类型的默认值。

make方式

s := make([]int, 5, 6)
fmt.Println(s, len(s), cap(s)) //[0 0 0 0 0] 5 6

通过make可以指定slice的长度和容量。

截取方式

切片可以从数组或者其他切片中截取获得,这时新的切片会和老的数组或切片共享一个底层数组,不管谁修改了数据,都会影响到底层的数组,但是如果新的切片发生了扩容,那么底层的数组就不是同一个。

s[:]

a := []int{0, 1, 2, 3, 4}
b := a[:]
fmt.Println(b, len(b), cap(b)) //[0 1 2 3 4] 5 5

通过: 获取 [0,len(a)-1]的切片,等同于整个切片的引用。

s[i:]

a := []int{0, 1, 2, 3, 4}
b := a[1:]
fmt.Println(b, len(b), cap(b)) //[1 2 3 4] 4 4

通过指定切片的开始位置来获取切片,它是左闭的包含左边的元素,此时它的容量cap(b)=cap(a)-i。这里要注意界限问题,a[5:]的话,相当于走到数组的尾巴处,什么元素也没了,此时就是个空切片,但是如果你用a[6:]的话,那么就会报错,超出了数组的界限。

a := []int{0, 1, 2, 3, 4}
b := a[5:] //[]
c := a[6:] //runtime error: slice bounds out of range [6:5]

c虽然报错了,但是它只是运行时报错,编译还是能通过的

s[:j]

a := []int{0, 1, 2, 3, 4}
b := a[:4]
fmt.Println(b, len(b), cap(b)) //[0 1 2 3] 4 5

获取[0-j)的数据,注意右边是开区间,不包含j,同时它的cap和j没关系,始终是cap(b) = cap(a),同样注意不要越界。

s[i:j]

a := []int{0, 1, 2, 3, 4}
b := a[2:4]
fmt.Println(b, len(b), cap(b)) //[2 3] 2 3

获取[i-j)的数据,注意右边是开区间,不包含j,它的cap(b) = cap(a)-i

s[i:j:x]

a := []int{0, 1, 2, 3, 4}
b := a[1:2:3]
fmt.Println(b, len(b), cap(b)) //[1] 1 2

通过上面的例子,我们可以发现切片b的cap其实和j没什么关系,和i存在关联,不管j是什么,始终是cap(b)=cap(a)-ix的出现可以修改b的容量,当我们设置x后,cap(b) = x-i而不再是cap(a)-i了。

看个例子

s0 := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s1 := s0[3:6] //[3 4 5] 3 7

s1是对s0的切片,所以它们大概是这样:

s2 := s1[1:3:4]

这时指定个s2,s2是对s1的切片,并且s2的len=2,cap=3,所以大概长这样:

s1[1] = 40
fmt.Println(s0, s1, s2)// [0 1 2 3 40 5 6 7 8 9] [3 40 5] [40 5]

这时把s1[1]修改成40,因为没有涉及到扩容,s0、s1、s2重叠部分都指向同一个底层数组,所以最终发现s0、s2对应的位置都变成了40。

s2 = append(s2, 10)
fmt.Println(s2, len(s2), cap(s2)) //[40 5 10] 3 3

再向s2中添加一个元素,因为s2还有一个空间,所以不用发生扩容。

s2 = append(s2, 11)
fmt.Println(s2, len(s2), cap(s2)) //[40 5 10 11] 4 6

继续向s2中添加一个元素,此时s2已经没有空间了,所以会触发扩容,扩容后指向一个新的底层数据,和原来的底层数组解耦了。

此时无论怎么修改s2都不会影响到s1和s2。

切片的扩容

slice的扩容主要通过growslice函数上来处理的:

func growslice(et *_type, old slice, cap int) slice {
    ....
    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
            }
        }
    }
    ....
    return slice{p, old.len, newcap}
}

入参说明下:

  • et是slice的类型。
  • old是老的slice。
  • cap是扩容后的最低容量,比如原来是4,append加了一个,那么cap就是5。 所以上面的代码解释为:
  • 如果扩容后的最低容量大于老的slice的容量的2倍,那么新的容量等于扩容后的最低容量。
  • 如果老的slice的长度小于1024,那么新的容量就是老的slice的容量的2倍
  • 如果老的slice的长度大于等于1024,那么新的容量就等于的容量不停的1.25倍,直至大于扩容后的最低容量。 这里需要说明下关于slice的扩容网上很多文章都说小于1024翻倍扩容,大于1024每次1.25倍扩容,其实就是基于这段代码,但其实这不全对,我们来看个例子:
a := []int{1, 2}
fmt.Println(len(a), cap(a)) //2 2
a = append(a, 2, 3, 4)
fmt.Println(len(a), cap(a)) // 5 6

按照规则1,这时的cap应该是5,结果是6。

a := make([]int, 1280, 1280)
fmt.Println(len(a), cap(a)) //1280 1280
a = append(a, 1)
fmt.Println(len(a), cap(a), 1280*1.25) //1281 1696 1600

按照规则3,这时的cap应该是原来的1.25倍,即1600,结果是1696。

内存对齐

其实上面两个扩容,只能说不是最终的结果,go还会做一些内存对齐的优化,通过内存对齐可以提升读取的效率。

// 内存对齐
capmem, overflow = math.MulUintptr(et.size, uintptr(newcap))
capmem = roundupsize(capmem)
newcap = int(capmem / et.size)

空切片和nil切片

空切片:slice的指针不为空,len和cap都是0
nil切片:slice的指针不指向任何地址即array=0,len和cap都是0

nil
var a []int a:=make([]int,0)
a:=*new([]int) a:=[]int{}

空切片虽然地址不为空,但是这个地址也不代表任何底层数组的地址,空切片在初始化的时候会指向一个叫做zerobase的地址,

var zerobase uintptr
if size == 0 {
      return unsafe.Pointer(&zerobase)
}

所有空切片的地址都是一样的。

var a1 []int
a2:=*new([]int)
a3:=make([]int,0)
a4:=[]int{}

fmt.Println(*(*[3]int)(unsafe.Pointer(&a1))) //[0 0 0]
fmt.Println(*(*[3]int)(unsafe.Pointer(&a2))) //[0 0 0]
fmt.Println(*(*[3]int)(unsafe.Pointer(&a3))) //[824634101440 0 0]
fmt.Println(*(*[3]int)(unsafe.Pointer(&a4))) //[824634101440 0 0]

数组是值传递,切片是引用传递?

func main() {
   array := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
   slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
   changeArray(array)
   fmt.Println(array) //[0 1 2 3 4 5 6 7 8 9]
   changeSlice(slice)
   fmt.Println(slice) //[1 1 2 3 4 5 6 7 8 9]
}

func changeArray(a [10]int) {
   a[0] = 1
}

func changeSlice(a []int) {
   a[0] = 1
}
  • 定义一个数组和一个切片
  • 通过changeArray改变数组下标为0的值
  • 通过changeSlice改变切片下标为0的值
  • 原数组值未被修改,原切片的值已经被修改 这个表象看起来像是slice是指针传递似的,但是如果我们这样呢:
func main() {
   slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
   changeSlice(slice)//[0 1 2 3 4 5 6 7 8 9]
}
func changeSlice(a []int) {
   a = append(a, 99)
}

会发现原slice的值并没有被改变,这是因为我们用了append,append之后,原slice的容量已经不够了,这时候会copy出一个新的数组。其实go的函数参数传递,只有值传递,没有引用传递,当slice的底层数据没有改变的时候,怎么修改都会影响原底层数组,当slice发生扩容时,扩容后就是新的数组,那么怎么修改这个新的数组都不会影响原来的数组。

数组和slice能不能比较

只有长度相同,类型也相同的数组才能比较

a:=[2]int{1,2}
b:=[2]int{1,2}
fmt.Println(a==b) true

a:=[2]int{1,2}
b:=[3]int{1,2,3}
fmt.Println(a==b) //invalid operation: a == b (mismatched types [2]int and [3]int)

a:=[2]int{1,2}
b:=[2]int8{1,2}
fmt.Println(a==b) //invalid operation: a == b (mismatched types [2]int and [2]int8)

slice只能和nil做比较,其余的都不能比较

a:=[]int{1,2}
b:=[]int{1,2}
fmt.Println(a==b)//invalid operation: a == b (slice can only be compared to nil)

但是需要注意的是,两个都是nil的slice也不能进行比较,它只能和nil对比,这里的nil是真真实实的nil。

var a []int
var b []int
fmt.Println(a == b) //invalid operation: a == b (slice can only be compared to nil)
fmt.Println(a == nil) //true

到此这篇关于golang之数组切片的具体用法的文章就介绍到这了,更多相关golang 数组切片内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 轻松读懂Golang中的数组和切片

    目录 一.数组和切片的区别是什么? 1.数组 2.切片 二.数组和切片的初始化? 1.数组 2.切片 二.常见问题 1.切片的初始化与追加 2.slice拼接问题 3.new和make的区别 总结 一.数组和切片的区别是什么? 1.数组 数组是内置(build-in)类型,是一组同类型数据的集合,它是值类型,通过从0开始的下标索引访问元素值.在初始化后长度是固定的,无法修改其长度.当作为方法的参数传入时将复制一份数组而不是引用同一指针.数组的长度也是其类型的一部分,通过内置函数len(array

  • golang将切片或数组根据某个字段进行分组操作

    我就废话不多说了,大家还是直接看代码 吧~ package main import ( "fmt" "sort" ) type Person struct { Name string Age int } func main() { p1 := Person{"Tom",20} p2 := Person{"Lily",21} p3 := Person{"Linda",23} p4 := Person{&quo

  • golang数组和切片作为参数和返回值的实现

    目录 1. 数组作为参数和返回值时 1.1数组的定义 1.2数组作为参数和返回值的时候 2.切片作为参数和返回值 2.1 切片的定义初始化 2.2 切片的存储大致分为3部分 2.3 切片作为参数和返回值 2.4 append 切片动态增长的原理 2.5 copy 函数 通过赋值切片可以使得两个切片的数据不共享 3. 总结: 1. 数组作为参数和返回值时 1.1数组的定义 数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整型.字符串或者自定义类型 var

  • 理解Golang中的数组(array)、切片(slice)和map

    我比较喜欢先给出代码,然后得出结论 数组 复制代码 代码如下: package main import (     "fmt" ) func main() {     arr := [...]int{1, 2, 3}     //打印初始的指针     fmt.Printf("the pointer is : %p \n", &arr)     printPointer(arr) } func printPointer(any interface{}) {

  • golang之数组切片的具体用法

    目录 数组 切片 切片的创建 直接声明 new方式初始化 字面量 make方式 截取方式 s[:] s[i:] s[:j] s[i:j] s[i:j:x] 看个例子 切片的扩容 内存对齐 空切片和nil切片 数组是值传递,切片是引用传递? 数组和slice能不能比较 只有长度相同,类型也相同的数组才能比较 slice只能和nil做比较,其余的都不能比较 数组 go开发者在日常的工作中slice算是用的比较多的了,在介绍slice之前,我们先了解下数组,数组相信大家都不陌生,数组的数据结构比较简单

  • golang语言中for循环语句用法实例

    本文实例讲述了golang语言中for循环语句用法.分享给大家供大家参考.具体分析如下: for循环是用来遍历数组或数字的.用for循环遍历字符串时,也有 byte 和 rune 两种方式.第一种为byte,第二种rune. 复制代码 代码如下: package main import ( "fmt" ) func main() { s := "abc汉字" for i := 0; i < len(s); i++ { fmt.Printf("%c,&

  • python多维数组切片方法

    1.数组a第0个元素(二维数组)下的所有子元素(一维数组)的第一列 import numpy as np b=np.arange(24) a=b.reshape(2,3,4) print a print a[0,:,0] 2.取所有二维数组下的每个二维数组的第0个元素(一维数组) b=np.arange(24) a=b.reshape(2,3,4) print a print '--------------------' print a[:,0] 结果: [[ 0 1 2 3] [12 13 1

  • Golang二维切片初始化的实现

    引言 之前,刷Leetcode的时候,有些题需要初始化二维数组,而一维数组的初始化,比如: var a = [5]int{1, 2, 3, 4, 5} // 用var b := [5]int{1, 2, 3, 4, 5} // 用类型推断 var c = [...]int{1, 2, 3, 4, 5} // 不确定长度 d := [...]int{1, 2, 3, 4, 5} 如果不知道数组元素的话,可以这样: var a [5]int b := [5]int{} c := make([]int

  • golang数据结构之golang稀疏数组sparsearray详解

    目录 一.稀疏数组 1. 先看一个实际的需求 2. 基本介绍 3. 应用实例 一.稀疏数组 1. 先看一个实际的需求 编写的五子棋程序中,有存盘退出和续上盘的功能 分析按照原始的方式来的二维数组的问题 因为该二维数组的很多值是默认值0,因此记录了很多没有意义的数据 2. 基本介绍 当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组. 稀疏数组的处理方法是: 1)记录数组一共有几行几列,有多少个不同的值 2)思想:把具有不同值的元素的行列及值记录在一个小规模的数组中,

  • GO语言对数组切片去重的实现

    目录 1.go中没有去重方法 2.自定义一个适配多个切片类型的去重器 补充: 通过map键的唯一性去重(推荐) 通过map键的唯一性去重 Go语言是2007年由Google开发的一种静态强类型的编译型语言,其语法结构上与C非常接近.在垃圾回收.错误处理以及包库方面比C要方便的多,因此从开发速度上来讲比C要快的多,而运行速度也接近于C语言.以下实现GO语言对数组切片去重 1.go中没有去重方法 自己实现 package main import (     "fmt" ) func mai

  • 一文详解Golang中的切片数据类型

    目录 含义 定义 三个要素 切片与数组的区别 示例代码 切片内存分布 切片定义分类 数组生成切片 示例代码 切片索引 直接声明切片 定义语法 代码示例 使用make定义切片 常用操作 长度计算 容量计算 判断是否为空 切片追加 语法格式 尾部追加 开始位置追加 中间位置追加 复制 引用和复制 切片的删除 删除开头 删除中间 删除结尾 指定位置 排序 迭代器 含义 切片是一个种特殊的数组.是对数组的一个连续片段的引用,所以切片是一个引用类型.切片可以是数组中的一部分,也可以是由起始和终止索引标识的

  • numpy数组切片的使用

    目录 numpy.array的数组切片 numpy的数组合并 numpy的常用函数讲解 np.arange() 随机函数seed() import numpy as np a = np.array([[1.1,2.1,3.1,4.1],[5,6,7,8],[9,10,11,12]]) a numpy.array的数组切片 1.使用省略号来省略数组的参数的用法 print (a[...,1]) # 索引为1的列元素 print ('--------') print (a[1,...]) # 索引为

  • 浅谈js数组和splice的用法

    首先添加一个splice函数: splice:该方法的作用就是从数组中删除一个元素 array.splice(index,count,value....); index:表示从哪一个下标开始, count:表示删除元素的个数 value:代表增加的元素 example: 1.var array = new Array(1,2,3,4,5,6); array.splice(0,1,2) result:2,2,3,4,5 2.var array = new Array(1,2,3,4,5,6); a

  • Javascript数组中push方法用法分析

    本文实例讲述了Javascript数组中push方法用法.分享给大家供大家参考,具体如下: 看下面代码: var o = { 1:'a' ,2:'b' ,length:2 ,push:Array.prototype.push }; o.push('c'); Q:o现在内部的值是什么样子? 我的第一反应是排斥,为什么要研究不合理情况下[解释引擎]的行为?但是这种推论有时候又很吸引人,于是我回来的时候仔细思考了下,发现其实很简单. 对于push这个方法,我条件反射地想到的就是栈,[数据结构的经典栈]

随机推荐