Go语言的数组
声明数组的方法:
var variable_name [SIZE] variable_type
//e.x var myarray [5] int
//数组初始化
var array1 = [5] int{1,2,3} //指定数组长度并顺序初始化,没有初始化的为零值
array2:=[]int{1,2,3} //让编译器判断数组的长度(这是一个切片)
var array3=[...]int{1,2,3} //让编译器判断数组的长度(这是一个数组)
// array4:=[...]int{1:6,9:10} 根据给定的索引最大值确定数组的长度,因此该数组的长度为10
array4:=[5]int{1:2,3:5,4:10}//可以指定初始化某个位置的数组元素
array5:=array4 //可以直接通过旧数组来声明或赋值给新数组,新数组是旧数组的拷贝
多维数组
声明方法:
var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type
//example
//var multi_array [2][3] int
multi_array := [][]int{}
row1 := []int{1, 2, 3}
row2 := []int{4, 5, 6}
multi_array = append(multi_array, row1)//append给数组填充内容,效果跟C++容器的insert相似,但需要接收返回值,否则没有实现添加
multi_array = append(multi_array, row2)
fmt.Println(multi_array[0])
fmt.Println(multi_array[1])
//输出[1 2 3] [4 5 6]
//创建各维度元素数量不一样的数组
package main
import "fmt"
func main() {
// 创建空的二维数组
animals := [][]string{}
// 创建三一维数组,各数组长度不同
row1 := []string{"fish", "shark", "eel"}
row2 := []string{"bird"}
row3 := []string{"lizard", "salamander"}
// 使用 append() 函数将一维数组添加到二维数组中
animals = append(animals, row1)
animals = append(animals, row2)
animals = append(animals, row3)
// 循环输出
for i := range animals {
fmt.Printf("Row: %v\n", i)
fmt.Println(animals[i])
}
}
初始化二维数组
a:=[2][3]int{
{1,2,3},
{4,5,6}} //最后一行的右侧括号不能单独成行,所以要这样写
或者
a:=[2][3]int{
{1,2,3},
{4,5,6},
} //这样就可以单独成行
向函数传递数组参数
func myfunc1 (array [] type) [return type]{ /* code */} //1
func myfunc2 (array [size] type) [return type]{ /* code */} //2
情况1:要注意指定了大小的数组(普通意义上的数组)只能传给指定了大小的形参的函数(上述的函数1)
情况2:没有指定长度的数组(在go中其实是切片类型)才能传给上述的函数2
声明数组:
nums := [3]int{1,2,3,}
声明切片:
nums := []int{1,2,3}
没有声明长度的其实是切片,可以理解为数组,但本质上类型不一样,例如下方的函数传参处理方式就不一样。
- Go 语言的数组是值,其长度是其类型的一部分,作为函数参数时,是 值传递,函数中的修改对调用者不可见
- Go 语言中对数组的处理,一般采用 切片 的方式,切片包含对底层数组内容的引用,切片作为函数参数时,类似于 指针传递,函数中的修改对调用者可见
对于情况1,传给函数的数组是值传递,和C++不同,C++传的是本质上是个指针,所以在go语言中,函数内处理的是数组的副本,不影响原数组
func change(array []int) {
array[0] = 100
}
func change_(array [5]int) {
array[0] = 1000
}
array1 := [5]int{1, 2, 3}
fmt.Println(array1[0])
change_(array1)
fmt.Println(array1)
//输出1 1
对于情况2,传给函数的数组是引用传递
func change(array []int) {
array[0] = 100
}
func change_(array [5]int) {
array[0] = 1000
}
array1 := []int{1, 2, 3}
fmt.Println(array1[0])
change(array1)
fmt.Println(array1)
//输出1 100
Go语言的切片类型
切片的下标访问方式,索引不能超过其长度len
arr2 := make([]int, 2, 10) //长度2 容量10的切片
fmt.Println(arr2[1]) //correct
fmt.Println(arr2[5]) //incorrect
一个奇怪但有趣的例子:
func main() {
s := []int{2, 3, 5, 7, 11, 13}
printSlice(s)
// 截取切片使其长度为 0
s = s[:0]
printSlice(s)
// 拓展其长度
s = s[:4]
printSlice(s)
// 舍弃前两个值
s = s[2:]
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
//output
len=6 cap=6 [2 3 5 7 11 13]
len=0 cap=6 []
len=4 cap=6 [2 3 5 7]
len=2 cap=4 [5 7]
直接下标访问例如arr[i]这样,i小于等于len(arr)-1。而如果通过上下界的方式来,就允许出现上面代码中扩展长度的用法(即使该语句中右侧的arr是个len=0的切片),要多加注意区分。个人觉得代码上尽量别这么写,自己都搞不明白,以后看代码更头疼。:
arr2 := make([]int, 0, 5)
fmt.Println("len of arr2", len(arr2), "cap of arr2", cap(arr2))
arr2 = arr2[:5]
fmt.Println(arr2)
//output
len of arr2 0 cap of arr2 5
[0 0 0 0 0]
切片的切片其实就是个二维切片,即切片的每个元素都是一个切片,和二维数组一样,每个元素都是一个切片。
arr5 := make([]([]int), 10)
arr5:=[][]int
//都是一个效果
二维切片中的每一个切片的长度和容量都可以不一样。
TwoDimArr := make([][]int, 5)
for i := 0; i < 5; i++ {
TwoDimArr[i] = make([]int, 5, i+5)
}
每个元素可以直接通过下标赋值替换:
func main() {
slice := Pic(5, 6)
fmt.Println(slice)
slice[0] = []uint8{1, 2, 3, 4, 5}
fmt.Println(slice)
}
//output
[[0 1 2 3 4] [0 1 2 3 4] [0 1 2 3 4] [0 1 2 3 4] [0 1 2 3 4] [0 1 2 3 4]]
[[1 2 3 4 5] [0 1 2 3 4] [0 1 2 3 4] [0 1 2 3 4] [0 1 2 3 4] [0 1 2 3 4]]
切片其实和C++的vector很相似,是可以动态变化大小的,只不过在go语言里,语法上和go的数组操作很像。数组和切片的区别在于,数组的长度是固定,声明后不变,而切片的长度是可变的。
切片的长度是用户能够访问的索引上限,容量是切片当前可拥有的最大长度。
声明切片的方式:
var slice []type
slice:=[]type{1,2,3}
slice:=make([]type,len)
slice:=make([]type,len,cap) //len是切片的初始长度,cap是切片的容量,也即不用扩容前能容纳的最大元素数量
切片的初始化(和python的列表下标使用方法很像)
通过对数组使用下标运算符来初始化,得到的是切片不是数组类型,而不加[]的话,则得到的是数组类型
array1:=[5]int{1,2,3,4,5}
array2:=array1//array2是数组类型
array3:=array1[:]//array3是切片类型
切片可以通过数组来初始化,也可以通过内置函数 make() 初始化。
初始化时 len=cap,在追加元素时如果容量 cap 不足时将按 len 的 2 倍扩容。
s :=[] int {1,2,3 } 直接初始化切片,[] 表示是切片类型,{1,2,3} 初始化值依次是 1,2,3。其 cap=len=3。
s := arr[:] 初始化切片 s,是数组 arr 的引用。
s := arr[startIndex:endIndex] 将 arr 中从下标 startIndex 到 endIndex-1 下的元素创建为一个新的切片。
s := arr[startIndex:] 缺省 endIndex 时将表示一直到 arr 的最后一个元素。
s := arr[:endIndex] 缺省 startIndex 时将表示从 arr 的第一个元素开始。
s1 := s[startIndex:endIndex] 通过切片 s 初始化切片 s1
s :=make([]int,len,cap) 通过内置函数 make() 初始化切片 s,[]int 标识为其元素类型为 int 的切片。
len函数是获取切片当前的元素个数,而cap函数获取的是切片的容量
一个未经初始化的切片 var slice [] type是一个空切片,默认为nil,slice==nil
切片截取:
可以通过设置下限及上限来设置截取切片 [lower-bound:upper-bound]
依旧是左闭右开,跟上面切片初始化的用法相似arr[idx1:idx2]
append和copy函数
因为切片是动态的,使用append可以往里增加元素,而使用copy可以复制切片A的内容到切片B上。
append的用法:
append函数可以为一个切片一个元素,至于如何增加、返回的是原切片还是一个新切片、长度和容量如何改变这些细节,append函数都会帮我们自动处理。
slice:=[]int{1,2,3,4,5}
slice=append(slice,7,8,9)//可以一次加一个元素 也可以增加多个 如果容量不够,底层会自动扩容增
//注意调用append之后一定要接收返回值,否则没有实现切片的元素增加
通过...可以在用同一纬度的切片来append:
slice1 := []int{1, 2, 3}
slice2 := make([]int, 10)
slice2 = append(slice2, slice1...) //就是这一行,实现将同维度的切片append到另一个切片上
fmt.Println(slice2)
//output
[0 0 0 0 0 0 0 0 0 0 1 2 3]
append函数的简单实现
func Append(slice, data []byte) []byte {
l := len(slice)
if l + len(data) > cap(slice) { // reallocate
// Allocate double what's needed, for future growth.
newSlice := make([]byte, (l+len(data))*2)
// The copy function is predeclared and works for any slice type.
copy(newSlice, slice)
slice = newSlice
}
slice = slice[0:l+len(data)]
copy(slice[l:], data)
return slice
}
copy函数用法
Copy returns the number of elements copied, which will be the minimum of len(src) and len(dst). 如果dst和src的其中一个长度为0,则没有复制任何元素。copy后,dst和src是不一样的内存,不存在共享。
copy(destination,source)
dst_slice:=make([]int,len,cap)
src_slice:=[]int{1,2,3,4,5}
copy(dst_slice,src_slice)
切片的数据结构包含三个基本元素,1.指向数组的指针,2.切片的长度len,3.切片的容量cap
多个切片是可以同时指向同一个数组的,修改其中一个切片会影响其他的切片:
src_slice := []int{7, 7, 7, 7, 7}
dst_slice := src_slice
dst_slice[2] = 9
fmt.Println(src_slice, dst_slice)
//输出 [7 7 9 7 7] [7 7 9 7 7]
除了通过make,或者自己给定元素的方式来初始化外,其他通过别的数组、切片来初始化切片,都会使得新的切片与别的切片或数组共享同一片内存,数据是共用的(初始化时,新的切片底层的数据结构其实直接就是等号右侧切片的副本,复制了它的地址、长度和大小信息)。
arr:=[5]int{1,2,3,4,5}
slice:=arr[:3]
arr[2]=20
fmt.Println(slice,arr)
//输出 [1 2 20] [1 2 20 4 5]
但如果某个切片一旦需要扩容,复制旧的元素到新的空间后,则是新的内存空间,不再影响之前的数组内存。
arr:=[5]int{1,2,3,4,5}
slice:=arr[:3]
slice=append(slice,7,8,9,10,11,12)
arr[2]=20
fmt.Println(slice,arr)
//输出 [1 2 3 7 8 9 10 11 12] [1 2 3 4 5]
切片名其实就是一个地址,是底层指向的数组元素的首地址,对切片名不需要用&取地址,而对数组名则需要&:
arr:=[5]int{1,2,3,4,5}
slice:=arr[:3]
arr[2]=20
fmt.Printf("arr address:%p slice address: %p\n",&arr,slice)
//输出arr address: 0xc0000240c0 slice address: 0xc0000240c0
//可以看到地址是相同的,表明确实是引用同一内存空间
一种声明切片和数组的方法
a:=[...]string{3:"hello",9:"world"}
b:=[]strign{3:"hello",9:"world"}
//指定数组或切片下标对应的元素值
new()和make()的区别
看起来二者没有什么区别,都在堆上分配内存,但是它们的行为不同,适用于不同的类型。
- new(T) 为每个新的类型T分配一片内存,初始化为 0 并且返回类型为*T的内存地址:这种方法 返回一个指向类型为 T,值为 0 的地址的指针,它适用于值类型如数组和结构体;它相当于 &T{}。
- make(T) 返回一个类型为 T 的初始值,它只适用于3种内建的引用类型:切片、map 和 channel
从字符串生成切片
字符串本质上是个字节数组,根据字符的不同,对应的字节数不一样,例如ascii码对应一个字节,utf8一个字符可能对应3个或4个字节( UTF-8 的是一种变长编码规则,从 1 到 4 个字节不等。 )
var r []byte
//比较适合ascii字符串,用汉字的话输出会乱码
var s string = "I love you"
r = append(r, s...) //...有解包的含义
for _, v := range r {
fmt.Printf("%c ", v)
}
go语言中的字符串是不可变对象,不能通过str[idx]='h'这样的形式来修改,编译不过。
byte切片和string类型互转
byte其实就是uint8,一个字节
string可以和byte的切片转换
1,string 转为[]byte
var str string = "test"
var data []byte = []byte(str)
2,byte转为string
var data [10]byte
byte[0] = 'T'
byte[1] = 'E'
var str string = string(data[:])