Go语言数组和切片

数组学习和测试

声明数组

数组声明用var name [3]int来声明一个名字叫做name的数组,他有3个元素。数组声明中必须要用[n]或者[...]来确定数组内元素数量。[...]表示初始化数组是编译器根据你给的数据自动推导数字的元素数量。记住,数组必须要有元素数量,而且元素数量不能更改。不同元素数量的数组是两个数据类型,比如[3]int[4]int是两个数据类型,好比intstring一样不能相互做逻辑运算。

数组定义

声明数组的格式是:
var 数组名字 [元素数量] 数组类型

数组初始化

初始化一个数组可以用下面的代码来声明并初始化名字是name的数组,他有两个元素"铁锤""钢蛋"

var name = [2] string {"铁锤","钢蛋"}

或者用

var name = [...]int{2,5,3}

来声明一个由编译器自动推导出有3个元素的数组name。还可以用

var name = [...]string {0:"铁柱",5:"钢蛋"}

来指定数组name的第0个元素保存铁柱,第5个元素保存铁柱来初始化数组。当然也可以用

name := [...]int {3,5}

这种简写方式声明并初始化数组。

多维数组

你可以用如下方法创建一个二维数组:

name := [2][3]string{
    {"铁蛋", "xiaoming", "hello"},
    {"钢锤", "laobain", "2B"},
}

或者:

name := [...][3]string{
    {"铁蛋", "xiaoming", "hello"},
    {"钢锤", "laobain", "2B"},
}

如果用自动推导元素数量的方式需要注意,编译器只能自动推导第一层的元素数量。用name := [...][...]string{}编译器直接报错,因为编译器只能推导第一层的[...]第二个中括号[]内必须明确的给定元素数量。

遍历数组

数组遍历一般推荐用for i,zhi := range name {}来遍历数组,i取得数字name的索引,zhi取得数组name的数据。或者也可以用for i:=0; i<=9; i++{}来遍历数组。

另外数组是值引用类型,因此赋值和传参数不会改变原数组的值。

name := [...]string{"铁蛋", "xiaoming", "hello"}
name1 := name
fmt.Printf("数组name:%v\n数组name1:%v\n", name, name1)
fmt.Println("------------------------------")
name1[1] = "柱子" //修改name1并不影响name
fmt.Printf("数组name:%v\n数组name1:%v\n", name, name1)

再另外:[n]*T表示指针数组,*[n]T表示数组指针 。

切片

切片是基于数组做的封装,切片长度可变。声明切片是不需要在[]中定义元素数量。切片是引用类型,也就是说对切片进行操作,会改变底层数组的数据。

切片定义

var 切片名字 []类型

可以使用内置函数len()取得切片长度,用内置函数cap()取得切片容量。

基于数组定义切片

为数组name创建切片:

qiepian := name [1:4]

基于数组name的切片qiepian。切片的数据是数字name的第1个元素开始到第四个元素之前结束。左开右闭原则,name[1:4]的意思说明左边数字1的元素到右边数字索引4之前的元素。当然你还可以对切片再次切片qiepian1:=qiepian[:2]

name := [5]string{"aa", "bb", "cc", "dd", "ee"}
qiepian := name[1:4] //切片'qiepian'是基于数组'name'的切片
qiepian1 := qiepian[1:4]//切片1'qiepian1'也是基于数组'name'的切片。

fmt.Printf("name:\n %v \n 类型:%T 长度:%v 容量:%v \n", name, name, len(name), cap(name))
fmt.Printf("qiepian:\n %v \n 类型:%T 长度:%v 容量:%v \n", qiepian, qiepian, len(qiepian), cap(qiepian))
fmt.Printf("qiepian1:\n %v \n 类型:%T 长度:%v 容量:%v \n", qiepian1, qiepian1, len(qiepian1), cap(qiepian1))

基于数组的切片如果超出原数组的长度qiepian:=name[1:6]编译器会报如下错误:

invalid slice index 6 (out of bounds for 5-element array)

如果切片qiepian1超出qiepian的长度译器会识别出来么?答案是不会,嘿嘿,虽然它爹是谷歌。编译是识别不出来,但是运行是会报错,比如修改为qiepian1 := qiepian[1:5]编译没问题,但是运行是会报错:

panic: runtime error: slice bounds out of range [:5] wit
goroutine 1 [running]:main.main()
/data/data/com.termux/files/home/go/src/socold.com/soc
exit status 2

