『GoLang』数组与切片
数组
数组是具有相同唯一类型的一组已编号且长度固定的数据项序列(这是一种同构的数据结构);这种类型可以是任意的原始类型例如整型、字符串或者自定义类型。
数组长度必须是一个常量表达式,并且必须是一个非负整数。
数组长度也是数组类型的一部分,所以
[5]int
和[10]int
是属于不同类型的。
一维数组
一维数组声明以及初始化常见方式如下:
var arrAge = [5]int{18, 20, 15, 22, 16}
var arrName = [5]string{3: "Chris", 4: "Ron"} //指定索引位置初始化
// {"","","","Chris","Ron"}
var arrCount = [4]int{500, 2: 100} //指定索引位置初始化 {500,0,100,0}
var arrLazy = [...]int{5, 6, 7, 8, 22} //数组长度初始化时根据元素多少确定
var arrPack = [...]int{10, 5: 100} //指定索引位置初始化,数组长度与此有关 {10,0,0,0,0,100}
var arrRoom [20]int
var arrBed = new([20]int)
数组在声明时需要确定长度,但是也可以采用上面不定长数组的方式声明,在初始化时会自动确定好数组的长度。上面 arrPack
声明中 len(arrPack)
结果为6
,表明初始化时已经确定了数组长度。而arrRoom
和arrBed
这两个数组的所有元素这时都为0
,这是因为每个元素是一个整型值,当声明数组时所有的元素都会被自动初始化为默认值 0
。
Go 语言中的数组是一种值类型(不像 C/C++ 中是指向首元素的指针),所以可以通过 new()
来创建:
var arr1 = new([5]int)
那么这种方式和 var arr2 [5]int
的区别是什么呢?arr1
的类型是 *[5]int
,而 arr2
的类型是 [5]int
。在Go语言中,数组的长度都算在类型里。
package main
import "fmt"
func main() {
var arr1 = new([5]int)
arr := arr1
arr1[2] = 100
fmt.Println(arr1[2], arr[2])
var arr2 [5]int
newarr := arr2
arr2[2] = 100
fmt.Println(arr2[2], newarr[2])
}
输出结果:
100 100
100 0
从上面代码结果可以看到,new([5]int)
创建的是数组指针,arr
其实和arr1
指向同一地址,故而修改arr1
时arr
同样也生效。而newarr
是由arr2
值传递(拷贝),故而修改任何一个都不会改变另一个的值。在写函数或方法时,如果参数是数组,需要注意参数长度不能过大。
由于把一个大数组传递给函数会消耗很多内存(值传递),在实际中我们通常有两种方法可以避免这种现象:
- 使用数组的指针
- 使用切片
通常使用切片是第一选择。
多维数组
多维数组在Go语言中也是支持的,例如:
[...][5]int{ {10, 20}, {30, 40} } // len() 长度根据实际初始化时数据的长度来定,这里为2
[3][5]int // len() 长度为3
[2][2][2]float64 // 可以这样理解 [2]([2]([2]float64))
在定义多维数组时,仅第一维允许使用
…
,而内置函数len
和cap
也都返回第一维度长度。定义数组时使用…
表示长度,表示初始化时的实际长度来确定数组的长度。
b := [...][5]int{ { 10, 20 }, { 30, 40, 50, 60 } }
fmt.Println(b[1][3], len(b)) //60 2
数组元素可以通过索引(下标)来读取(或者修改),索引从 0 开始,第一个元素索引为 0,第二个索引为 1,以此类推。(数组以 0 开始在所有类 C 语言中是相似的)。元素的数目,也称为长度或者数组大小必须是固定的并且在声明该数组时就给出(编译时需要知道数组长度以便分配内存);数组大小最大为 2 GB。
遍历数组的方法既可以for
条件循环,也可以使用 for-range
。这两种 for
结构对于切片(slices
)来说也同样适用。
另外,如数组元素类型支持
==
,!=
操作符,那么数组也支持此操作,但如果数组类型不一样则不支持(需要长度和数据类型一致,否则编译不通过)。
切片
切片(slice
) 是对底层数组一个连续片段的引用,所以切片是一个引用类型。切片提供对该数组中编号的元素序列的访问。未初始化切片的值为nil
。
与数组一样,切片是可索引的并且具有长度。切片s
的长度可以通过内置函数len()
获取;与数组不同,切片的长度可能在执行期间发生变化。元素可以通过整数索引0
到len(s)-1
来寻址。我们可以把切片看成是一个长度可变的数组。
切片提供了计算容量的函数 cap()
,可以测量切片最大长度。切片的长度永远不会超过它的容量,所以对于切片 s
来说,这个不等式永远成立:0 <= len(s) <= cap(s)
。
多个切片如果表示同一个数组的片段,它们可以共享数据;因此一个切片和相关数组的其他切片是共享存储的,相反,不同的数组总是代表不同的存储。数组实际上是切片的构建块。
切片可以延伸超过切片的末端,容量是切片长度与切片之外的数组长度的总和。
使用内置函数
make()
可以给切片初始化,该函数指定切片类型和指定长度和可选容量的参数。
注意 绝对不要用指针指向
slice
。切片本身已经是一个引用类型,所以它本身就是一个指针!!
切片初始化:
var slice1 []type = arr1[start:end]
var x = []int{2, 3, 5, 7, 11}
make创建切片
当相关数组还没有定义时,我们可以使用 make()
函数来创建一个切片 同时创建好相关数组
slice1 := make([]type, len)
slice1 := make([]type, len, cap)
如果从数组或者切片中生成一个新的切片,我们可以使用下面的表达式:
a[low : high : max]
max-low
的结果表示容量,high-low
的结果表示长度。
切片重组
通过改变切片长度得到新切片的过程称之为切片重组 (reslicing
)
slice1 := make([]type, start_length, capacity)
当我们在一个切片基础上重新划分一个切片时,新的切片会继续引用原有切片的数组。如果你忘了这个行为的话,在你的应用分配大量临时的切片用于创建新的切片来引用原有数据的一小部分时,会导致难以预期的内存使用。
简单说,有一个切片长度和容量都是10000,你现在却只需要使用其中的三个元素,如下所示:
package main
import "fmt"
func get() []byte {
raw := make([]byte, 10000)
fmt.Println(len(raw), cap(raw), &raw[0]) // 显示: 10000 10000 数组首字节地址
return raw[:3] // 10000个字节实际只需要引用3个,其他空间浪费
}
func main() {
data := get()
fmt.Println(len(data), cap(data), &data[0]) // 显示: 3 10000 数组首字节地址
}
上面的代码原因很简单,对切片进行切片,由于切片是引用类型,所以如果你原切片占用空间很多,而现在只需要一点点的数据,那么最好不要用切片,而应该用
copy
函数,将少部分的数据复制出来,这样就可以释放原切片空间。
func get() []byte {
raw := make([]byte, 10000)
fmt.Println(len(raw), cap(raw), &raw[0]) // 显示: 10000 10000 数组首字节地址
res := make([]byte, 3)
copy(res, raw[:3]) // 利用copy 函数复制,raw 可被GC释放
return res
}
顺带说一下append
内置函数:
func append(s S, x ...T) S // T是S元素类型
append()
函数将 0
个或多个具有相同类型S的元素追加到切片s
后面并且返回新的切片;追加的元素必须和原切片的元素同类型。如果s
的容量不足以存储新增元素,append()
会分配新的切片来保证已有切片元素和新增元素的存储。
因此,append()
函数返回的切片可能已经指向一个不同的相关数组了。append()
函数总是返回成功,除非系统内存耗尽了。
s0 := []int{0, 0}
s1 := append(s0, 2) // append 单个元素 s1 == []int{0, 0, 2}
s2 := append(s1, 3, 5, 7) // append 多个元素 s2 == []int{0, 0, 2, 3, 5, 7}
s3 := append(s2, s0...) // append 一个切片 s3 == []int{0, 0, 2, 3, 5, 7, 0, 0}
s4 := append(s3[3:6], s3[2:]...) // append 切片片段 s4 == []int{3, 5, 7, 2, 3, 5, 7, 0, 0}
append()
函数操作如果导致分配新的切片来保证已有切片元素和新增元素的存储,也就是返回的切片可能已经指向一个不同的相关数组了,那么新的切片已经和原来切片没有任何关系,即使修改了数据也不会同步。
append()
函数操作后,有没有生成新的切片需要看原有切片的容量是否足够。
append()
函数操作如果导致分配新的切片来保证已有切片元素和新增元素的存储,也就是返回的切片可能已经指向一个不同的相关数组了,那么新的切片已经和原来切片没有任何关系,即使修改了数据也不会同步。
有一个奇特之处,对一个切片s=[]int{1,2,3,4,5}
:
package main
import "fmt"
func main() {
s := []int{1, 2, 3, 4, 5}
p := s[1:]
q := s[:4]
fmt.Println(len(p), cap(p)) // 4 4
fmt.Println(len(q), cap(q)) // 4 5
}
『GoLang』数组与切片的更多相关文章
- golang笔记——数组与切片
一.切片的定义 我们可以从数组(go语言中很少直接使用数组)或者切片来初始化一个新的切片,也可以直接通过 make 来初始化一个所有元素为默认零值的切片. //1.通过数组来初始化切片 arr := ...
- GoLang笔记-数组和切片,本质是就是长度不可变的可变的区别
数组 Arrays 数组是内置(build-in)类型,是一组同类型数据的集合,它是值类型,通过从0开始的下标索引访问元素值.在初始化后长度是固定的,无法修改其长度.当作为方法的入参传入时将复制一份数 ...
- golang之数组与切片
数组 数组可以存放多个同一类型数据,数组也是一种数据类型,在Go中,数组是值类型. 数组的定义: var 数组名 [数组大小]数据类型 var a [5]int 赋初值 a[0] = 1 a ...
- 『GoLang』反射
方法和类型的反射 反射是应用程序检查其所拥有的结构,尤其是类型的一种能.每种语言的反射模型都不同,并且有些语言根本不支持反射.Go语言实现了反射,反射机制就是在运行时动态调用对象的方法和属性,即可从运 ...
- 『GoLang』字典Map
map是一种元素对的无序集合,一组称为元素value,另一组为唯一键索引key. 未初始化map的值为nil.map 是引用类型,可以使用如下声明: var map1 map[keytype]valu ...
- 『GoLang』函数
函数介绍 Go语言函数基本组成包括: 关键字func 函数名 参数列表 返回值 函数体 返回语句 语法如下: func 函数名(参数列表) (返回值列表) { // 函数体 return } 除了ma ...
- 『GoLang』string及其相关操作
目录 1. 字符串简介 2. 字符串的拼接 3. 有关 string 的常用处理 3.1 strings 包 3.1.1 判断两个 utf-8 编码字符串是否相同 3.1.2 判断字符串 str 是否 ...
- 深入学习golang(1)—数组与切片
数据(array)与切片(slice) 数组声明: ArrayType = "[" ArrayLength "]" ElementType . 例如: va ...
- golang中数组与切片的区别
初始化:数组需要指定大小,不指定也会根据初始化的自动推算出大小,不可改变 数组: a := [...],,} a := [],,} 切片: a:= [],,} a := make([]) a := m ...
随机推荐
- 【springcloud】服务熔断与降级(Hystrix)
转自:https://blog.csdn.net/pengjunlee/article/details/86688858 服务熔断 服务熔断的作用类似于我们家用的保险丝,当某服务出现不可用或响应超时的 ...
- SpringCloud商品服务调用方式之Ribbon
1.创建order_service项目 pom依赖 <dependency> <groupId>org.springframework.boot</groupId> ...
- 通过location响应头实现重定向
package day08; import java.io.IOException; import javax.servlet.ServletException; import javax.servl ...
- hdfs中数据迁移
1.hdfs集群间数据迁移 hadoop distcp hdfs://192.128.112.66:8020/user/hive/warehouse/data.db/dwi_xxxx_d /user ...
- 高德地图——骑行路线&骑行指定
&plugin=AMap.Riding 也是[]中放json, 包含keyword和city,不能有途经1.骑行路线(关键字) <!DOCTYPE html> <html&g ...
- js调试之firbug
说下几种方法吧: 1.用alert 这个最最直观 把你想要的内容弹出来给你看,但是要看哪里 就要在哪里加,比较麻烦 2.用firefox 或者chrome浏览器 里面有debug工具的 3.如果想用i ...
- Mysql主从复制、半同步复制、并行复制
MySQL之间数据复制的基础是二进制日志文件(binary log file).一台MySQL数据库一旦启用二进制日志后,其作为master,它的数据库中所有操作都会以"事件"的方 ...
- 快速构建CLI程序并发布到PyPi
构造一个简单的CLI程序 typer 这个从去年就被各种营销号吹成Web框架的 第三方库, 与 FastAPI 同出一人之手,它不是Web框架,它是一个用来构建CLI程序的库,我们就简单搞个例子 # ...
- 新来的前端小姐姐问:Vue路由history模式刷新页面出现404问题
摘要:vue-router 默认 hash 模式 -- 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载. 本文分享自华为云社区<学习Vue Rou ...
- Java 字符串格式化和工具类使用
前言 我们在做项目时候经常需要对字符串进行处理,判断,操作,所以我就总结了一下java 字符串一些常用操作,和推荐比较好用我在自用的工具类,毕竟有轮子我们自己就不用重复去写了,提供开发效率,剩下的时间 ...