Golang切片Slice功能操作详情

目录
  • 一、概述
  • 二、切片
    • 2.1 切片的定义
    • 2.2 切片的长度和容量
    • 2.3 切片表达式
      • 简单切片表达式
      • 完整切片表达式
    • 2.4 使用make()函数构造切片
    • 2.5 for range循环迭代切片
    • 2.6 切片的本质
    • 2.7 判断切片是否为空
  • 三、切片功能操作
    • 3.1 切片不能直接比较
    • 3.2 切片的赋值拷贝
    • 3.3 使用copy()函数复制切片
    • 3.4 append()方法为切片添加元素
    • 3.5 从切片中删除元素
      • 从开头位置删除
      • 从中间位置删除
      • 从尾部删除
    • 3.6 切片的扩容策略

一、概述

切片(Slice)是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容

切片是一个引用类型,它的内部结构包含地址长度容量。切片一般用于快速地操作一块数据集合。

二、切片

2.1 切片的定义

声明切片类型的基本语法如下:

var name []T

说明

  • name:表示变量名
  • T:表示切片中的元素类型

示例:

func main() {
	// 声明切片类型
	var a []string              //声明一个字符串切片
	var b = []int{}             //声明一个整型切片并初始化
	var c = []bool{false, true} //声明一个布尔切片并初始化
	var d = []bool{false, true} //声明一个布尔切片并初始化
	fmt.Println(a)              //[]
	fmt.Println(b)              //[]
	fmt.Println(c)              //[false true]
	fmt.Println(a == nil)       //true
	fmt.Println(b == nil)       //false
	fmt.Println(c == nil)       //false
	// fmt.Println(c == d)   //切片是引用类型,不支持直接比较,只能和nil比较
}

2.2 切片的长度和容量

一个 slice 由三个部分构成:指针 、 长度 和 容量 。

指针指向第一个 slice 元素对应的底层数组元素的地址,要注意的是 slice 的第一个元素并不一定就是数组的第一个元素。

长度对应 slice 中元素的数目;长度不能超过容量,容量一般是从 slice 的开始位置到底层数据的结尾位置。

简单的讲,容量就是从创建切片索引开始的底层数组中的元素个数,而长度是切片中的元素个数。

内置的 len 和 cap 函数分别返回 slice 的长度和容量。

s := make([]string, 3, 5)
fmt.Println(len(s)) // 3
fmt.Println(cap(s)) // 5

如果切片操作超出上限将导致一个 panic 异常。

s := make([]int, 3, 5)
fmt.Println(s[10]) //panic: runtime error: index out of range [10] with length 3

2.3 切片表达式

切片表达式从字符串、数组、指向数组或切片的指针构造子字符串或切片。

它有两种变体:

  • 一种指定low和high两个索引界限值的简单的形式
  • 另一种是除了low和high索引界限值外还指定容量完整的形式。

简单切片表达式

切片的底层就是一个数组,所以我们可以基于数组通过切片表达式得到切片。

切片表达式中的lowhigh表示一个索引范围(左包含,右不包含),也就是下面代码中从数组a中选出1<=索引值<4的元素组成切片s,得到的切片长度=high-low容量等于得到的切片的底层数组的容量

func main() {
	a := [5]int{1, 2, 3, 4, 5}
	s := a[1:3]  // s := a[low:high]
	fmt.Printf("s:%v len(s):%v cap(s):%v\n", s, len(s), cap(s))
}

运行结果:

s:[2 3] len(s):2 cap(s):4

为了方便起见,可以省略切片表达式中的任何索引。

省略了low则默认为0;省略了high则默认为切片操作数的长度:

a[2:]  // 等同于 a[2:len(a)]
a[:3]  // 等同于 a[0:3]
a[:]   // 等同于 a[0:len(a)]

注意:

对于数组或字符串,如果0 <= low <= high <= len(a),则索引合法,否则就会索引越界(out of range)。

对切片再执行切片表达式时(切片再切片),high的上限边界是切片的容量cap(a),而不是长度。

常量索引必须是非负的,并且可以用int类型的值表示;对于数组或常量字符串,常量索引也必须在有效范围内。

如果lowhigh两个指标都是常数,它们必须满足low <= high。如果索引在运行时超出范围,就会发生运行时panic

func main() {
	a := [5]int{1, 2, 3, 4, 5}
	s := a[1:3]  // s := a[low:high]
	fmt.Printf("s:%v len(s):%v cap(s):%v\n", s, len(s), cap(s))
	s2 := s[3:4]  // 索引的上限是cap(s)而不是len(s)
	fmt.Printf("s2:%v len(s2):%v cap(s2):%v\n", s2, len(s2), cap(s2))
}

