定义数组(2种方式):
var arr = [5]int{1,2,3,4,5} // 指定数组长度为5
var arr2 = [...]int{1,2,3,4,5,6,7,8,9} // "..."表示由编译器确定数组大小(9)
可以先定义数组后赋值
var arr [4]int // 也要指定长度
arr[0]=1; arr[1]=2; arr[2]=3; arr[3]=4
Go 语言数组声明需要指定元素类型及元素个数
不指定也会根据初始化的元素个数自动推算出大小,不可改变
数组需要明确指定大小,切片不指定大小
定义切片:
func main() {
/*通过数组来初始化切片*/
var arr = [...]int {2,6,9,8,1} // 先定义数组, 大小为5
var s1 = arr[:] // 切片s1 -> [2,6,9,8,1]
var s2 = arr[1:3] // [6,9] 取不到最后一个, 1<= index <3
var s3 = arr[1:] // [6,9,8,1]
var s4 = arr[:4] // [2,6,9,8] 取不到最后一个
/*通过切片来初始化切片*/
var s5 = s1[2:] // [9,8,1]
/*直接初始化切片, make([]type, len, cap)*/
var s6 = make([]int , 3, 6) // [0,0,0]
/*直接赋值初始化切片, 可以看到如下的中括号里面没有内容*/
var s7 = []int {1,2,3,4,5} // [1,2,3,4,5]
}
var s []type 用于定义切片, 通常[]type是省略了的, 所以如下是错误的操作
var s []int = [5]int{1,2,3,4,5} // 左边是定义切片, 右边是数组
数组与切片的打印可使用%v
func main() {
var array = [3]int{1,2,3}
var slice = array[:]
fmt.Printf("%v", array)
fmt.Printf("%v", slice)
}
数组与切片的关系:
func main() {
var a [6]int = [...]int{1,2,3,4,5,6} // 数组
s1 := a[2:4] // 切片s1基于数组a -- > 3,4
s2 := a[2:] // 切片s2基于数组a --> 3,4,5,6
fmt.Printf("%p\n", s1) // 0xc00006e040
fmt.Printf("%p\n", s2) // 0xc00006e040
}
如上所示, 切片s1与s2都是基于数组a
输出结果: s1与s2相同, 即: 切片就是数组的第一个元素的地址(所以切片是引用类型)
这就是数组与切片的基于关系: 切片指向它基于的数组的第一个元素
所以修改数组元素的值时, 切片对应元素会跟随变化, 反之也是
append与切片容量:
append用于向切片末尾追加元素, 参数为可变参函数
追加后, 如果切片容量不够, 则需要增加容量(切片地址改变)
所以用法类似这样: s = append(s, 10) (理解: append后并不知道是否超出容量, 因此需要使用赋值符)
如果没有超出容量, 则不会重新分配内存
func main() {
var a [8]int = [...]int{1,2,3,4,5,6,7,8}
s := a[6:]
fmt.Println(cap(s)) // 容量为:2
fmt.Printf("%p\n", s) // 地址为:0xc0000780f0
s = append(s,10) // 添加一个元素(超出了容量, 扩容)
fmt.Println(cap(s)) // 容量为:4
fmt.Printf("%p\n", s) // 地址为:0xc0000460c0
s = append(s,10) // 再添加一个元素(没有超出容量, 不扩容)
fmt.Println(cap(s)) // 容量为:4
fmt.Printf("%p\n", s) // 地址为:0xc0000460c0
}
如上所示: 切片容量发生变化时, 会重新分配地址
切片与基于底层数组关系断开(不再基于原底层数组)
即: 如果切片与数组是基于关系(切片指向数组), 会相互影响, 如果断开基于, 则不再相互影响
func main() {
a := [...]int{10, 20, 30, 40, 50}
s1 := a[:] // s1基于a
s2 := a[:] // s2基于a
s1[0] = 100
fmt.Println(a[0]) // 100, 修改切片会同时修改其底层数组
fmt.Println(s2[0]) // 100, 修改切片会同时修改同一底层数组的其它切片
/*改变切片容量, 则该切片与原底层数组关系断开*/
s1 = append(s1, 200) // 切片s1增加一个元素, 达到扩容
fmt.Println(cap(s1)) // 10, 容量增加到10
s1[0] = 0
fmt.Println(a[0]) // 100, s1已与底层数组关系断开
fmt.Println(s2[0]) // 100
}
切片长度容量获取:
获取切片长度: len(slice)
获取切片容量: cap(slice)
func main() {
a := [...]int{10, 20, 30, 40, 50}
s1 := a[:]
s2 := a[:]
s1 = append(s2, 1, 2, 3) // 10,20,30,40,50,1,2,3
var s3 = append(s1, s2...) // 10,20,30,40,50,1,2,3,10,20,30,40,50
println(cap(s3)) // 20
}
切片复制:copy(t_silice, s_slice)
复制切片要保证目标切片有足够的长度(和容量无关)
func main() {
s1 := []int{10, 20, 30, 40, 50}
s2 := make([]int, 6, 10)
copy(s2,s1)
println(s2[4])
}
切片没有插入和删除元素的方法, 可通过子切片操作
例: 去掉切片中值为30的元素
func main() {
/*假定目标切片*/
s1 := []int{10, 20, 30, 40, 50, 15, 30, 77}
/*先循环一遍, 得到有几个值为30的元素*/
var count = 0;
for i:= 0; i
数组和切片都可作为函数参数传递:
由于数组是值类型, 切片是引用类型
所以函数接收切片可直接修改内容, 接收数组则不能(需要接收地址才行)
通常函数的参数都会是切片, 而不是数组
func main() {
// 定义切片
var array = []int{}
setArray(array)
// 传入数组
var array2 = [5]int{1, 2}
setArray2(array2)
}
func setArray(params []int) { // 切片作为形参, 不能传入数组
fmt.Println(len(params))
}
func setArray2(params [5]int) { // 数组作为形参, 不能传入切片
fmt.Println(len(params))
}
切片作为函数参数时
如果函数内部修改了此切片某些索引的值,会影响到原切片
但如果在函数内部删除或添加了切片长度,则不会影响到原切片
(我猜想: 删除或添加元素的操作, 可能会使切片容量变化
如果容量变化, 那么地址变化, 则操作的就是新切片
所以删除或添加的操作就干脆不让它影响原切片)
func main() {
var sli = []int{1,2,3}
setSlice(sli)
fmt.Println(sli[0]) // 100, 这个值改变了
fmt.Println(len(sli)) // 3, 长度还是不变
}
func setSlice(params []int) {
params[0] = 100 // 修改第一个元素的值
fmt.Println(len(params)) // 长度为3
params = append(params, 1) // 添加一个元素
fmt.Println(len(params)) // 长度变4
}
字符串与切片
字符串本质是字节数组
func main() {
var str = "hello world"
s := str[:5]
fmt.Println(s) // hello, 这样就很方便得到子串
s[0] = 'A' // error,不能这样操作, 因为字符串是不能改变内容的
}
深入分析:
字符串底层是一个指针, 指向字节数组
切片是底层也是指针, 指向数组
它们都有长度, 区别就在于一个指向的是byte数组, 一个指向任何数组
str := "hello" // 长度为5, 指向数组: ['h' 'e' 'l' 'l' 'o']
slice := str[2:] // 长度为3, 指向数组: ['l' 'l' 'o']
更改字符串中的字符:
func main() {
var str = "中ello"
c := []rune(str) // 不要使用byte, rune表示字符, 如果是非英文字母使用byte会有问题
c[0] = 'h'
fmt.Println(string(c)) // hello
}
因为一个字母表示一个字节, 但是一个非字母就不一定表示一个字节
而rune是可变的, 如果表示英文时是一个字节, 表示中文时是三个字节
排序和查找:
导入sort包, 其中有对整数, 字符串, 浮点数排序的函数
func main() {
arr := [...]int{1,2,80,-7, 0,2}
sort.Ints(arr[:])
fmt.Println(arr)
arr2 := [...]string{"a", "A", "abc", "fg"}
sort.Strings(arr2[:])
fmt.Println(arr2)
arr3 := [...]float64{2.5, 3.3, 0.6, 9}
sort.Float64s(arr3[:])
fmt.Println(arr3)
a := [...]int{1,2,4,7,0,9,11,2}
sort.Ints(a[:]) // 需要先排序, 排序后: 0,1,2,2,4,7,9,11
index := sort.SearchInts(a[:], 11) // 返回下标值, 从0开始
fmt.Println(index) // 7
}