Go数组与切片轻松掌握

目录
  • 数组(array)
    • 初始化数组
    • 数组赋值
    • 遍历数组
    • 数组对比
  • 切片(slice)
    • 切片的性质
    • 切片初始化
    • 切片赋值
    • 切片的容量
    • append以及扩容

在 Go 中,数组和切片的功能其实是类似的,都是用来存储一种类型元素的集合。数组是固定长度的,而切片的长度是可以调整的

数组(array)

我们在声明一个数组的时候据必须要定义它的长度,并且不能修改。

数组的长度是其类型的一部分:比如,[2]int 和 [4]int 是两个不同的数组类型。

初始化数组

// 1. 创建一维数组
// 元素都是默认值
var arr1 [3]int
// 指定长度并设置初始值
var arr2 = [3]int{1, 2, 3}
var arr3 [3]int = [3]int{1, 2, 3}
// 自动推导数组长度
var arr4 = [...]int{1, 2, 3}
// 指定特定下标的元素的值,其他的为默认值
var arr5 = [3]int{1: 9}
// 2. 创建多维数组 与一维数组类似,不再赘述
var arr6 = [3][2]int{{1, 2}, {3, 4}, {5, 6}}
fmt.Println(arr1)
fmt.Println(arr2)
fmt.Println(arr3)
fmt.Println(arr4)
fmt.Println(arr5)
fmt.Println(arr6)

------结果----------------------------
[0 0 0]
[1 2 3]
[1 2 3]
[1 2 3]
[0 9 0]
[[1 2] [3 4] [5 6]]

数组赋值

var arr = [3]int{1, 2, 3}
fmt.Println(arr)
arr[2] = 9
fmt.Println(arr)

------结果----------------------------
[1 2 3]
[1 2 9]

遍历数组

方法一:for 循环遍历

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

------结果----------------------------
1
2
3

方法二:for range 循环遍历

使用 index 和 value 分别接收每次循环到的位置的下标和值

var arr = [3]int{1, 2, 3}
for index, value := range arr {
	fmt.Printf("index:%d value:%d\n", index, value)
}

------结果----------------------------
index:0 value:1
index:1 value:2
index:2 value:3

数组对比

数组比较的方法比较简单,使用 == 符号即可

var arr = [3]int{1, 2, 3}
var arr2 = [3]int{1, 2, 3}
fmt.Println(arr == arr2)
var arr3 = [...]int{1, 2, 3}
fmt.Println(arr == arr3)
var arr4 = [...]int{1, 2, 4}
fmt.Println(arr == arr4)

------结果----------------------------
true
true
false

不能比较长度不同的数组类型,否则编译器会报错,如下:

var arr = [3]int{1, 2, 3}
var arr5 = [...]int{1, 2}
fmt.Println(arr == arr5)

切片(slice)

切片的性质

切片类型的定义

type slice struct {
    array unsafe.Pointer //指向数组的指针
    len int //切片的长度,可以理解为切片表示的元素的个数
    cap int //容量,指针所指向的数组长度(从指针位置向后)
}

切片的特性

  • 切片是一个引用类型,是对数组的一个连续片段的引用
  • 切片本身是一个结构体,通过值拷贝传递
  • 切片的 cap 一定是大于等于 len 的

切片初始化

//直接声明并赋值
s0 := []int{1, 2, 3, 4, 5}
//通过数组或者切片获取
arr := [...]int{1, 2, 3, 4, 5}
s1 := s0[:] // 切片 s0 中的全部元素
s2 := s0[:2] // 切片 s0 第一个元素到第二个元素
s3 := arr[3:] // 数组 arr 从第四个元素开始向后的所有元素
s4 := arr[0:0] // 创建一个空切片
//通过 make(t Type, size ...IntegerType) 初始化,
//接受的第一个 int 表示切片长度,第二个表示容量大小。如果只有一个int参数则默认长度和容量是相同的
s5 := make([]int, 5) //创建一个长度为 5 切片,
s6 := make([]int, 5, 8) //创建一个长度为 5 容量为 8 的int型切片(长度为5的部分会被初始化为默认值)
fmt.Println(s0, s1, s2, s3, s4, s5 ,s6)