输出:

s:[2 3] len(s):2 cap(s):4
s2:[5] len(s2):1 cap(s2):1

完整切片表达式

对于数组,指向数组的指针,或切片a(注意不能是字符串)支持完整切片表达式:

a[low : high : max]

上面的代码会构造与简单切片表达式a[low: high]相同类型、相同长度和元素的切片。另外,它会将得到的结果切片的容量设置为max-low。在完整切片表达式中只有第一个索引值(low)可以省略;它默认为0。

func main() {
	a := [5]int{1, 2, 3, 4, 5}
	t := a[1:3:5]
	fmt.Printf("t:%v len(t):%v cap(t):%v\n", t, len(t), cap(t))
}

运行结果:

t:[2 3] len(t):2 cap(t):4

完整切片表达式需要满足的条件是0 <= low <= high <= max <= cap(a),其他条件和简单切片表达式相同。

2.4 使用make()函数构造切片

上面都是基于数组来创建的切片,如果需要动态的创建一个切片,就需要使用内置的make()函数,

格式如下:

make([]T, size, cap)

说明:

  • T:切片的元素类型
  • size:切片中元素的数量
  • cap:切片的容量

示例:

func main() {
	a := make([]int, 2, 10)
	fmt.Println(a)      //[0 0]
	fmt.Println(len(a)) //2
	fmt.Println(cap(a)) //10
}

上面代码中a的内部存储空间已经分配了10个,但实际上只用了2个。 容量并不会影响当前元素的个数,所以len(a)返回2,cap(a)则返回该切片的容量。

提示:

使用 make() 函数生成的切片一定发生了内存分配操作,但给定开始与结束位置(包括切片复位)的切片只是将新的切片结构指向已经分配好的内存区域,设定开始与结束位置,不会发生内存分配操作。

2.5 for range循环迭代切片

for range可以用来迭代切片里的每一个元素,如下所示:

func main(){
	// 创建一个整型切片,并赋值
	slice := []int{10, 20, 30, 40}
	// 迭代每一个元素,并显示其值
	for index, value := range slice {
		fmt.Printf("Index: %d Value: %d\n", index, value)
	}
}

运行结果:

Index: 0 Value: 10
Index: 1 Value: 20
Index: 2 Value: 30
Index: 3 Value: 40

  • index:表示每一个元素的索引
  • value:表示每一个元素的值

当迭代切片时,for range 会返回两个值,第一个值是当前迭代到的索引位置,第二个值是该位置对应元素值的一份副本,如下图所示。

注意 for range 返回的是每个元素的副本,而不是直接返回对该元素的引用。

示例:

func main(){
	// 创建一个整型切片,并赋值
	slice := []int{10, 20, 30, 40}
	// 迭代每个元素,并显示值和地址
	for index, value := range slice {
		fmt.Printf("Value: %d Value-Addr: %X ElemAddr: %X\n", value, &value, &slice[index])
	}
}

运行结果:

Value: 10 Value-Addr: C00009E058 ElemAddr: C00009C120
Value: 20 Value-Addr: C00009E058 ElemAddr: C00009C128
Value: 30 Value-Addr: C00009E058 ElemAddr: C00009C130
Value: 40 Value-Addr: C00009E058 ElemAddr: C00009C138

因为迭代返回的变量是一个在迭代过程中根据切片依次赋值的新变量,所以 value 的地址总是相同的,要想获取每个元素的地址,需要使用切片变量和索引值(例如上面代码中的 &slice[index])。

如果不需要索引值,也可以使用下划线_来忽略这个值,

代码如下所示:

func main(){
	// 创建一个整型切片,并赋值
	slice := []int{10, 20, 30, 40}
	// 迭代每个元素,并显示其值
	for _, value := range slice {
		fmt.Printf("Value: %d\n", value)
	}
}

运行结果;

Value: 10
Value: 20
Value: 30
Value: 40

for range 总是会从切片头部开始迭代。如果想对迭代做更多的控制,则可以使用传统的 for 循环,

代码如下所示:

func main(){
	// 创建一个整型切片,并赋值
	slice := []int{10, 20, 30, 40}
	// 从第三个元素开始迭代每个元素
	for index := 2; index < len(slice); index++ {
		fmt.Printf("Index: %d Value: %d\n", index, slice[index])
	}
}

