数组学习和测试
声明数组
数组声明用var name [3]int
来声明一个名字叫做name
的数组,他有3个元素。数组声明中必须要用[n]
或者[...]
来确定数组内元素数量。[...]
表示初始化数组是编译器根据你给的数据自动推导数字的元素数量。记住,数组必须要有元素数量,而且元素数量不能更改。不同元素数量的数组是两个数据类型,比如[3]int
和[4]int
是两个数据类型,好比int
和string
一样不能相互做逻辑运算。
数组定义
声明数组的格式是: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
的数据。因为切片的值是引用类型,而且qiepian
和qiepian2
都是基于数组name
。所以修改qiepian[2]
的数据,就是修改底层数组name
的数据。既然底层数组name
的数据修改了,那么基于数组name
的两个切片qiepian
和qiepian1
显示出来的数据都该改变。下面输出已然证实结论。
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)
上面代码name
,name2
,name3
都没有保存数据,但是name3
返回的是true
,因为name
只是声明了切片,并没有初始化,还没有底层数组,所以返回true
。那怎么判断切片有没有保存数据呢,只能用len(name)==0
来判断切片有没有数据。一个nil
的切片没有底层数组。
切片的拷贝
拷贝切片有两种方法,先说第一种最简单的赋值拷贝。赋值拷贝简单方便,但是有一个缺点,name
和name2
都属于同一个底层数组,所以修改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)
说白了,就是掩耳盗铃,我不看你就就是没有,就是删除成功了。自己骗自己,但是效果出来了啊。呵呵~~~