Golang学习之数组&切片
数组
- 数组:同一种数据类型的固定长度的数据集合
- 数组定义:
var a [len]int
,比如:var a [5]int
,数组长度必须是常量,且是类型的组成部分。一旦定义,长度不能变 - 长度是数组类型的一部分,因此,
var a[5] int
和var a[10]int
是不同的类型 - 数组可以通过下标进行访问,下标是从0开始,最后一个元素下标是:
len-1
for i := 0; i < len(a); i++ {}
for index, v := range a {}
- 访问越界,如果下标在数组合法范围之外,则触发访问越界,会
panic
- 数组是值类型,赋值和传参会复制整个数组,而不是指针。因此改变副本的值,不会改变本身的值
- 支持 "=="、"!=" 操作符,因为内存总是被初始化过的。
- 指针数组
[n]*T
,数组指针*[n]T
。 - Golang中数组的另一个特点是占用内存的连续性,也就是说数组中的元素是被分配到连续的内存中的,因此索引数组元素的速度非常快。
数组定义
var 数组变量名 [元素数量]Type
var 数组变量名 = [元素数量]Type{元素……}
var 数组变量名 = [……]Type{元素……}
数组变量名 := [……]Type{元素……}
数组变量名 := [元素数量]Type{元素……}
数组遍历
方法1
a := [...]int{1:1, 3:5}
for i := 0; i < len(a); i++ {
fmt.Print(a[i], " ")
}
方法2
a := [...]int{1:1, 3:5}
for _, value := range a {
fmt.Print(value, " ")
}
多维数组
// 多维数组
全局:
var 数组名 [行][列]类型
var 数组名 [...][列]类型
局部:
数组名 := [行][列]类型{{第一行},{第二行},...,{第n行}}
数组名 := [...][列]类型{{第一行},{第二行},...,{第n行}}
// 多维数组遍历
func main() {
var f [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}}
for k1, v1 := range f {
for k2, v2 := range v1 {
fmt.Printf("(%d,%d)=%d ", k1, k2, v2)
}
fmt.Println()
}
}
数组的内存管理
数组,定长且元素类型一致的数据集合
(1)数组内存是连续的
(2)数组的内存地址实际上就是数组第一个元素的内存地址
数组中的元素是可变的,但长度是固定的,不可更改的
拷贝,,复制+粘贴,,另创一个空间存储拷贝的数组
切片
切片Slice并不是数组或者数组指针,它通过内部指针和相关属性引用数组片段,以实现变长方案
//切片的数据结构定义
type slice struct {
array unsafe.Pointer
len int
cap int
}
//切片的结构体由3部分构成,Pointer是指向一个数组的指针,len代表当前切片的长度,cap是当前切片的容量
- 切片:切片是数组的一个应用,因此切片是引用类型,但自身是结构体,值拷贝传递
- 切片的长度可以改变,因此,切片是一个可变的数组
- 切片遍历方式和数组一样,可以用
len()
求长度,表示可用元素数量 cap
求slice最大扩张容量,不能超出数组限制
切片的定义
var 切片名 [] 类型名
// 切片不需要说明长度
// 使用make()函数来创建切片:
var slice [] type = make([]type,len)
简写为
slice := make([]type,len)
// 指定容量,其中capacity为可选参数
slice := make([]T,length,capacity)
// len是数组的长度,并且也是切片的初始长度
切片创建及初始化
// 直接初始化切片,[]表示是切片类型,{1,2,3}初始化依次是1,2,3,其中cap=len=3
s := []int {1,2,3}
// 初始化切片s,是数组arr的引用
s := arr[:]
// 将数组arr从下标startIndex到endIndex-1的元素创建一个新的切片
s := arr[startIndex:endIndex]
// 默认endIndex时将表示一直到arr的最后一个元素
s := arr[startIndex:]
// 默认startIndex时将表示从arr的第一个元素开始
s := arr[:endIndex]
// 通过切片s初始化切片s1
s1 = s[startIndex:endIndex]
// 通过内置函数make()初始化切片s,[]int标识为其元素类型为int的切片
// make()函数允许在运行期动态指定数组长度,绕开了数组类型必须使用编译期常量的限制
s := make([]int,len,cap)
var s = make([]int,len,cap)
// make函数只用于切片,字典,channel创建
切片的指针类型
var v1 = new([]int) //创建并初始化,指向默认0
var v2 *[]int //无初始化,指向nil
nil和空切片
- 一个切片在未初始化之前默认为
nil
(空切片),长度为0 - 判断一个切片是否为空,要用
len(s) == 0
来判断,而不应该使用s == nil
来判断 - nil切片描述一个不存在的切片,当函数发生异常的时候,返回的切片就是nil切片,nil切片的指针指向nil
- 空切片和nil切片的区别在于空切片指向的地址不是nil,指向的是一个内存地址,但是它没有分配任何内存空间
len()函数、cap()函数
- 切片是可索引的,由
len()
方法获取长度 - 切片提供了计算容量的方法
cap()
可以测量切片最长可以达到多少 - 切片之间是不能比较的,无法使用 == 操作符来判断两个切片是否含有全部相等的元素
- 切片唯一合法的比较操作,是和
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; s1 != nil
append()函数、copy()函数
- append()函数,增加切片元素
- copy()函数,拷贝
package main
import "fmt"
func main() {
var numbers []int
printSlice(numbers)
/* 允许追加空切片 */
numbers = append(numbers, 0)
printSlice(numbers)
/* 向切片添加一个元素 */
numbers = append(numbers, 1)
printSlice(numbers)
/* 同时添加多个元素 */
numbers = append(numbers, 2,3,4)
printSlice(numbers)
/* 创建切片 numbers1 是之前切片的两倍容量*/
numbers1 := make([]int, len(numbers), (cap(numbers))*2)
/* 拷贝 numbers 的内容到 numbers1 */
copy(numbers1,numbers)
printSlice(numbers1)
}
func printSlice(x []int){
fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}
Slice底层的实现
-
切片的设计想法是由动态数组概念而来的,为了方便的使一个数据结构自动增加或减少,但是它本身并不是动态数据或者数组指针
-
在Go中,数组是值类型,赋值和函数传参操作都会复制整个数组数据
-
因此当数组元素量多的话,会消耗大量的内存,于是,函数传参用数组的指针,也就是说开辟一个空间存放数组,然后设置一个指针指向数组的开端即可,以高效利用内存
-
但是,传指针有一个弊端,就是当原数组的指针指向更改了,那么函数里的指针指向也会更改
-
而用切片传数组参数,既可以达到节约内存的目的,也可以达到合理处理好共享内存的问题
-
故,将一个大数组传递给函数会消耗很多内存,而采用切片的方式传递参数可以避免上述问题
-
切片是引用传递,所以不需要使用额外的内存并且比使用数组更有效率
-
切片本身并不是动态数组或者数组指针,其内部实现的数据结构通过指针引用底层数组,设定相关属性将数据读写操作限定在指定的区域内。切片本身是一个只读对象,其工作机制类似数组指针的一种封装
-
切片是对数组一个连续片段的引用,是一个引用类型,,类似python中的list类型
slice := []int{10,20,30,40,50,60}
// 创建一个len=6,cap=6的切片,同时完成数组里面每个元素值的初始化
// 需要注意的是,[]里面不能写数组的容量,因为如果写了数以后就是数组了,而不再是切片了
切片的底层原理
切片的本质是就是一个框,框住了一个连续的内存,只能保存相同类型的元素。
属于引用类型,真正的数据都是保存在底层数组中的
Slice的自动扩容策略
-
如果新申请的容量比原来容量的2倍大,则新申请的容量就是最终容量;
-
否则
- 如果原切片的长度小于1024,则2倍扩容
- 如果原切片的长度大于或等于1024,则每次循环扩容1.25倍,直到新申请的容量大于原容量
- 在进行循环1.25倍扩容计算时,若最终容量超出
int
的最大范围,则最终容量就是新申请的容量
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南