-------结果-----------------------------------
[1 2 3 4 5] [1 2 3 4 5] [1 2] [4 5] [] [0 0 0 0 0] [0 0 0 0 0]

切片赋值

和数组相同根据 index 赋值

//直接声明并赋值
s0 := []int{1, 2, 3, 4, 5}
fmt.Println(s0)
s0[0] = 999
fmt.Println(s0)

-------结果-----------------------------------
[1 2 3 4 5]
[999 2 3 4 5]

切片的容量

我们可以通过 len(slice) 获取一个切片的长度,可以通过 cap(slice) 获取一个切片的容量。

容量:指针所指向的数组长度(从指针位置向后),如何理解 从指针位置向后 这个意思,通过代码观察:

s0 := []int{1, 2, 3, 4, 5}
s1 := s0[1:3] //第二个元素到第三个元素
fmt.Printf("len: %d\n", len(s1))
fmt.Printf("cap: %d\n", cap(s1))
fmt.Println(s1 )

------结果---------------
len: 2
cap: 4
[2 3]

如上,s1 实际指向的数组是 s0 的数组的一个连续片段。

所有我们可以使用 cap 把切片 s1 指向的数组(指针向后,包含指针)的去拿不元素都获取到:

s0 := []int{1, 2, 3, 4, 5}
s1 := s0[1:3]
s2 := s1[:cap(s1)]
fmt.Printf("len: %d\n", len(s2))
fmt.Printf("cap: %d\n", cap(s2))
fmt.Println(s2)

-------结果------------
len: 4
cap: 4
[2 3 4 5]

append以及扩容

append 可以动态地向切片中追加元素

s0 := []int{1, 2, 3, 4, 5}
s0 = append(s0, 6, 7, 8, 9, 10) //追加元素
fmt.Printf("len: %d\n", len(s0))
fmt.Printf("cap: %d\n", cap(s0))
fmt.Println(s0)
s1 := []int{11, 12, 13, 14, 15}
s0 = append(s0, s1...) //追加切片,切片需要解包
fmt.Printf("len: %d\n", len(s0))
fmt.Printf("cap: %d\n", cap(s0))
fmt.Println(s0)

len: 10

cap: 10

[1 2 3 4 5 6 7 8 9 10]

len: 15

cap: 20

[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15]

我们可以发现,在第二次和第三次追加元素的时候,切片的容量发生了变化,两次都是扩充为之前容量的两倍。

但是一定都是两倍扩容吗?事实上不是的,如以下代码:

s0 := make([]int, 1000)
fmt.Printf("len: %d, cap: %d\n", len(s0), cap(s0))
s0 = append(s0, make([]int, 200)...)
fmt.Printf("len: %d, cap: %d\n", len(s0), cap(s0))
s0 = append(s0, make([]int, 400)...)
fmt.Printf("len: %d, cap: %d\n", len(s0), cap(s0))

-----结果--------------------------
len: 1000, cap: 1000
len: 1200, cap: 1536
len: 1600, cap: 2304

可以发现第一次扩容后,容量变为 1536,第二次扩容后容量又变成了 2304,并不是什么两倍的关系。

通过查看 append 源码中的容量计算部分

