数组:一组数组
什么是数组
- 一组数
- 数组需要是相同类型的数据的集合
- 数组是需要定义大小的
- 数组一旦定义了大小是不可以改变的。
数组的声明
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)
}