运行结果:

Index: 2 Value: 30
Index: 3 Value: 40

for range不仅仅可以用来遍历切片,它还可以用来遍历数组、字符串、map 或者通道等。

2.6 切片的本质

切片的本质就是对底层数组的封装,它包含了三个信息:底层数组的指针、切片的长度(len)和切片的容量(cap)。

举个例子,现在有一个数组a := [8]int{0, 1, 2, 3, 4, 5, 6, 7},切片s1 := a[:5]

相应示意图如下:

切片s2 := a[3:6]相应示意图如下:

2.7 判断切片是否为空

要检查切片是否为空,请始终使用len(s) == 0来判断,而不应该使用s == nil来判断。

三、切片功能操作

3.1 切片不能直接比较

切片之间是不能比较的,我们不能使用==操作符来判断两个切片是否含有全部相等元素。 切片唯一合法的比较操作是和nil比较。 一个nil值的切片并没有底层数组,一个nil值的切片的长度和容量都是0。

但是我们不能说一个长度和容量都是0的切片一定是nil

例如下面的示例:

var s1 []int         //len(s1)=0;cap(s1)=0;s1==nil
s2 := []int{}        //len(s2)=0;cap(s2)=0;s2!=nil
s3 := make([]int, 0) //len(s3)=0;cap(s3)=0;s3!=nil

所以要判断一个切片是否是空的,要是用len(s) == 0来判断,不应该使用s == nil来判断。

3.2 切片的赋值拷贝

下面的代码中演示了拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容,这点需要特别注意。

func main() {
	s1 := make([]int, 3) //[0 0 0]
	s2 := s1             //将s1直接赋值给s2,s1和s2共用一个底层数组
	s2[0] = 100
	fmt.Println(s1) //[100 0 0]
	fmt.Println(s2) //[100 0 0]
}

由于切片是引用类型,所以s1和s2其实都指向了同一块内存地址。修改s2的同时s1的值也会发生变化。

3.3 使用copy()函数复制切片

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

copy( destSlice, srcSlice []T) int
  • srcSlice: 数据来源切片
  • destSlice: 目标切片

copy()函数就是将 srcSlice 复制到 destSlice,目标切片必须分配过空间且足够承载复制的元素个数,并且来源和目标的类型必须一致,copy() 函数的返回值表示实际发生复制的元素个数。

示例

func main(){
	slice1 := []int{1, 2, 3, 4, 5}
	slice2 := []int{5, 4, 3}
	//copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中
	copy(slice1, slice2) // 只会复制slice2的3个元素到slice1的前3个位置
	for _, value := range slice1 {
		fmt.Printf("%d \t", value)
	}
	//for _, value := range slice2 {
	//	fmt.Printf("%d \t", value)
	//}
}

虽然通过循环复制切片元素更直接,不过内置的 copy() 函数使用起来更加方便,copy() 函数的第一个参数是要复制的目标 slice,第二个参数是源 slice,两个 slice 可以共享同一个底层数组,甚至有重叠也没有问题。

示例:

func main() {
	// 设置元素数量为1000
	const elementCount = 1000
	// 预分配足够多的元素切片
	srcData := make([]int, elementCount)
	// 将切片赋值
	for i := 0; i < elementCount; i++ {
		srcData[i] = i
	}
	// 引用切片数据
	refData := srcData
	// 预分配足够多的元素切片
	copyData := make([]int, elementCount)
	// 将数据复制到新的切片空间中
	copy(copyData, srcData)
	// 修改原始数据的第一个元素
	srcData[0] = 999
	// 打印引用切片的第一个元素
	fmt.Println(refData[0])
	// 打印复制切片的第一个和最后一个元素
	fmt.Println(copyData[0], copyData[elementCount-1])
	// 复制原始数据从4到6(不包含)
	copy(copyData, srcData[4:6])
	for i := 0; i < 5; i++ {
		fmt.Printf("%d ", copyData[i])
	}
}

运行结果:

999
0 999
4 5 2 3 4

3.4 append()方法为切片添加元素

Go语言的内建函数append()可以为切片动态添加元素。 可以一次添加一个元素,可以添加多个元素,也可以添加另一个切片中的元素(后面加…)。

func main(){
	var s []int
	s = append(s, 1)        // [1]
	s = append(s, 2, 3, 4)  // [1 2 3 4]
	s2 := []int{5, 6, 7}
	s = append(s, s2...)    // [1 2 3 4 5 6 7]
}

