Go语言【数据结构】切片
切片
简介
简单地说,切片就是一种简化版的动态数组。Go 数组的长度不可改变,而切片长度是不固定,切片的长度自然也就不能是类型的组成部分了。数组虽然有适用它们的地方,但是数组的类型和操作都不够灵活,因此在Go代码中数组使用的并不多。而切片则使用得相当广泛,理解切片的原理和用法是一个Go程序员的必备技能。当进行append与copy函数操作时会对真实数据进行内存拷贝,append的时长度len超过申请的空间cap进行内存真实拷贝
初始化
package main import "fmt" func main() {
// 第一种方式 对比数组不指定size大小
var slice1 = [] int{1,2,3,4}
// 第二种方式
slice2 := [] int{1,2,3,4}
// 第三种方式 make生成空切片
var slice3 []int = make([]int,3,5)
// 第四种方式 简写 len=3 cap=5
slice4 := make([]int,3,5) fmt.Println(slice1)
fmt.Println(slice2)
fmt.Println(slice3)
fmt.Println(slice4)
} //[1 2 3 4]
//[1 2 3 4]
//[0 0 0]
//[0 0 0]
数据结构
我们先看看切片的结构定义,reflect.SliceHeader
:
type SliceHeader struct {
// 指向数组内存地址 赋值时拷贝的是数组地址
Data uintptr
// 长度
Len int
// 申请空间
Cap int
}
可以看出切片的开头部分和Go字符串是一样的,但是切片多了一个Cap
成员表示切片指向的内存空间的最大容量(对应元素的个数,而不是字节数)。下图是x := []int{2,3,5,7,11}
和y := x[1:3]
两个切片对应的内存结构。
赋值、切片与copy
赋值等同于把结构体SliceHeader 内的变量拷贝了一份,并未进行真实数据拷贝, Data与初始切片指向的是同一块内存地址
package main import "fmt" func main() {
a := [] int{1,2,3,4}
b := a
// Data ptr指向的内存地址一致
fmt.Printf("a[0] %p\n",&a[0])
fmt.Printf("b[0] %p\n",&b[0])
// 修改元素值相互影响
b[0] = 10
fmt.Println(a)
fmt.Println(b)
} //a[0] 0xc000054140
//b[0] 0xc000054140
//[10 2 3 4]
//[10 2 3 4]
切片时,新生成的切片Data指向初始切片位置元素的内存地址,对元素值进行修改时,相互影响
package main import "fmt" func main() {
a := [] int{1,2,3,4}
b := a[:]
// 全切片时Data ptr指向a的第一个元素的内存地址
fmt.Printf("a[0] %p\n",&a[0])
fmt.Printf("b[0] %p\n",&b[0])
// 修改元素值相互影响
b[0] = 10
fmt.Println(a)
fmt.Println(b)
} //a[0] 0xc000054140
//b[0] 0xc000054140
//[10 2 3 4]
//[10 2 3 4]
copy函数进行操作时,会对真实数据进行内存拷贝,新的切片Data指向新的地址
package main import "fmt" func main() {
a := [] int{1,2,3,4}
b := make([]int, len(a), cap(a))
copy(b,a)
// Data ptr指向的新的内存地址
fmt.Printf("a[0] %p\n",&a[0])
fmt.Printf("b[0] %p\n",&b[0])
// 修改元素值相互不影响
b[0] = 10
fmt.Println(a)
fmt.Println(b)
} //a[0] 0xc000054140
//b[0] 0xc000054160
//[1 2 3 4]
//[10 2 3 4]
添加元素
内置的泛型函数append
可以在切片的尾部追加N
个元素:
package main import "fmt" func main() {
var a []int
// 追加1个元素
a = append(a, 1)
// 追加多个元素, 手写解包方式
a = append(a, 1, 2, 3)
// 追加一个切片, 切片需要解包
a = append(a, []int{1,2,3}...) fmt.Println(a)
} //[1 1 2 3 1 2 3]
不过要注意的是,在容量不足的情况下,append
的操作会导致重新分配内存,可能导致巨大的内存分配和复制数据代价。即使容量足够,依然需要用append
函数的返回值来更新切片本身,因为新切片的长度已经发生了变化。
除了在切片的尾部追加,我们还可以在切片的开头添加元素:
package main import "fmt" func main() {
var a = []int{1,2,3}
// 在开头添加1个元素
a = append([]int{0}, a...)
// 在开头添加1个切片
a = append([]int{-3,-2,-1}, a...) fmt.Println(a)
} //[-3 -2 -1 0 1 2 3]
在开头一般都会导致内存的重新分配,而且会导致已有的元素全部复制1次。因此,从切片的开头添加元素的性能一般要比从尾部追加元素的性能差很多。
由于append
函数返回新的切片,也就是它支持链式操作。我们可以将多个append
操作组合起来,实现在切片中间插入元素:
package main import "fmt" func main() {
var a []int
// 在第i个位置插入x
a = append(a[:i], append([]int{x}, a[i:]...)...)
// 在第i个位置插入切片
a = append(a[:i], append([]int{1,2,3}, a[i:]...)...)
} //[-3 -2 -1 0 1 2 3]
append与内存地址变化
当指向append操作时,会对切片的真实数据进行内存拷贝,与初始切片互不影响
package main import "fmt" func main() {
a := [] int{1,2,3,4}
b := append(a, 5)
// 执行append时 Data ptr指向全新的内存地址
fmt.Printf("a[0] %p\n",&a[0])
fmt.Printf("b[0] %p\n",&b[0])
// 修改元素值相互不影响
b[0] = 10
fmt.Println(a)
fmt.Println(b)
} //a[0] 0xc000054140
//b[0] 0xc00006e100
//[1 2 3 4]
//[10 2 3 4 5]
删除元素
利用切片和append操作组合进行删除
package main import "fmt" func main() {
a := []int{1, 2, 3, 4, 5}
// 删除尾部1个元素
a = a[:len(a)-1]
fmt.Println(a)
// 删除头部1个元素
a = a[1:len(a)]
fmt.Println(a)
// 删除中间一个元素
a = append(a[:1],a[2:]...)
fmt.Println(a)
} //[1 2 3 4]
//[2 3 4]
//[2 4]
函数传参
切片作为参数传递是,与赋值一致,只拷贝了结构体中的变量,Data指向的是同一块地址
package main import "fmt" func change(list []int){
// 拷贝了 Data ptr指向的内存地址
fmt.Printf("list %p\n",&list[0])
// 对切片进行修改
list[0] = 100
} func main() {
list := [] int{1,2,3,4}
fmt.Printf("list %p\n",&list[0])
change(list)
// slice 受影响
fmt.Println(list)
} //list 0xc000054140
//list 0xc000054140
//[100 2 3 4]
Cap超出时才会重新内存分配
package main import "fmt" func main() {
slice := []int{1,2,3,4}
slice1 := slice
slice2 := append(slice[:2],5) fmt.Println(slice2)
fmt.Println(slice1) fmt.Println(&slice2[2])
fmt.Println(&slice1[2])
} //[1 2 5]
//[1 2 5 4]
//0xc00000e370
//0xc00000e370
切片与Cap之间的关系
package main import "fmt" func main() {
// len = cap
a := [] int {1,2,3,4,5}
fmt.Printf("len(a)=%d,cap(a)=%d\n",len(a),cap(a)) b := a[1:3]
fmt.Println("b =",b)
fmt.Printf("len(b)=%d,cap(b)=%d\n",len(b),cap(b)) c := a[:]
fmt.Println("c =",c)
fmt.Printf("len(c)=%d,cap(c)=%d\n",len(c),cap(c)) d := a[:4]
fmt.Println("d =",d)
fmt.Printf("len(d)=%d,cap(d)=%d\n",len(d),cap(d)) // len != cap
e := d[:2]
fmt.Println("e =",e)
fmt.Printf("len(e)=%d,cap(e)=%d\n",len(e),cap(e)) f := d[1:3]
fmt.Println("f =",f)
fmt.Printf("len(f)=%d,cap(f)=%d\n",len(f),cap(f))
} //len(a)=5,cap(a)=5
//b = [2 3]
//len(b)=2,cap(b)=4
//c = [1 2 3 4 5]
//len(c)=5,cap(c)=5
//d = [1 2 3 4]
//len(d)=4,cap(d)=5
//e = [1 2]
//len(e)=2,cap(e)=5
//f = [2 3]
//len(f)=2,cap(f)=4
Go语言【数据结构】切片的更多相关文章
- go语言 rune切片
go语言 rune切片 示例 package main import ( "fmt" ) //http://www.cnblogs.com/osfipin/ func main() ...
- go语言之切片即动态数组
切片和数组的类型有什么不一样,我们可以打印一下,就可以知道两者的区别了,数组是容量的,所以中括号中有容量,切片的动态数组,是没有容量,这是数组和切片最大的区别 test8_4 := [20] int ...
- Python语言数据结构和语言结构(2)
目录 1. Python预备基础 2. Python数据类型 3. Python条件语句 4. while循环和for循环 1. Python预备基础 1.1 变量的命名 变量命名规则主要有以下几 ...
- go中的数据结构切片-slice
1.部分基本类型 go中的类型与c的相似,常用类型有一个特例:byte类型,即字节类型,长度为,默认值是0: bytes = []btye{'h', 'e', 'l', 'l', 'o'} 变量byt ...
- Go语言中切片的内部实现和基础功能
切片是一种数据结构,这种数据结构便于使用和管理数据集合.切片是围绕动态数组的概念构建的,可以按需自动增长和缩小.切片的动态增长是通过内置函数append来实现的.这个函数可以快速且高效的增长切片.还可 ...
- 读谭浩强C语言数据结构有感(1)
1.什么是数据结构? 数据结构,就是我们计算机内部的运算,编程语言的基础工作模式吧,个人总结的 = = !! 数据:说简单一点,就是计算机二进制机器码,然后通过一些复杂的操作,变为复杂的语言. 数据元 ...
- C语言数据结构----栈与递归
本节主要说程序中的栈函数栈的关系以及栈和递归算法的关系. 一.函数调用时的栈 1.程序调用时的栈是也就是平时所说的函数栈是数据结构的一种应用,函数调用栈一般是从搞地质向低地址增长的,栈顶为内存的低地址 ...
- Go语言的切片
Go 语言切片(Slice) Go 语言切片是对数组的抽象. Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片("动态数组" ...
- 第二章 R语言数据结构
R语言存储数据的结构包括:标量.向量.矩阵.数组.数据框和列表:可以处理的数据类型包括:数值型.字符型.逻辑型.复数型和原生型. 数据结构 向量 向量是用来存储数值型.字符型或逻辑型数据的一维数组.单 ...
- C语言---数据结构(内建,数组,自定义)
数组是一组有序数据的集合,每个元素都属于同一个数据类型. 一维数组的定义: 类型符 数组名[常量表达式] 常量表达式中,可以包括常量和符号常量,int a[3+5]是合法的.但是不能包含int a[ ...
随机推荐
- JSON.stringify & JSON.parse 简析
以前用到JSON的场景也不少,但是没有仔细的研究过,这几天趁着一个需求用到了,就整理了一下相关用法. 一. JSON.stringify() 1. 语法 JSON.stringify(value[, ...
- Width Height -- (2)
上回说到,宽高对于块级元素和行内元素发生的改变,结果是块级元素会接受宽高属性所发生的改变的,而行内元素不接受宽高属性所发生的改变. 但是,事无绝对 —— CSS属性display 通过上回进行的测试我 ...
- Vue.js最佳实践--给大量子孙组件传值(provide/inject)
开发中有个需求,有个Parent组件(例如div)下,输入框,下拉框,radiobutton等可编辑的子孙组件几百个,根据某个值统一控制Parent下面的所有控件的disabled状态 类似于这样,给 ...
- android studio学习----Warning:Unable to find optional library: org.apache.http.legacy
主要是没有 android-23的版本 1.导入工程以后 Error:Could not find com.Android.tools.build:gradle:1.3.1. Searched in ...
- navicat mysql 书写存储过程并导出成sql
navicat创建存储过程: 选中该数据库 然后完成,保存的时候出错: 需要为字段类型添加类型的大小.下面加一下. 然后就在这里面写相关的业务代码了. 语句结尾需要加上分号; .否则会报错. 这边展 ...
- mysql 常用 sql 语句 - 快速查询
Mysql 常用 sql 语句 - 快速查询 1.mysql 基础 1.1 mysql 交互 1.1.1 mysql 连接 mysql.exe -hPup ...
- Apache Flink流式处理
花了四小时,看完Flink的内容,基本了解了原理. 挖个坑,待总结后填一下. 2019-06-02 01:22:57等欧冠决赛中,填坑. 一.概述 storm最大的特点是快,它的实时性非常好(毫秒级延 ...
- 使用cookiecutter创建django项目
使用cookiecutter创建django项目 下载安装: pip install cookiecutter cookiecutter https://github.com/pydanny/cook ...
- Elasticsearch 报错:Fielddata is disabled on text fields by default. Set `fielddata=true` on [`your_field_name`] in order to load fielddata in memory by uninverting the inverted index.
Elasticsearch 报错: Fielddata is disabled on text fields by default. Set `fielddata=true` on [`your_fi ...
- Java 基本类型、封装类型、常量池、基本运算
基本数据类型: byte:Java中最小的数据类型,在内存中占8位(bit),即1个字节,取值范围-128~127,默认值0 short:短整型,在内存中占16位,即2个字节,取值范围-32768~3 ...