func growslice(et *_type, old slice, cap int) slice {
	...
newcap := old.cap
	doublecap := newcap + newcap
	if cap > doublecap {
		newcap = cap
	} else {
		const threshold = 256
		if old.cap < threshold {
			newcap = doublecap //小容量直接扩容到两倍容量
		} else {
			// Check 0 < newcap to detect overflow
			// and prevent an infinite loop.
			for 0 < newcap && newcap < cap {
				// Transition from growing 2x for small slices
				// to growing 1.25x for large slices. This formula
				// gives a smooth-ish transition between the two.
				//大容量取消了 1.25 倍扩容,选择了一个更为平滑的扩容方案
				newcap += (newcap + 3*threshold) / 4
			}
			// Set newcap to the requested cap when
			// the newcap calculation overflowed.
			if newcap <= 0 {
				newcap = cap
			}
		}
	}
	var overflow bool
	var lenmem, newlenmem, capmem uintptr
	// Specialize for common values of et.size.
	// For 1 we don't need any division/multiplication.
	// For goarch.PtrSize, compiler will optimize division/multiplication into a shift by a constant.
	// For powers of 2, use a variable shift.
	switch {
	case et.size == 1:
		lenmem = uintptr(old.len)
		newlenmem = uintptr(cap)
		capmem = roundupsize(uintptr(newcap))
		overflow = uintptr(newcap) > maxAlloc
		newcap = int(capmem)
	case et.size == goarch.PtrSize:
		lenmem = uintptr(old.len) * goarch.PtrSize
		newlenmem = uintptr(cap) * goarch.PtrSize
		capmem = roundupsize(uintptr(newcap) * goarch.PtrSize)
		overflow = uintptr(newcap) > maxAlloc/goarch.PtrSize
		newcap = int(capmem / goarch.PtrSize)
	case isPowerOfTwo(et.size):
		var shift uintptr
		if goarch.PtrSize == 8 {
			// Mask shift for better code generation.
			shift = uintptr(sys.Ctz64(uint64(et.size))) & 63
		} else {
			shift = uintptr(sys.Ctz32(uint32(et.size))) & 31
		}
		lenmem = uintptr(old.len) << shift
		newlenmem = uintptr(cap) << shift
		capmem = roundupsize(uintptr(newcap) << shift)
		overflow = uintptr(newcap) > (maxAlloc >> shift)
		newcap = int(capmem >> shift)
	default:
		lenmem = uintptr(old.len) * et.size
		newlenmem = uintptr(cap) * et.size
		capmem, overflow = math.MulUintptr(et.size, uintptr(newcap))
		capmem = roundupsize(capmem)
		newcap = int(capmem / et.size)
	}
	...
    return slice{p, old.len, newcap}
}

从源码中可以得知:

  • 当需要的容量大于两倍旧切片的容量时,需要的容量
  • 就是新容量当需要的容量小于两倍旧切片的容量时, 判断是否旧切片的长度, 如果小于 256 , 那么新的容量就是两倍旧的容量,当大于等于 256 时, 会选择一个过度算法 newcap += (newcap + 3*256) / 4 不断增加,直至大于等于需要的容量
  • 特殊的一点是,后面的 capmem = roundupsize(uintptr(newcap) * et.size) 这个方法,做了内存对齐,导致最后算出的容量大于等于推算出来的容量,至于内存对齐都做了哪些操作,还有待研究。