注意:通过var声明的零值切片可以在append()函数直接使用,无需初始化。

var s []int
s = append(s, 1, 2, 3)

没有必要像下面的代码一样初始化一个切片再传入append()函数使用。

s := []int{}  // 没有必要初始化
s = append(s, 1, 2, 3)

var s = make([]int)  // 没有必要初始化
s = append(s, 1, 2, 3)

每个切片会指向一个底层数组,这个数组的容量够用就添加新增元素。当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略进行“扩容”,此时该切片指向的底层数组就会更换。“扩容”操作往往发生在append()函数调用时,所以我们通常都需要用原变量接收append函数的返回值。

示例:

func main() {
	//append()添加元素和切片扩容
	var numSlice []int
	for i := 0; i < 10; i++ {
		numSlice = append(numSlice, i)
		fmt.Printf("%v  len:%d  cap:%d  ptr:%p\n", numSlice, len(numSlice), cap(numSlice), numSlice)
	}
}

运行结果:

[0]  len:1  cap:1  ptr:0xc00009e058
[0 1]  len:2  cap:2  ptr:0xc00009e0a0
[0 1 2]  len:3  cap:4  ptr:0xc00009c140
[0 1 2 3]  len:4  cap:4  ptr:0xc00009c140
[0 1 2 3 4]  len:5  cap:8  ptr:0xc0000b2100
[0 1 2 3 4 5]  len:6  cap:8  ptr:0xc0000b2100
[0 1 2 3 4 5 6]  len:7  cap:8  ptr:0xc0000b2100
[0 1 2 3 4 5 6 7]  len:8  cap:8  ptr:0xc0000b2100
[0 1 2 3 4 5 6 7 8]  len:9  cap:16  ptr:0xc0000d0080
[0 1 2 3 4 5 6 7 8 9]  len:10  cap:16  ptr:0xc0000d0080

从上面的结果可以看出:

  • append()函数将元素追加到切片的最后并返回该切片。
  • 切片numSlice的容量按照1,2,4,8,16这样的规则自动进行扩容,每次扩容后都是扩容前的2倍。

append()函数还支持一次性追加多个元素。

示例

var citySlice []string
// 追加一个元素
citySlice = append(citySlice, "北京")
// 追加多个元素
citySlice = append(citySlice, "上海", "广州", "深圳")
// 追加切片
a := []string{"成都", "重庆"}
citySlice = append(citySlice, a...)
fmt.Println(citySlice) //[北京 上海 广州 深圳 成都 重庆]

3.5 从切片中删除元素

Go语言并没有对删除切片元素提供专用的语法或者接口,需要使用切片本身的特性来删除元素,根据要删除元素的位置有三种情况,分别是从开头位置删除、从中间位置删除和从尾部删除,其中删除切片尾部的元素速度最快。

从开头位置删除

删除开头的元素可以直接移动数据指针:

a = []int{1, 2, 3}
a = a[1:] // 删除开头1个元素
a = a[N:] // 删除开头N个元素

也可以不移动数据指针,但是将后面的数据向开头移动,可以用 append 原地完成(所谓原地完成是指在原有的切片数据对应的内存区间内完成,不会导致内存空间结构的变化):

a = []int{1, 2, 3}
a = append(a[:0], a[1:]...) // 删除开头1个元素
a = append(a[:0], a[N:]...) // 删除开头N个元素

还可以用 copy() 函数来删除开头的元素:

a = []int{1, 2, 3}
a = a[:copy(a, a[1:])] // 删除开头1个元素
a = a[:copy(a, a[N:])] // 删除开头N个元素

从中间位置删除

对于删除中间的元素,需要对剩余的元素进行一次整体挪动,同样可以用 append 或 copy 原地完成:

a = []int{1, 2, 3, ...}
a = append(a[:i], a[i+1:]...) // 删除中间1个元素
a = append(a[:i], a[i+N:]...) // 删除中间N个元素
a = a[:i+copy(a[i:], a[i+1:])] // 删除中间1个元素
a = a[:i+copy(a[i:], a[i+N:])] // 删除中间N个元素

从尾部删除

a = []int{1, 2, 3}
a = a[:len(a)-1] // 删除尾部1个元素
a = a[:len(a)-N] // 删除尾部N个元素

删除开头的元素和删除尾部的元素都可以认为是删除中间元素操作的特殊情况,下面来看一个示例。

示例:

删除切片指定位置的元素