修改切片qiepian[2]的数据。也就是修改底层数组name的数据。因为切片的值是引用类型,而且qiepianqiepian2都是基于数组name。所以修改qiepian[2]的数据,就是修改底层数组name的数据。既然底层数组name的数据修改了,那么基于数组name的两个切片qiepianqiepian1显示出来的数据都该改变。下面输出已然证实结论。

name := [5]string{"aa", "bb", "cc", "dd", "ee"}
qiepian := name[1:4] //切片'qiepian'是基于数组'name'的切片
qiepian1 := name[1:4]//切片1'qiepian1'也是基于数组'name'的切片。

fmt.Printf("name:\n %v \n 类型:%T 长度:%v 容量:%v \n", name, name, len(name), cap(name))
fmt.Printf("qiepian:\n %v \n 类型:%T 长度:%v 容量:%v \n", qiepian, qiepian, len(qiepian), cap(qiepian))
fmt.Printf("qiepian1:\n %v \n 类型:%T 长度:%v 容量:%v \n", qiepian1, qiepian1, len(qiepian1), cap(qiepian1)


//修改切片'qiepian[2]'的数据。
qiepian[2] = "gg") 

fmt.Println("----------------------------------------")
fmt.Println("----------------------------------------")
fmt.Printf("name:\n %v \n 类型:%T 长度:%v 容量:%v \n", name, name, len(name), cap(name))
fmt.Printf("qiepian:\n %v \n 类型:%T 长度:%v 容量:%v \n", qiepian, qiepian, len(qiepian), cap(qiepian))
fmt.Printf("qiepian1:\n %v \n 类型:%T 长度:%v 容量:%v \n", qiepian1, qiepian1, len(qiepian1), cap(qiepian1))

同理,对切片的在切片修改数据一样的道理,会修改底层数组name。上面代码把qiepian1:=name[1:4]改成qiepian1:=qiepian[1:4]效果一样滴。

append(qiepian,"mm")来为切片qiepian添加一个元素mm时,如果原数组name容量够用,会直接修改数组name的数据,并为切片qiepian扩大一个容量。底层还是原数组name

name := [5]string{"aa", "bb", "cc", "dd", "ee"}
qiepian := name[1:4]  //切片'qiepian'是基于数组'name'的切片
qiepian1 := name[1:5] //切片1'qiepian1'也是基于数组'name'的切片。

fmt.Printf("name:\n %v \n 类型:%T 长度:%v 容量:%v \n", name, name, len(name), cap(name))
fmt.Printf("qiepian:\n %v \n 类型:%T 长度:%v 容量:%v \n", qiepian, qiepian, len(qiepian), cap(qiepian))
fmt.Printf("qiepian1:\n %v \n 类型:%T 长度:%v 容量:%v \n", qiepian1, qiepian1, len(qiepian1), cap(qiepian1))


//原数组'name'容量够用的话直接添加元素。
qiepian = append(qiepian, "mm")

fmt.Println("----------------------------------------")
fmt.Println("'qiepian'添加元素'mm'")
fmt.Println("----------------------------------------")
fmt.Printf("name:\n %v \n 类型:%T 长度:%v 容量:%v \n", name, name, len(name), cap(name))
fmt.Printf("qiepian:\n %v \n 类型:%T 长度:%v 容量:%v \n", qiepian, qiepian, len(qiepian), cap(qiepian))
fmt.Printf("qiepian1:\n %v \n 类型:%T 长度:%v 容量:%v \n", qiepian1, qiepian1, len(qiepian1), cap(qiepian1))

如果添加元素时,原数组容量不够,那就为切片qiepian重新造一个数组。比如下面代码,为切片qiepian添加两个元素是,原数组容量肯定不够用。我们又修改了切片qiepian1[0]的数据为AA,输出显示切片qiepian1的底层数组还是name,因为下面三组数据只有数组name和切片qiepian1的数据改变了,切片qiepian的数据并没有发生变化,所以,系统为切片qiepian重新造了一个新的底层数组。

name := [5]string{"aa", "bb", "cc", "dd", "ee"}
qiepian := name[1:4]  //切片'qiepian'是基于数组'name'的切片
qiepian1 := name[1:5] //切片1'qiepian1'也是基于数组'name'的切片。