到此这篇关于Golang数组与切片轻松掌握的文章就介绍到这了,更多相关Golang数组和切片内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 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

  • 浅析Go语言容器之数组和切片的使用

    目录 序列容器 数组 Vector Deque List 单链表 总结 在 Java 的核心库中,集合框架可谓鼎鼎大名:Array .List.Set.Queue.HashMap 等等,随便拎一个出来都值得开发者好好学习如何使用甚至是背后的设计源码(这类文章也挺多,大家上网随便一搜). 虽然 Go 语言没有如此丰富的容器类型,但也有一些基本的容器供开发者使用,接下来让我们一一认识这些容器类型吧. 序列容器 序列容器存储特定类型的数据元素.目前有 5 种序列容器的实现: array vector

  • 详解Go语言中数组,切片和映射的使用

    目录 1.Arrays (数组) 2.切片 2.1 make创建切片 3.映射Map Arrays (数组), Slices (切片) 和 Maps (映射) 是常见的一类数据结构 1.Arrays (数组) 数组是定长的. 长度不可改变. 初始化 package main import ( "fmt" ) func main() { var scores [10]int scores[0] = 99 fmt.Printf("scoers:%d\n", scores

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

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

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

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

  • 详细介绍Go语言之数组与切片

    目录 一.数组 1.数组的定义 2.数组赋值 3.定义并初始化 4.数组的大小是类型的一部分 5.数组是值类型 6.数组长度 len() 数组长度在定义阶段已经固定 7.数组循环 8.多维数组 9.数组定义并指定位置初始化 二.切片基础 1.切片的定义 2.使用切片 3.修改切片,会影响数组 4.修改数组也会影响切片 5.切片只切数组的一部分 6.当多个切片共用相同的底层数组时,每个切片所做的更改将反应在数组中 7.切片的长度和容量 8.切片追加值 一.数组 数组是同一类型元素的集合,可以放多个

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

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

  • Go数组与切片轻松掌握

    目录 数组(array) 初始化数组 数组赋值 遍历数组 数组对比 切片(slice) 切片的性质 切片初始化 切片赋值 切片的容量 append以及扩容 在 Go 中,数组和切片的功能其实是类似的,都是用来存储一种类型元素的集合.数组是固定长度的,而切片的长度是可以调整的 数组(array) 我们在声明一个数组的时候据必须要定义它的长度,并且不能修改. 数组的长度是其类型的一部分:比如,[2]int 和 [4]int 是两个不同的数组类型. 初始化数组 // 1. 创建一维数组 // 元素都是

  • GO语言数组和切片实例详解

    本文实例讲述了GO语言数组和切片的用法.分享给大家供大家参考.具体分析如下: 一.数组 与其他大多数语言类似,Go语言的数组也是一个元素类型相同的定长的序列. (1)数组的创建. 数组有3种创建方式:[length]Type .[N]Type{value1, value2, ... , valueN}.[...]Type{value1, value2, ... , valueN} 如下: 复制代码 代码如下: func test5() {     var iarray1 [5]int32    

  • 对Python 数组的切片操作详解

    高级特性 切片操作:对list,tuple元素进行截取操作,非常简便. L[0:3],L[:3] 截取前3个元素. L[1:3] 从1开始截取2个元素出来. L[-1] 取倒数第一个元素出来. L[-10] 取后10个数 L[10:20] 取前11-20个数 L[:10:2] 取前10个数,每两个取一个 L[::5] 所有数,每5个取一个 L[:] 原样复制一个list tuple,字符串也可以进行切片操作 以上这篇对Python 数组的切片操作详解就是小编分享给大家的全部内容了,希望能给大家一

  • 把csv文件转化为数组及数组的切片方法

    在Python中我们经常会用到两个库Numpy和pandas csv文件转化为数组 import numpy my_matrix = numpy.loadtxt(open("c:\\1.csv","rb"),delimiter=",",skiprows=0) //CSV文件转化为数组 将数组或者矩阵存储为csv文件可以使用如下代码实现: numpy.savetxt('new.csv', my_matrix, delimiter = ',') 数组

  • go特性之数组与切片的问题

    数组: 复制传递(不要按照c/c++的方式去理解,c/c++中数组是引用传递),定长 切片: 引用传递,底层实现是3个字段 array(数组) + len(长度) +cap(容量) go/src/runtime/slice.go slice结构定义: type slice struct { array unsafe.Pointer len int cap int } 要特别注意的是,切片的引用传递指的是切片传递时,切片的array字段是引用传递的,len和cap字段依然是赋值传递. 写个伪代码:

  • 简单聊一聊Go语言中的数组和切片

    目录 1. 数组 2. 切片(Slice) append 函数 总结 1. 数组 数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成.因为数组的长度是固定的,因此在 Go 语言中很少直接使用数组.和数组对应的类型是 Slice(切片),它是可以增长和收缩的动态序列,slice 功能也更灵活. 数组的每个元素可以通过索引下标来访问,索引下标的范围是从 0 开始到数组长度减 1 的位置.内置的 len 函数将返回数组中元素的个数. var a [3]int // arra

  • 浅谈Go数组比切片好在哪

    目录 数组是什么 切片是什么 数组的优势 可比较 编译安全 长度是类型 规划内存布局 访问速度 总结 参考 前段时间有播放一条快讯,就是 Go1.17 会正式支持切片(Slice)转换到数据(Array),不再需要用以前那种骚办法了,安全了许多. 但是也有同学提出了新的疑惑,在 Go 语言中,数组其实是用的相对较少的,甚至会有同学认为在 Go 里可以把数组给去掉. 数组相较切片到底有什么优势,我们又应该在什么场景下使用呢? 这是一个我们需要深究的问题,因此今天就跟大家一起来一探究竟,本文会先简单

随机推荐