func main(){
	seq := []string{"a", "b", "c", "d", "e"}

	// 指定删除位置
	index := 2

	// 查看删除位置之前的元素和之后的元素
	fmt.Println(seq[:index], seq[index+1:])

	// 将删除点前后的元素连接起来
	seq = append(seq[:index], seq[index+1:]...)

	fmt.Println(seq)
}

运行结果:

[a b] [d e]
[a b d e]

代码的删除过程可以使用下图来描述:

Go语言中删除切片元素的本质是,以被删除元素为分界点,将前后两个部分的内存重新连接起来。

3.6 切片的扩容策略

可以通过查看$GOROOT/src/runtime/slice.go源码,其中扩容相关代码如下:

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
		}
	}
}

从上面的代码可以看出以下内容:

  • 首先判断,如果新申请容量(cap)大于2倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap)。
  • 否则判断,如果旧切片的长度小于1024,则最终容量(newcap)就是旧容量(old.cap)的两倍,即(newcap=doublecap),
  • 否则判断,如果旧切片长度大于等于1024,则最终容量(newcap)从旧容量(old.cap)开始循环增加原来的1/4,即(newcap=old.cap,for {newcap += newcap/4})直到最终容量(newcap)大于等于新申请的容量(cap),即(newcap >= cap)
  • 如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)。

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

