GO学习笔记 数组与切片
一.数组
1.数组的介绍
数组可以可以存放多个同一类型数据。数组也是一种数据类型,在GO中,数组是值类型。
举例:
package main import "fmt" func main(){ // 使用数组 // 定义一个数组 var nums [6]float64; // 给数组的每一个元素赋值 nums[0] = 3.0 nums[1] = 5.0 nums[2] = 1.0 nums[3] = 3.4 nums[4] = 2.0 nums[5] = 50.0 // 遍历数字求平均值 total := 0.0 for i := 0; i < len(nums); i++ { total += nums[i] } avg_num := fmt.Sprintf("%.2f",total / float64(len(nums))) fmt.Println(avg_num) }
2.数组定义和内存布局
数组的定义
var 数组 [数组大小]数据类型
如:
var a [10]int;
赋值a[0],a[1]....a[9]
数组的地址可以通过数组名来获取 &arr
数组的首地址就是数组第一个值的地址的。 &arr[0]
数组第二个值的地址就是第一个地址加这个数组类型的字节数,这边如果是int就加8个字节。
package main import "fmt" func main() { var arr [4]int; fmt.Println("第一个地址:",&arr[0]) fmt.Println("第二个地址:",&arr[1]) fmt.Println("第三个地址:",&arr[2]) fmt.Println("第四个地址:",&arr[3]) // 打印结果: // 第一个地址: 0xc000052120 // 第二个地址: 0xc000052128 // 第三个地址: 0xc000052130 // 第四个地址: 0xc000052138 }
3.数组的使用
访问数组元素
数组名[下标] 比如:你要使用a数组的第三个元素 a[2]
package main import "fmt" func main() { var intArr [4]int intArr[0] = 10 intArr[1] = 20 intArr[2] = 30 intArr[3] = 40 fmt.Println("第一个地址:", &intArr[0]) fmt.Println("第二个地址:", &intArr[1]) fmt.Println("第三个地址:", &intArr[2]) fmt.Println("第四个地址:", &intArr[3]) // 打印结果: // 第一个地址: 0xc000052120 // 第二个地址: 0xc000052128 // 第三个地址: 0xc000052130 // 第四个地址: 0xc000052138 // 遍历数组打印 for i := 0; i < len(intArr); i++ { fmt.Println(intArr[i]) } }
4.四种初始化数组的方式
package main import "fmt" func main() { // 四种数组的初始化 // 法一 var numArr01 [3]int = [3]int{1, 2, 3} fmt.Println("numArr01", numArr01) // 法二 var numArr02 = [3]int{4, 5, 6} fmt.Println("numArr02", numArr02) // 法三 var numArr03 = [...]int{7, 8, 9} fmt.Println("numArr03", numArr03) // 法四 var numArr04 = [...]int{1:11, 0:10, 2:12} // 按下表的位置顺序输出,有序 fmt.Println("numArr04", numArr04) }
5.数组的变量
方式1 :常规变量
package main import "fmt" func main() { // for 遍历数组 var arrDemo = [...]int{1,2,3,4,5} for i := 0; i < len(arrDemo); i++ { fmt.Println(arrDemo[i]) } }
方式二:for-range结构遍历
GO 语言一种独有的结构,可以用来遍历访问数组的元素。
基本语法
for index,value := range arr {
....
}
说明:
(1)第一个返回值index是数组的下标
(2)第二个value是在该下标位置的值
(3)仅在for循环内部可见的局部变量
(4)遍历数组元素的时候,如果不想使用下标index,可以直接把下标index标为下划线_
(5)index和value 的名称不是固定的,可自定义
举例:
package main import "fmt" func main() { var arrDemo = [...]int{1,2,3,4,5} // for-range遍历方式 for index, value := range arrDemo{ fmt.Println(index,value) } }
6.数组的使用注意事项和细节
(1)数组是多个相同类型数据的结合,一个数组一旦声明定义了,其长度是固定的,不能动态变化。
(2)var arr[]int 这时arr就是一个slice切片。
(3)数组中的元素可以是任何数据类型,包括值类型和引用类型,但是不能混用
(4)数组创建后,如果没有赋值,有默认值
数值类型数组:默认值为0
字符串数组: 默认为””
bool数组: 默认值为false
(5)使用数组的步骤1.声明数组并开辟空间 2.给数组各个元素赋值 3. 使用数组
(6)数组的下标是从0开始的
(7)数组的下标必须在指定范围内使用,否则报panic:数组越界,比如:
var arr [5]int 则有效下标为0-4
(8)GO的数组属值类型,在默认情况下是值传递,因此会进行拷贝。数组间不会互相影响。
(9)如想在其中函数中,去修改原来的数组,可以使用引用传递(指针方式)。
(10)长度是数组的一部分,在传递函数参数时,需要考虑数组的长度。
举例:(8),(9)
package main import "fmt" func test(arr [3]int) { arr[0] = 88 } func demo(arr *[3]int) { (*arr)[1] = 99 } func main() { arr := [3]int{11,22,33} fmt.Println(arr) test(arr) // 数组的值不会因为调用的函数改变而改变 // 拷贝的思想 fmt.Println(arr) // demo 修改指针的方法可以实现修改数组里面的值 // 传地址 demo(&arr) fmt.Println(arr) }
7.数组的应用案例
1.创建一个byte类型的26个元素的数组,分别放置A-Z。使用for循环访问所有元素并打印
出来。提示:字符数据运算 A + 1 > B
package main import "fmt" func main(){ // 创建一个byte类型的26个元素的数组,分别放置A-Z。使用for循环访问所有元素并打印 // 出来。提示:字符数据运算 A + 1 > B // 思路: //1.声明一个数组 var myChars [26]bytes // 2.使用for循环,利用 字符可以进行运算的特点来赋值 'A' + 1 -> 'B' var myChars [26]byte for i := 0; i < 26; i++ { myChars[i] = 'A' + byte(i) // 注意需要将 i = > byte } for i:=0; i < 26; i++ { fmt.Printf("%c ",myChars[i]) } }
2.请求出一个数组最大值,并得到对应的下标
package main import "fmt" func main() { // 请求出一个数组最大值,并得到对应的下标 // 思路 假定第一个元素就是最大值,下标是0 // 然后从第二个元素开始循环比较,如果发现有更大,则交换 var intArr[5] int = [...]int {1,-1,-99,90,11} maxVal := intArr[0] maxValindex := 0 for i:=1; i<len(intArr); i++ { if maxVal < intArr[i] { maxVal = intArr[i] maxValindex = i } } fmt.Printf("maxVal=%v maxValindex=%v",maxVal,maxValindex) }
3.请求一个数组的和和平均值。 for range
package main import "fmt" func main(){ // 请求一个数组的和和平均值。 for range var intArr[5] int = [...]int {1,-1,-5,90,11} sum := 0 for _,val := range intArr{ // 求和 sum += val } fmt.Println(sum,float64(sum)/float64(len(intArr))) }
4.一个数组的反转
package main import "fmt" import "math/rand" import "time" func main() { // 一个数组的反转 // 要求:随机生成5个数,并将其反转打印 // rand.Intn() 生成(0,n)的随机数 // 反转打印,交换的次数是 len / 2 // 第一个与倒数第一 第二与倒数第二 var intArr [5] int // 为了每次生成的随机数不一样 rand.Seed(time.Now().UnixNano()) for i:=0; i<len(intArr); i++ { intArr[i] = rand.Intn(100) // } fmt.Println(intArr) temp := 0 for i:=0; i < len(intArr) / 2; i ++{ temp = intArr[len(intArr) - 1 - i] intArr[len(intArr) -1 - i] = intArr[i] intArr[i] = temp } fmt.Println(intArr) }
二.切片 Slice
1.切片的基本介绍
(1)slice 就是切片的意思
(2)切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制。
(3)切片的使用和数组类似,遍历切片,访问切片的元素和求切片长度len(slice) 都一样。
(4)切片的长度是可以变化的,因此切片是一个可以动态变化的数组
(5)切片定义的基本语法:(特别像python的列表)
var 切片名 [] 类型
举例:
package main import "fmt" func main() { // 先定义一个数组 var intArr [5]int = [...]int{1, 22, 33, 66, 99} // 定义一个切片 slice := intArr[1:3] // 表示引用 intArr数组的下标为1的元素 到 下标为3 的元素,结束下标的元素不算在内 fmt.Println(slice) // [22 33] fmt.Println(len(slice)) // 2 fmt.Println(cap(slice)) // 切片的容量是可以动态变化的,一般是slice的两倍 }
2.切片在内存中形式
(1)slice的确是一个引用类型
(2)slice从底层来说,其实就是一个数据结构struct结构体
(3)slice 里面有三个部分,第一部分是引用数据开始的指针地址,第二部分是存放数据大小,第三部分是存放cap容量。
slice[1] = 44 // 改变切片的值,会使得切片引用的数组相应的数据也改变 fmt.Println(intArr[2]) /
3.切片的使用
(1)第一种方式
定义一个切片,然后让切片去引用一个已经创建好的数组。
如下:
package main import "fmt" func main() { // 先定义一个数组 var intArr [5]int = [...]int{1, 22, 33, 66, 99} // 定义一个切片 slice := intArr[1:3] // 表示引用 intArr数组的下标为1的元素 到 下标为3 的元素,结束下标的元素不算在内 fmt.Println(slice) // [22 33] }
(2)第二种方式
通过make来创建切片
基本语法:
var 切片名 [] type = make([],len,[cap])
type:就是数据类型
len:大小
cap:指定切片容量,可选
package main import "fmt" func main() { var slice []int = make([]int, 4, 10) fmt.Println(slice) fmt.Println("slice len=",len(slice),"slice cap=",cap(slice)) slice[0] = 100 slice[2] = 200 fmt.Println(slice) }
总结:
—1—通过make方式创建切片可以指定切片的大小和容量
—2—如果没有给切片的各个元素赋值,那么就会使用默认值(int,float=>0 string=>””,bool=>false)
—3—通过make方式创建的切片对应的数组是由make底层维护,对外不可见
(3)第三种方式
定义一个切片,直接就指定具体数组,使用原理类似make的方式。
package main import "fmt" func main() { // 第三种方式 var strSlice []string = []string {"tom","jack","mary"} fmt.Println(strSlice) }
这种方法没有指定cap的大小,所有与len的大小相同。
4.切片的遍历
切片的遍历和数组一样,也有两种方式。
(1)for 循环常规方式遍历
(2)for-range结构遍历切片
为上面两种方式举例:
package main import "fmt" func main() { // 使用常规的for循环遍历切片 var arr [5]int = [...]int{10, 20, 30, 40, 50} slice := arr[1:4] // 第二个元素到第4个元素 for i := 0; i < len(slice); i++ { fmt.Printf("i=%v v=%v\n",i,slice[i]) } // 使用for--range方式遍历切片 for i, v := range slice { fmt.Printf("i=%v v=%v\n", i, v) } }
5.切片注意事项和细节说明
(1)切片初始化时 var slice = arr[startindex:endindex]
说明:从arr数组下标为startindex开始,取到下标为endindex的元素(不含最后那一个元素)。
(2)切片初始化时,仍然不能越界。范围在[0-len(arr)]之间,但是可以动态增长。
1)var slice = arr[0:end] 可以简写var slice = arr[:end]
2)var slice = arr[strat:len(arr)] 可以简写:var slice = arr[start:]
3) var slice = arr[0:len(arr)] 可以简写:var slice = arr[:]
(3)cap是一个内置函数,用于统计切片的容量,即最大可以存放多少个元素。
(4)切片定义完后,还不能使用,因为本身是一个空的,需要让其引用到一个数组,或者make一个空间供切片来使用。
(5)切片还可以切片。
(6)切片是引用类型,所以在传递时,遵守引用传递机制。要改元素会一起变化。
举例:这个是一个原切片数据和修改后的数据都变化
package main import "fmt" func main() { var slice1 []int var arr [5]int = [...]int {1,2,3,4,5} // 数组 slice1 = arr[:] // slice1 属于arr数组的一部分,slice2的指针和slice1存的数据一样 var slice2 = slice1 slice2[0] = 10 fmt.Println(slice1) fmt.Println(slice2) fmt.Println(arr) // [10 2 3 4 5] // [10 2 3 4 5] // [10 2 3 4 5]
举例2:这个是一个原切片数据不变,修改后的数据变化
package main import "fmt" func test(slice []int) { slice[0] = 100 // 这里修改slice[0] } func main(){ // var slice = []int {1,2,3,4} fmt.Println("slice=",slice) // [1,2,3,4] test(slice) fmt.Println("slice=",slice) }
6.切片中的使用方法append
用append内置函数,可以对切片进行动态追加。
切片append操作的底层原来分析:
(1)切片append操作的本质就是数组扩容
(2)go底层会创建一个新的数组newArr(安装扩容大小)
(3)将slice原来包含的元素拷贝到新的数组newArr
(4)slice重新引用到newArr(就是整个数组的指针地址变化)
(5)注意newArr是底层来维护的
举例:
package main import "fmt" func main(){ // 用append 内置函数,可以对切片进行动态追加 var slice []int = []int {100,200,300} fmt.Printf("slice=%v\n",slice) slice = append(slice,400) fmt.Println("new_slice=",slice) var demo_slice []int = []int{1,2,3} // 切片追加切片 slice = append(slice,demo_slice...) // ... 不能忘 fmt.Println("new_new_slice=",slice) }
7.切片的拷贝操作
切片使用copy内置函数完成拷贝
copy拷贝的数据,数据空间是独立的,之间相互不影响。
如果要拷贝的切片元素个数大于新的切片的元素个数,那么只能够拷贝到新的切片的最大个数的数据到新的切片中。
举例:
package main import "fmt" func main(){ var a []int = []int {1,2,3,4,5} var slice = make([]int, 10) // 10个0的一个切片 fmt.Println(slice) copy(slice,a) fmt.Println(slice) // 将a上的元素复制到全为0的切片中 // 切片才能进行拷贝操作 // [0 0 0 0 0 0 0 0 0 0] // [1 2 3 4 5 0 0 0 0 0] }
8.string 与slice
(1)string底层是一个byte数组,因此string也可以进行切片处理
(2)string是不可变的,也就是说不能通过str[0] = ‘z’ 方式来修改字符串的内容
(3)如果需要修改字符串,可以先将string-->[]byte再重新转回来 或者 []rune -->修改-->重写转成string (其实就相当于形成一个新的字符串)
举例:修改字符串举例
package main import "fmt" func main() { //string底层是一个byte数组 ,因此string也可以进行切片 str := "hello_hello_hsz" // 使用切片获取hsz slice := str[12:] fmt.Println("slice=", slice) // string --> byte 转成byte数组 arr := []byte(str) arr[0] = 'z' str = string(arr) fmt.Println("str=", str) // 可以处理英文和数字,但是不能处理中文 // []byte 字节来处理,而一个汉字,是3个字节,因此就会出现乱码 // 解决方法: string 转成 []rune 因此rune是按字符来计算处理的,兼容汉字 arr1 := []rune(str) arr1[0] = '好' str = string(arr1) fmt.Println("str=", str) }
9.斐波那契数列放到切片中
package main import "fmt" func fbn(n int) []uint64 { // 声明一个切片,切片大小n fbnSlice := make([]uint64, n) // 第一个数和第二个数的斐波那契为1 if n == 1 { fbnSlice[0] = 1 } else if n == 2 { fbnSlice[0] = 1 fbnSlice[1] = 1 } else { fbnSlice[0] = 1 fbnSlice[1] = 1 // 进行for循环来存放斐波那契的数列 for i := 2; i < n; i++ { fbnSlice[i] = fbnSlice[i-1] + fbnSlice[i-2] } } return fbnSlice } func main() { // 编写一个函数 fbn(n int ) // 1. 可以接收一个 n int // 2.能够将斐波那契的数列放到切片中 // 提示: slice[0] = 1 slice[1] = 1 slice[2] = 2 slice[3] = 3 slice[4] = 5 fnbSlice := fbn(4) fmt.Println("fbnSlice=", fnbSlice) }