fmt.Printf("name:\n %v \n 类型:%T 长度:%v 容量:%v \n", name, name, len(name), cap(name))
fmt.Printf("qiepian:\n %v \n 类型:%T 长度:%v 容量:%v \n", qiepian, qiepian, len(qiepian), cap(qiepian))
fmt.Printf("qiepian1:\n %v \n 类型:%T 长度:%v 容量:%v \n", qiepian1, qiepian1, len(qiepian1), cap(qiepian1))


//现在肯定超出原数组'name'的容量了。
qiepian = append(qiepian, "mm")
qiepian = append(qiepian, "zz")

fmt.Println("----------------------------------------")
fmt.Println("'qiepian'添加元素'mm'")
fmt.Println("----------------------------------------")
fmt.Printf("name:\n %v \n 类型:%T 长度:%v 容量:%v \n", name, name, len(name), cap(name))
fmt.Printf("qiepian:\n %v \n 类型:%T 长度:%v 容量:%v \n", qiepian, qiepian, len(qiepian), cap(qiepian))
fmt.Printf("qiepian1:\n %v \n 类型:%T 长度:%v 容量:%v \n", qiepian1, qiepian1, len(qiepian1), cap(qiepian1))


qiepian1[0] = "AA"

fmt.Println("----------------------------------------")
fmt.Println("修改'qiepian1[0]'的数据为'AA'")
fmt.Println("----------------------------------------")
fmt.Printf("name:\n %v \n 类型:%T 长度:%v 容量:%v \n", name, name, len(name), cap(name))
fmt.Printf("qiepian:\n %v \n 类型:%T 长度:%v 容量:%v \n", qiepian, qiepian, len(qiepian), cap(qiepian))
fmt.Printf("qiepian1:\n %v \n 类型:%T 长度:%v 容量:%v \n", qiepian1, qiepian1, len(qiepian1), cap(qiepian1))

切片添加元素

为切片添加元素可以使用append(qiepian)为切片添加一个元素。也可以使用for为切片添加元素。添加元素是主要数据类型,不要搞错了。append()可以为没有初始化或者没有分配内存的切片初始化。

//为切片添加多个元素
var name = [6]int{9, 8, 7, 6, 5, 4}
qiepian := name[0:6]
qiepian = append(qiepian, 0, 2, 3) //为切片添加三个元素0,2,3
fmt.Printf("%v \n", qiepian)

//用for为切片添加元素
nameI := [3]int{4, 5, 6}
for _, v := range nameI {
    qiepian = append(qiepian, v)
}
fmt.Printf("%v", qiepian)

//注意看'append(qiepian, s...)'。因为's'也是一个切片,里面有多个元素。's...'的意思是把's'中的元素拆开,添加到切片'qiepian'中。
s := []int{2, 3, 4, 5}
qiepian = append(qiepian, s...)
fmt.Printf("%v", qiepian)

构造切片

可以使用make([]数据类型,元素数量,切片容量)来构造一个切片。下面提供三种构造切片的方法。用make()构造的切片并申请内存空间。第二种name根据存储数据来动态扩容。一般推荐用make()来构造切片,一次申请到够用的空间,提高程序运行效率。第二种就麻烦了,存一个元素时申请一个数组,存三个元素时在申请一个底层数组,存10个元素是在更换一个底层数组,拖累程序运行效率。推荐一次直接到位的使用make()申请到够用的元素数量和容量。第三种var name3 []string只是声明切片,并没有分配到底层数组。和name2一样是动态扩容的切片。

name := make([]string, 3, 9)
name2 := []string{}
var name3 []string
fmt.Printf("name:%v len:%v cap:%v \n", name, len(name), cap(name))
fmt.Printf("name2:%v len:%v cap:%v \n", name2, len(name2), cap(name2))
fmt.Printf("name3:%v len:%v cap:%v \n", name3, len(name3), cap(name3))

切片不能直接比较

我们不能使用==来直接比较两个切片中的元素全部相等。切片唯一合法的比较操作是和nil比较。一个nil的切片没有底层数组。

name := make([]string, 3, 9)
name2 := []string{}
var name3 []string
fmt.Printf("name:%v len:%v cap:%v \n", name, len(name), cap(name))
fmt.Printf("name2:%v len:%v cap:%v \n", name2, len(name2), cap(name2))
fmt.Printf("name:%t name2:%t name3:%t \n", name == nil, name2 == nil, name3 == nil)

上面代码namename2name3都没有保存数据,但是name3返回的是true,因为name只是声明了切片,并没有初始化,还没有底层数组,所以返回true。那怎么判断切片有没有保存数据呢,只能用len(name)==0来判断切片有没有数据。一个nil的切片没有底层数组。

