回顾函数
-
什么是函数 (一段代码的集合)
-
函数的声明(多个参数、多个返回值)
-
函数的基本调用 ()
-
可变参数 ...
-
函数的作用域
-
递归函数 : 自己调用自己
-
defer 、延迟函数执行 ,研究程序的执行逻辑,倒序执行,参数先传递进去的
-
函数的本质:函数本身也是一种数据类型
-
Go语言支持函数式编程
-
匿名函数
-
回调函数
-
闭包结构
-
数组:一组数
什么是数组
-
一组数
-
数组需要是相同类型的数据的集合
-
数组是需要定义大小的
-
数组一旦定义了大小是不可以改变的。
-
数组的声明
package main import "fmt" // 数组 // 数组和其他变量定义没什么区别,唯一的就是这个是一组数,需要给一个大小 [6]int [10]string // 数组是一个相同类型数据的==有序==集合,通过下标来取出对应的数据 // 数组几个特点: // 1、长度必须是确定的,如果不确定,就不是数组,大小不可以改变 // 2、元素必须是相,同类型不能多个类型混合, [any也是类型,可以存放任意类型的数据] // 3、数组的中的元素类型,可以是我们学的所有的类型,int、string、float、bool、array、slice、map func main() { // array数组定义,变量 // 数组也是一个数据类型 // 数组的定义: [数组的大小size]变量的类型 , // 我们定义了一组这个类型的数组集合,大小为size,最多可以保存size个数 var arr1 [5]int // [0,0,0,0,0] // 给数组赋值,下标index,所有的数组下标都是从0开始的。 arr1[0] = 100 arr1[1] = 200 arr1[2] = 300 arr1[3] = 400 arr1[4] = 500 // 打印数组 fmt.Println(arr1) // 取出数组中的某个元素 fmt.Println(arr1[1]) // 数组中的常用方法 len()获取数组的长度 cap() 获取数组的容量 fmt.Println("数组的长度:", len(arr1)) fmt.Println("数组的容量:", cap(arr1)) // 修改数组的值,index 1 代表的第二个数据了 arr1[1] = 10 fmt.Println(arr1) fmt.Println(arr1[1]) }
初始化数组的几种方式
package main import "fmt" // 数组的赋值初始化 func main() { // 在定义数组的时候就直接初始化 var arr1 = [5]int{1, 2, 3, 4, 5} fmt.Println(arr1) // 快速初始化 := arr2 := [5]int{1, 2, 3, 4, 5} fmt.Println(arr2) // 比较特殊的点 // 数据如果来自用户,我不知道用户给我多少个数据,数组 // ... 代表数组的长度 // Go的编译器会自动根据数组的长度来给 ... 赋值,自动推导长度 // 注意点:这里的数组不是无限长的,也是固定的大小,大小取决于数组元素个数。 var arr3 = [...]int{1, 2, 3, 4, 5, 5, 6, 7, 8, 8, 8} fmt.Println(len(arr3)) fmt.Println(arr3) // 数组默认值,我只想给其中的某几个index位置赋值。 // {index:值} var arr4 [10]int arr4 = [10]int{1: 100, 5: 500} fmt.Println(arr4) // [0 100 0 0 0 500 0 0 0 0] }
遍历数组元素
package main import "fmt" /* 1、直接通过下标获取元素 arr[index] 2、 0-len i++ 可以使用for循环来结合数组下标进行遍历 3、for range:范围 (new) */ func main() { var arr1 = [5]int{1, 2, 3, 4, 5} fmt.Println(arr1[0]) fmt.Println(arr1[1]) fmt.Println(arr1[2]) fmt.Println(arr1[3]) fmt.Println(arr1[4]) // 错误:index 5 out of bounds [0:5] 数组下标越界 // 数组的长度只有5,你要取出6个元素,不可能取出 //fmt.Println(arr1[5]) fmt.Println("------------------") // 获取数组的长度 len() // 下标从0开始,不能<= for i := 0; i < len(arr1); i++ { fmt.Println(arr1[i]) } fmt.Println("------------------") // goland 快捷方式 数组.for,未来循环数组、切片很多时候都使用for range // for 下标,下标对应的值 range 目标数组切片 // 就是将数组进行自动迭代。返回两个值 index、value // 注意点,如果只接收一个值,这个时候返回的是数组的下标 // 注意点,如果只接收两个值,这个时候返回的是数组的下标和下标对应的值 for _, value := range arr1 { fmt.Println(value) } }
数组是值类型
package main import "fmt" // 数组是值类型: 所有的赋值后的对象修改值后不影响原来的对象。 func main() { //数组类型的样子 [size]type arr1 := [4]int{1, 2, 3, 4} arr2 := [5]string{"kuangshen", "xuexiangban"} fmt.Printf("%T\n", arr1) // [4]int fmt.Printf("%T\n", arr2) // [5]string // 数组的值传递和int等基本类型一致 arr3 := arr1 fmt.Println(arr1) fmt.Println(arr3) // 修改arr3观察arr1是否会变化 arr3[0] = 12 fmt.Println(arr1) fmt.Println(arr3) // 数组是值传递,拷贝一个新的内存空间 }
数组的排序(面试)
arr := [6]int{1,2,3,4,5,0} // 升序 ASC : 从小到大 0,1,2,3,4,5 A-Z 00:00-24:00 // 降序 DESC : 从大到小 5,4,3,2,1,0
数组的排序,一组数是乱序的,我们如何将它按照升序或者降序排列。
排序算法:冒泡排序、插入排序、选择排序、希尔、堆、快排.....
数据结构:数组就是最简单的数据结构之一
算法:冒泡排序
package main import "fmt" // 冒泡:每次筛选出一个最大或者最小的数. /* index 0 1 2 3 4 value 12 99 79 48 55 */ // 冒泡排序逻辑,两两比较,大的往后移或者前移。 大 // 第一轮 : 12 79 48 55 99 // 5 // 第二轮 : 12 48 55 79 99 // 4 // 第三轮 : 12 48 55 79 99 // 3 // // 第四轮 : 12 48 55 79 99 // // 第五轮 : 12 48 55 79 99 // 代码实践 /* // 两个数判断,如果一个数大,则交换位置,大放到后面 if arr[x] > arr[x+1] { arr[x], arr[x+1] = arr[x+1],arr[x] } // 多轮判断,for, 循环次数 【数组大小】 */ func main() { arr := [...]int{12, 99, 79, 48, 55, 1, 110, 111, 23, 52, 354, 2, 3412, 3, 12, 31} fmt.Println("初始数组:", arr) // 冒泡排序 // 1、多少轮 for i := 1; i < len(arr); i++ { // 2、筛选出来最大数字以后,我们下次不需要将它再计算了 for j := 0; j < len(arr)-i; j++ { // 比较 // 改变升降序只需要改变符号即可 if arr[j] < arr[j+1] { arr[j], arr[j+1] = arr[j+1], arr[j] } } // 作业:排序已经结束,如果有个机制可以判断已经结束了,不需要再往后了。 // if xxx { // break // } fmt.Println("第", i, "交换:", arr) } }
多维数组(简单的)
一维数组: 线性的,一组数
二维数组: 表格性的,数组套数组
三维数组: 立体空间性的,数组套数组套数组
xxxx维数组:xxx,数组套数组套数组.....
三维数组,x,y,z,立体空间一样的。
package main import "fmt" func main() { // 定义一个多维数组 二维 arr := [3][4]int{ {0, 1, 2, 3}, // arr[0] //数组 {4, 5, 6, 7}, // arr[1] {8, 9, 10, 11}, // arr[2] } // 二维数组,一维数组存放的是一个数组 fmt.Println(arr[0]) // 要获取这个二维数组中的某个值,找到对应一维数组的坐标,arr[0] 当做一个整体 fmt.Println(arr[0][1]) fmt.Println("------------------") // 如何遍历二维数组 for i := 0; i < len(arr); i++ { for j := 0; j < len(arr[i]); j++ { fmt.Println(arr[i][j]) } } // for range for i, v := range arr { fmt.Println(i, v) } }
切片:对数组的一个抽象Slice
Go 语言切片是对数组的抽象。
Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型 切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
切片是一种方便、灵活且强大的包装器,切片本身没有任何数据,他们只是对现有数组的引用。
切片与数组相比,不需要设定长度,在[]中不用设定值,相对来说比较自由
从概念上面来说 slice 像一个结构体,这个结构体包含了三个元素:
-
指针:指向数组中 slice 指定的开始位置
-
长度:即slice的长度
-
最大长度:也就是 slice 开始位置到数组的最后位置的长度
定义切片
package main import "fmt" // 定义切片 func main() { arr := [4]int{1, 2, 3, 4} // 定长 fmt.Println(arr) var s1 []int // 变长,长度是可变的 fmt.Println(s1) // 切片的空判断,初始的切片中,默认是 nil if s1 == nil { fmt.Println("切片是空的") } s2 := []int{1, 2, 3, 4} // 切片 变长 fmt.Println(s2) fmt.Printf("%T,%T\n", arr, s2) // [4]int,[]int fmt.Println(s2[1]) }
make来创建切片
package main import "fmt" func main() { // make() // make([]Type,length,capacity) // 创建一个切片,长度,容量 s1 := make([]int, 5, 10) fmt.Println(s1) fmt.Println(len(s1), cap(s1)) // 思考:容量为10,长度为5,我能存放6个数据吗? s1[0] = 10 s1[7] = 200 // index out of range [7] with length 5 // 切片的底层还是数组 [0 0 0 0 0] [2000] // 直接去赋值是不行的,不用用惯性思维思考 fmt.Println(s1) // 切片扩容 }
切片扩容
package main import "fmt" func main() { s1 := make([]int, 0, 5) fmt.Println(s1) // 切片扩容,append() s1 = append(s1, 1, 2) fmt.Println(s1) // 问题:容量只有5个,那能放超过5个的吗? 可以,切片是会自动扩容的。 s1 = append(s1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 7) fmt.Println(s1) // 切片扩容之引入另一个切片。 // new : 解构 slice.. ,解出这个切片中的所有元素。 s2 := []int{100, 200, 300, 400} // slice = append(slice, anotherSlice...) // ... 可变参数 ...xxx // [...] 根据长度变化数组的大小定义 // anotherSlice... , slice...解构,可以直接获取到slice中的所有元素 // s2... = {100,200,300,400} s1 = append(s1, s2...) }
遍历切片
package main import "fmt" func main() { s1 := make([]int, 0, 5) fmt.Println(s1) // 切片扩容,append() s1 = append(s1, 1, 2) fmt.Println(s1) // 问题:容量只有5个,那能放超过5个的吗? 可以,切片是会自动扩容的。 s1 = append(s1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 7) fmt.Println(s1) // 切片扩容之引入另一个切片。 // new : 解构 slice.. ,解出这个切片中的所有元素。 s2 := []int{100, 200, 300, 400} // slice = append(slice, anotherSlice...) // ... 可变参数 ...xxx // [...] 根据长度变化数组的大小定义 // anotherSlice... , slice...解构,可以直接获取到slice中的所有元素 // s2... = {100,200,300,400} s1 = append(s1, s2...) // 遍历切片 for i := 0; i < len(s1); i++ { fmt.Println(s1[i]) } for i := range s1 { fmt.Println(s1[i]) } }
扩容的内存分析
// 1、每个切片引用了一个底层的数组 // 2、切片本身不存储任何数据,都是底层的数组来存储的,所以修改了切片也就是修改了这个数组中的数据 // 3、向切片中添加数据的时候,如果没有超过容量,直接添加,如果超过了这个容量,就会自动扩容,成倍的增加, copy // - 分析程序的原理 // - 看源码 // // 4、切片一旦扩容,就是重新指向一个新的底层数组。
package main import "fmt" // 切片扩容的内存分析 // 结论 // 1、每个切片引用了一个底层的数组 // 2、切片本身不存储任何数据,都是底层的数组来存储的,所以修改了切片也就是修改了这个数组中的数据 // 3、向切片中添加数据的时候,如果没有超过容量,直接添加,如果超过了这个容量,就会自动扩容,成倍的增加, copy // - 分析程序的原理 // - 看源码 // // 4、切片一旦扩容,就是重新指向一个新的底层数组。 func main() { // 1、cap 是每次成倍增加的 // 2、只要容量扩容了,地址就会发生变化 s1 := []int{1, 2, 3} fmt.Println(s1) fmt.Printf("len:%d,cap:%d\n", len(s1), cap(s1)) // len:3,cap:3 fmt.Printf("%p\n", s1) // 0xc000016108 s1 = append(s1, 4, 5) fmt.Printf("len:%d,cap:%d\n", len(s1), cap(s1)) // len:5,cap:6 fmt.Printf("%p\n", s1) // 0xc000010390 s1 = append(s1, 6, 7, 8) fmt.Printf("len:%d,cap:%d\n", len(s1), cap(s1)) // len:8,cap:12 fmt.Printf("%p\n", s1) // 0xc00005e060 s1 = append(s1, 9, 10) fmt.Printf("len:%d,cap:%d\n", len(s1), cap(s1)) // len:10,cap:12 fmt.Printf("%p\n", s1) // 0xc00005e060 s1 = append(s1, 11, 12, 13, 14) fmt.Printf("len:%d,cap:%d\n", len(s1), cap(s1)) // len:14,cap:24 fmt.Printf("%p\n", s1) // 0xc00010c000 }
copy
package main import "fmt" // copy 方法 func main() { numbers := []int{1, 2, 3} fmt.Printf("len=%d,cap=%d,slice=%v\n", len(numbers), cap(numbers), numbers) // 方法一: 直接使用make创建切片扩容 numbers2 := make([]int, len(numbers), cap(numbers)*2) // 将原来的底层数据的值拷贝到新的数组中 // func copy(dst, src []Type) int copy(numbers2, numbers) fmt.Printf("len=%d,cap=%d,slice=%v\n", len(numbers2), cap(numbers2), numbers2) }
下节预告
-
通过数组创建切片
-
切片是引用类型
-
深拷贝、浅拷贝
-
map
-
map怎么初始化
-
map怎么用
-
map怎么遍历
-
map结合slice
-