到此这篇关于Golang切片Slice功能操作详情的文章就介绍到这了,更多相关Golang切片内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • GoLang切片相关问题梳理讲解

    目录 1.数组和切片有什么区别 2.拷贝大切片一定比拷贝小切片代价大吗 3.切片的深浅拷贝 4.零切片 空切片 nil切片是什么 4.1零切片 4.2nil切片 4.3空切片 5.切片的扩容策略 1.17之前 1.18之后 6. 参数传递切片和切片指针有什么区别 7.range遍历切片有什么要注意的 1.数组和切片有什么区别 Go语言中数组是固定长度的,不能动态扩容,在编译期就会确定大小,声明方式如下: var buffer [255]int buffer := [255]int{0} 切片是对

  • golang切片原理详细解析

    目录 切片的解析 切片的初始化 字面量初始化 make初始化 切片的截取 切片的复制 切片的扩容 总结 切片的解析 当我们的代码敲下[]时,便会被go编译器解析为抽象语法树上的切片节点, 被初始化为切片表达式SliceType: // go/src/cmd/compile/internal/syntax/parser.go // TypeSpec = identifier [ TypeParams ] [ "=" ] Type . func (p *parser) typeDecl(g

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

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

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

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

  • GoLang切片并发安全解决方案详解

    目录 1.介绍切片并发问题 2.实践检验真理 3.回答切片并发安全问题 4.解决切片并发安全问题方式 5.附 1.介绍切片并发问题 关于切片的,Go语言中的切片原生支持并发吗? 2.实践检验真理 实践是检验真理的唯一标准,所以当我们遇到一个不确定的问题,直接写demo来验证,因为切片的特点,我们可以分多种情况来验证 1.不指定索引,动态扩容并发向切片添加数据 2.指定索引,指定容量并发向切片添加数据 不指定索引,动态扩容并发向切片添加数据 不指定索引,动态扩容并发向切片添加数据: 通过打印数据发

  • 浅谈Golang Slice切片如何扩容的实现

    目录 一.Slice数据结构是什么? 二.详细代码 1.数据结构 2.扩容原则 3.如何理解扩容规则一 1.当小于1024个元素时 2.当大于1024个元素时 4.如何理解扩容规则二 1.简单理解内存地址更换 总结 一.Slice数据结构是什么? 切片(slice)是 Golang 中一种比较特殊的数据结构,这种数据结构更便于使用和管理数据集合.切片是围绕动态数组的概念构建的,可以按需自动增长和缩小.切片(slice)是可以看做是一个长度可变的数组.切片(slice)自身并不是动态数组或者数组指

  • Golang切片删除指定元素的三种方法对比

    目录 前言 1.截取法(修改原切片) 2.拷贝法(不改原切片) 3.移位法(修改原切片) 3.1 方式一 3.2 方式二 4.性能对比 5.小结 前言 Go 并没有提供删除切片元素专用的语法或函数,需要使用切片本身的特性来删除元素. 删除切片指定元素一般有如下几种方法,本文以 []int 为例给出具体实现. 1.截取法(修改原切片) 这里利用对 slice 的截取删除指定元素.注意删除时,后面的元素会前移,所以下标 i 应该左移一位. // DeleteSlice1 删除指定元素. func D

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

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

  • 浅谈Golang 切片(slice)扩容机制的原理

    我们知道 Golang 切片(slice) 在容量不足的情况下会进行扩容,扩容的原理是怎样的呢?是不是每次扩一倍?下面我们结合源码来告诉你答案. 一.源码 Version : go1.15.6  src/runtime/slice.go //go1.15.6 源码 src/runtime/slice.go func growslice(et *_type, old slice, cap int) slice { //省略部分判断代码 //计算扩容部分 //其中,cap : 所需容量,newcap

  • Golang切片Slice功能操作详情

    目录 一.概述 二.切片 2.1 切片的定义 2.2 切片的长度和容量 2.3 切片表达式 简单切片表达式 完整切片表达式 2.4 使用make()函数构造切片 2.5 for range循环迭代切片 2.6 切片的本质 2.7 判断切片是否为空 三.切片功能操作 3.1 切片不能直接比较 3.2 切片的赋值拷贝 3.3 使用copy()函数复制切片 3.4 append()方法为切片添加元素 3.5 从切片中删除元素 从开头位置删除 从中间位置删除 从尾部删除 3.6 切片的扩容策略 一.概述

  • 浅析Golang切片截取功能与C++的vector区别

    目录 1. 引言 2.分析过程 2.1 s[:]的方式截取元素 2.2 append的方式截取元素 3. 结论 浅析golang切片截取(删除)功能 1. 引言 golang的切片被认为是和C++的vector容器类似,都可以认为是动态数组,但又不完全一样. 那么区别到底在哪里呢?对元素的删除方式是很重要的一点区别 对于C++的vector来说,用erase函数来删除元素,其原理是将当前位置后面的元素都向前移动一位,删除一个元素的平均时间复杂度为O(n) 对于golang的slice来说,没有用

  • Golang文件读写操作详情

    目录 一.概念 二.读取文件操作 2.1 打开和关闭文件 defer 语句 2.2 file.Read() 读取文件 Read 方法定义 ReadAt方法定义 一次性读取 循环读取 2.3 bufio 读取文件 2.4 ioutil 读取文件 效率比较 三.写入文件操作 3.1 os.OpenFile()函数 3.2 Write和WriteString 方式写入 3.3 bufio.NewWriter 3.4 ioutil.WriteFile 四.复制文件 4.1 通过ioutil进行复制 4.

  • JS数组操作(数组增加、删除、翻转、转字符串、取索引、截取(切片)slice、剪接splice、数组合并)

    POP 删除最后一项 删除最后一项,并返回删除元素的值:如果数组为空则返回undefine var a = [1,2,3,4,5]; a.pop();//a:[1, 2, 3, 4] a.pop();//a:[1, 2, 3] a.pop();//a:[1, 2] shift 删除第一项 删除原数组第一项,并返回删除元素的值:如果数组为空则返回undefine var a = [1,2,3,4,5]; a.shift(); //a:[2,3,4,5] a.shift(); //a:[3, 4,

  • golang切片反序实例

    看代码吧~ package main import ( "fmt" ) func main() { fmt.Println(reverse([]byte{11,22,33,44})) } func reverse(s []byte) []byte { for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { s[i], s[j] = s[j], s[i] } return s } 补充:golang切片内存应用技巧 在 Go 语言中切片是

  • golang 切片截取参数方法详解

    以 s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}为例 0. 建议:做slice截取时建议用两个参数,尤其是从底层数组进行切片操作时,因为这样在进行第一次append操作时,会给切片重新分配空间,这样减少切片对数组的影响. 1. 结论:s = s[low : high : max] 切片的三个参数的切片截取的意义为 low为截取的起始下标(含), high为窃取的结束下标(不含high),max为切片保留的原切片的最大下标(不含max):即新切片从老切片的low

  • Python 字符串操作详情

    目录 1.字符串的定义 2.转义字符串和原始字符串 4.字符串的运算 4.1 拼接运算符 4.2 成员运算 5.获取字符串长度 6.索引和切片 7.字符串的方法 7.1 转换大小写 7.2 查找操作 7.3性质判断 7.4格式化字符串 8.修剪操作 1.字符串的定义 所谓字符串,就是由0个或者多个字符组成的有限序列. 在Python程序中,如果我们把单个或多个字符用单引号''或者双引号""包裹起来,就可以表示一个字符串,也可以用三个单引号或者双引号进行折行.字符串的字符可以是特殊符号.

随机推荐