切片的拷贝

拷贝切片有两种方法,先说第一种最简单的赋值拷贝。赋值拷贝简单方便,但是有一个缺点,namename2都属于同一个底层数组,所以修改name2[2]的数据,会导致底层数组的数据改变,相应的name也会改变数据。入下代码:

//用make()构造切片
name := make([]string, 3, 5)

//初始化切片name
name = []string{"aa", "bb", "cc"}

//赋值拷贝name到name2
name2 := name[0:]

//打印name和name2
fmt.Printf("name:%v \n", name)
fmt.Printf("name2:%v \n", name2)

//修改name[2]的数据
name2[2] = "gg"

//再次输出name和name2
fmt.Printf("name:%v \n", name)
fmt.Printf("name2:%v \n", name2)

第二种办法使用内置函数copy()拷贝。用copy()相当于把切片name的底层数组也拷贝到了切片name2,虽然保存的数据相同,但是两个切片的底层数组不一样了。这样修改切片name2就不会改动name的数据了。用法:copy(目标切片,数据来源切片)copy()不会帮切片申请内存和初始化。

//用make()构造切片
name := make([]string, 3, 5)

//初始化切片name
name = []string{"aa", "bb", "cc"}

//用make()构造切片
name2 := make([]string, 3, 5)

//用copy()函数把切片'name'拷贝到'name2'
copy(name2, name)

//打印name和name2
fmt.Printf("name:%v \n", name)
fmt.Printf("name2:%v \n", name2)

//修改name[2]的数据
name2[2] = "gg"

//再次输出name和name2
fmt.Println("修改'name2[2]=gg'后:")
fmt.Printf("name:%v \n", name)
fmt.Printf("name2:%v \n", name2)

从切片中删除元素

并没有删除切片元素的方法,但是我们可以用但是我们可以用切片的特性来删除切片元素。如下例:

//声明并初始化数组'name'
name := [...]string{"aa", "bb", "cc", "dd", "ee"}
//输出数组'name'
fmt.Printf("数组name:%v \n", name)

//声明并初始化切片'name1'
name1 := name[:]
//输出切片'name1'
fmt.Printf("切片name1:%v \n", name1)

//删除切片元素
name1 = append(name1[:1], name1[2:]...)
//输出数组'name'
fmt.Printf("数组name:%v \n", name)
//输出切片
fmt.Printf("切片name1:%v \n", name1)

输出如下:

数组name:[aa bb cc dd ee]
切片name1:[aa bb cc dd ee]
数组name:[aa cc dd ee ee]
切片name1:[aa cc dd ee]

仔细看数组name的数据。append()函数删除元素实际并不是删除,而且添加元素,或者叫修改元素,Go语言没有真正的删除切片或者数组元素的办法。name1=append(name1[:1],name1[2:]...)语句等同于:

//声明并初始化数组'name'
name := [...]string{"aa", "bb", "cc", "dd", "ee"}
//输出数组'name'
fmt.Printf("数组name:%v \n", name)

//声明并初始化切片'name1'
name1 := name[:]
//输出切片'name1'
fmt.Printf("切片name1:%v \n", name1)

//删除切片元素
// name1 = append(name1[:1], name1[2:]...)

//上面语句等同于下面3行语句。
name[1] = name[2]
name[2] = name[3]
name[3] = name[4]

//输出数组'name'
fmt.Printf("数组name:%v \n", name)
//输出切片
fmt.Printf("切片name1:%v \n", name1)

说白了,就是掩耳盗铃,我不看你就就是没有,就是删除成功了。自己骗自己,但是效果出来了啊。呵呵~~~


  转载请注明: So Cold Go语言数组和切片

 上一篇
晨星基金评级 晨星基金评级
晨星基金评级 代码 名称 分类 三年 五年 回报 519674 银河创新成长混合 激进 五星 四星 47.90 000751 嘉实新兴产业股票 股票 五星 五星 18.57 161903 万家行业优选混合 (LOF) 激进
2020-03-01
下一篇 
SpaceVim官方文档 SpaceVim官方文档
描述该层用于golang开发。 它还提供了其他特定于语言的键映射。 安装要使用此配置层,请使用以下命令更新自定义配置文件: [[layers]] name = "lang#go" 安装后,在vim中运行:GoInstallBinarie
2020-02-20
  目录