数组
什么是数组
数组的特点:
长度必须是确定的,如果不确定,就不是数组,大小不可以改变;
数组元素必须是相同类型的,不能多个不同类型的混合;
数组中的元素类型,可以是学的所有类型,int、float、string、bool、array、slice、map等
数组的声明
package main
import "fmt"
func main() {
// 数组的定义:[大小size]类型
// 定义了一组数的集合,表示最多可以保存size个数
var arr [5]int
// 给数组赋值,所有数组的下标都是从0开始的
arr[0] = 100
arr[1] = 200
arr[2] = 300
arr[3] = 400
arr[4] = 500
// 打印数组
fmt.Println(arr) // [100 200 300 400 500]
// 取出数组第二个数据
fmt.Println(arr[1]) // 200
// 数组中常用方法,len()获取数组的长度,cap()获取数组的容量
fmt.Printf("数组的长度:%d\n", len(arr)) // 5
fmt.Printf("数组的容量:%d\n", cap(arr)) // 5
// 修改数组第二个数据的值
arr[1] = 20
fmt.Println(arr) // [100 20 300 400 500]
}
初始化数组的几种方式
package main
import "fmt"
func main() {
// 1、在定义数组的时候直接初始化
var arr1 = [5]int{1, 2, 3, 4, 5} // [1 2 3 4 5]
fmt.Println(arr1)
// 2、快速初始化 :=
arr2 := [5]int{1, 2, 3, 4, 5} // [1 2 3 4 5]
fmt.Println(arr2)
// 特殊的地方
// 如果用户不知需要存放多少个数据,可以使用 ... 代表数组长度
// Go编译器会自动根据存放数据的长度给 ... 赋值,自动推导长度
// 注意:这样使用的数组不是无限长,也是固定大小的,长度取决于存放的数据个数
var arr3 = [...]int{1, 2, 3, 4, 5, 6, 7, 8}
fmt.Println(arr3) // [1 2 3 4 5 6 7 8]
fmt.Println(len(arr3)) // 8
// 数组的默认值为0
// 如果只想给指定的index下标赋值,可以{index:值}
var arr4 [6]int
arr4 = [6]int{0: 1, 5: 6}
fmt.Println(arr4) // [1 0 0 0 0 6]
}
遍历数组元素
直接通过下标获取元素,arr[index];
可以使用for循环,结合数组下标进行遍历;
使用 for range
package main
import "fmt"
func main() {
var arr = [5]int{1, 2, 3, 4, 5}
fmt.Println(arr[0])
fmt.Println(arr[1])
fmt.Println(arr[2])
fmt.Println(arr[3])
fmt.Println(arr[4])
// 错误:invalid argument: index 5 out of bounds [0:5] 数组下标越界
// 数组长度只有5,不可能取出第六个元素
// fmt.Println(arr[5])
fmt.Println("---------------------")
for i := 0; i < len(arr); i++ {
fmt.Println(arr[i])
}
fmt.Println("----------------------")
// 快捷方式 数组.for
// 循环数组、切片,经常会使用 for range
// 就是将数组进行自动迭代,返回index、value
// 注意:如果只接受一个值,则返回的是数组下标
// 注意:如果接受两个值,则返回的是数组下标和下标对应的值
for index, value := range arr {
fmt.Printf("数组下标:%d 下标对应的值:%d\n", index, value)
}
for _, value := range arr {
fmt.Printf("下标对应的值:%d\n", value)
}
}
数组是值类型
数组是值类型:所有赋值的对象被修改值后,不会影响原来对象值
package main
import "fmt"
func main() {
arr1 := [5]int{1, 2, 3, 4, 5}
arr2 := [2]string{"str1", "str2"}
// 打印数组类型 [size]type
fmt.Printf("%T\n", arr1) // [5]int
fmt.Printf("%T\n", arr2) // [2]string
// 数组的值传递和int等基本类型是一致的
arr3 := arr1
fmt.Println(arr3) // [1 2 3 4 5]
// 修改arr3观察arr1是否也变化
arr3[0] = 10
fmt.Println(arr1) // [1 2 3 4 5]
fmt.Println(arr3) // [10 2 3 4 5] 所以数组是值传递,arr3拷贝了一个新的内存空间
}
数组的冒泡排序
package main
import "fmt"
func main() {
var arr = [10]int{5, 4, 7, 0, 8, 3, 2, 1, 6, 9}
for i := 0; i < len(arr)-1; i++ {
for j := 0; j < len(arr)-i-1; j++ {
if arr[j] > arr[j+1] {
arr[j], arr[j+1] = arr[j+1], arr[j]
}
}
}
fmt.Println(arr)
}
多维数组
一维数组: 线性的,一组数
二维数组: 表格性的,数组套数组
三维数组: 立体空间性的,数组套数组套数组
package main
import "fmt"
func main() {
// 二维数组定义
arr := [3][4]int{
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
}
// 二维数组相当于一维数组存放的是一个数组
fmt.Println(arr[0]) // [1 2 3 4]
fmt.Println(arr[0][0]) // 1
// 二维数组遍历
for i := 0; i < len(arr); i++ {
for j := 0; j < len(arr[i]); j++ {
fmt.Println(arr[i][j])
}
}
for i, v := range arr {
fmt.Println(i, v)
}
// 0 [1 2 3 4]
// 1 [5 6 7 8]
// 2 [9 10 11 12]
}
切片
什么是切片
Go语言中,切片是对数组的一个抽象。
Go数组长度是不能改变的,在许多特定的场景下就不太适用。因此Go提供了一种灵活且功能强悍的内置类型切片(“动态数组”)。
与数组相比,切片的长度是不固定的,可以追加元素,在追加时可能会使切片的容量增大。
切片是一种方便、灵活且强大的包装器,切片本身没有数据,只是对现有数组的引用。
在定义切片时,不需要设定长度,比较自由。
从概念上讲,slice像是一个结构体,其中包含:
指针:指向数组中slice指定的开始位置
长度:即slice的长度
最大长度:即slice开始位置到数组最后位置的长度
定义切片
package main
import "fmt"
func main() {
arr := [5]int{1, 2, 3, 4, 5} // 数组定长
fmt.Println(arr)
var s1 []int // 切片变长,长度是可变的
fmt.Println(s1) // []
// 切片的空判断 初始的切片中,默认是nil
if s1 == nil {
fmt.Println("切片是空的") // 切片是空的
}
s2 := []int{1, 2, 3, 4, 5}
fmt.Printf("%T %T\n", arr, s2) // [5]int []int
fmt.Println(s2) // [1 2 3 4 5]
fmt.Println(s2[1]) // 2
}
make创建切片
package main
import "fmt"
func main() {
// make([]Type, length, capacity) 创建一个切片,长度,容量
s1 := make([]int, 5, 10)
fmt.Println(s1) // [0 0 0 0 0]
fmt.Printf("长度:%d 容量:%d\n", len(s1), cap(s1)) // 长度:5 容量:10
// 思考:容量为10,长度为5 的切片可以给第六个数据元素赋值吗?
s1[0] = 10
s1[6] = 70 // 运行时错误 runtime error: index out of range [6] with length 5
// 切片的底层还是数组,所以直接赋值会报错的
}
切片扩容
package main
import "fmt"
func main() {
s1 := make([]int, 0, 5)
fmt.Println(s1) // []
// append() 切片扩容
s1 = append(s1, 1, 2)
fmt.Println(s1) // [1 2]
fmt.Printf("长度:%d 容量:%d\n", len(s1), cap(s1)) // 长度:2 容量:5
// 思考:容量只有5个,可以存放超过5个的吗?
s1 = append(s1, 3, 4, 5, 6, 7, 8)
fmt.Println(s1) // [1 2 3 4 5 6 7 8]
fmt.Printf("长度:%d 容量:%d\n", len(s1), cap(s1)) // 长度:8 容量:10
// 答案是可以的,切片会自动扩容的
// 切片可以追加另一个切片
s2 := []int{9, 10, 11, 12, 13}
// slice... 解构,和javascript中的结构差不多,可以直接获取slice中的所有元素
s1 = append(s1, s2...)
fmt.Println(s1) // [1 2 3 4 5 6 7 8 9 10 11 12 13]
fmt.Printf("长度:%d 容量:%d\n", len(s1), cap(s1)) // 长度:13 容量:20
}
遍历切片
package main
import "fmt"
func main() {
s1 := []int{1, 2, 3, 4, 5}
for i := 0; i < len(s1); i++ {
fmt.Println(s1[i])
}
for i := range s1 {
fmt.Println(s1[i])
}
}
扩容的内存分析
每个切片引用了一个底层数组;
切片本身不存储任何数据,都是底层的数组存储的,所以修改了切片也就是修改了底层数组的数据;
向切片中添加数据的时候,如果没有超过容量,直接添加;如果超过了,就会自动扩容,成倍的增加;
切片扩容后,会指向一个新的底层数组。
package main
import "fmt"
func main() {
s1 := []int{1, 2, 3, 4}
fmt.Println(s1) // [1 2 3 4]
fmt.Printf("len:%d cap:%d\n", len(s1), cap(s1)) // len:4 cap:4
fmt.Printf("%p\n", s1) // 0xc0000121a0
fmt.Println("-------------------------------")
s1 = append(s1, 5, 6, 7)
fmt.Println(s1) // [1 2 3 4 5 6 7]
fmt.Printf("len:%d cap:%d\n", len(s1), cap(s1)) // len:7 cap:8
fmt.Printf("%p\n", s1) // 0xc00001a380
fmt.Println("-------------------------------")
s1 = append(s1, 8, 9)
fmt.Println(s1) // [1 2 3 4 5 6 7 8 9]
fmt.Printf("len:%d cap:%d\n", len(s1), cap(s1)) // len:9 cap:16
fmt.Printf("%p\n", s1) // 0xc00010a080
}
手动模拟底层扩容 copy方法
package main
import "fmt"
func main() {
s1 := []int{1, 2, 3, 4}
fmt.Printf("len=%d, cap=%d, slice=%v\n", len(s1), cap(s1), s1)
// len=4, cap=4, slice=[1 2 3 4]
// 使用make方法创建切片扩容
s2 := make([]int, len(s1), cap(s1)*2)
// func copy(dst, src []Type) int
// 将原来底层数组的值拷贝到新的数组中
copy(s2, s1)
fmt.Printf("len=%d, cap=%d, slice=%v\n", len(s2), cap(s2), s2)
// len=4, cap=8, slice=[1 2 3 4]
}
使用数组创建切片
package main
import "fmt"
func main() {
arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// 通过数组来创建切片 数组的截取 [start, end)
s1 := arr[:5]
s2 := arr[3:7]
s3 := arr[5:]
s4 := arr[:]
fmt.Println(s1) // [1 2 3 4 5]
fmt.Println(s2) // [4 5 6 7]
fmt.Println(s3) // [6 7 8 9 10]
fmt.Println(s4) // [1 2 3 4 5 6 7 8 9 10]
// 查看切片的长度和容量
fmt.Printf("len: %d cap: %d\n", len(s1), cap(s1)) // len: 5 cap: 10
fmt.Printf("len: %d cap: %d\n", len(s2), cap(s2)) // len: 4 cap: 7
fmt.Printf("len: %d cap: %d\n", len(s3), cap(s3)) // len: 5 cap: 5
fmt.Printf("len: %d cap: %d\n", len(s4), cap(s4)) // len: 10 cap: 10
// 查看数组和切片的内存地址
fmt.Printf("%p\n", &arr) // 0xc000014690
fmt.Printf("%p\n", s1) // 0xc000014690 指向了原数组
fmt.Printf("%p\n", s2) // 0xc0000146a8 指向了开始截断的下标
fmt.Printf("%p\n", &arr[3]) // 0xc0000146a8
fmt.Printf("%p\n", s3) // 0xc0000146b8 指向了开始截断的下标
fmt.Printf("%p\n", &arr[5]) // 0xc0000146b8
fmt.Printf("%p\n", s4) // 0xc000014690 指向了原数组
fmt.Println("----------------------------------")
// 修改数组值,切片也随之变化
arr[0] = 10
fmt.Println(arr) // [10 2 3 4 5 6 7 8 9 10]
fmt.Println(s1) // [10 2 3 4 5]
fmt.Println(s2) // [4 5 6 7]
fmt.Println(s3) // [6 7 8 9 10]
fmt.Println(s4) // [10 2 3 4 5 6 7 8 9 10]
// 修改切片的数据,数组的数据也随之变化(本质:修改的是底层数组)
s2[2] = 20
fmt.Println(arr) // [10 2 3 4 5 20 7 8 9 10]
fmt.Println(s1) // [10 2 3 4 5]
fmt.Println(s2) // [4 5 20 7]
fmt.Println(s3) // [20 7 8 9 10]
fmt.Println(s4) // [10 2 3 4 5 20 7 8 9 10]
fmt.Println("----------------------------------")
// 切片扩容
// 如果在切片容量内添加数据,会导致原数组数据发生修改
//s1 = append(s1, 11, 12, 13, 14, 15)
//fmt.Println(s1) // [10 2 3 4 5 11 12 13 14 15]
//fmt.Println(arr) // [10 2 3 4 5 11 12 13 14 15]
// 只有在扩容之后,才会发生拷贝,指向新数组
s1 = append(s1, 11, 12, 13, 14, 15, 16)
fmt.Println(s1) // [10 2 3 4 5 11 12 13 14 15 16]
fmt.Println(arr) // [10 2 3 4 5 20 7 8 9 10]
// 此时,切片地址和原数组地址不一样了
fmt.Printf("%p\n", s1) // 0xc0000240a0
fmt.Printf("%p\n", &arr) // 0xc000014690
}
切片是引用类型
package main
import "fmt"
func main() {
// 数组是值类型
arr1 := [5]int{1, 2, 3, 4, 5}
arr2 := arr1
arr1[0] = 10
fmt.Println(arr1) // [10 2 3 4 5]
fmt.Println(arr2) // [1 2 3 4 5]
// 切片是引用类型
s1 := []int{1, 2, 3, 4, 5}
s2 := s1
s1[0] = 10
fmt.Println(s1) // [10 2 3 4 5]
fmt.Println(s2) // [10 2 3 4 5]
// s1,s2指向了同一个地址
fmt.Printf("%p %p\n", s1, s2) // 0xc00000e3f0 0xc00000e3f0
}
深拷贝、浅拷贝
深拷贝:拷贝的是数据本身
值类型的数据,默认都是深拷贝,array、int、float、string、bool、struct...
浅拷贝:拷贝的是数据的地址,会造成多个变量指向同一块内存
引用类型的数据:slice、map
切片实现深拷贝 copy
package main
import "fmt"
func main() {
// 切片实现深拷贝 将原来切片中的数据拷贝到新切片中
// 第一种方法
s1 := []int{1, 2, 3, 4, 5}
s2 := make([]int, 0) // len: 0 cap: 0
for i := 0; i < len(s1); i++ {
s2 = append(s2, s1[i])
}
fmt.Println(s1, s2) // [1 2 3 4 5] [1 2 3 4 5]
s1[0] = 10
fmt.Println(s1, s2) // [10 2 3 4 5] [1 2 3 4 5]
// 第二种方法 copy
s3 := []int{5, 6, 7, 8}
fmt.Println(s2, s3) // [1 2 3 4 5] [5 6 7 8]
// 将s2中的元素拷贝到s3中
copy(s3, s2)
fmt.Println(s2, s3) // [1 2 3 4 5] [1 2 3 4]
}
函数中参数传递问题
按照数据的存储特点来分
值类型的数据:操作的是数据本身,int 、string、bool、float64、array...
引用类型的数据:操作的是数据的地址, slice、map、chan....
值传递 引用传递
package main
import "fmt"
func main() {
arr1 := [5]int{1, 2, 3, 4, 5}
fmt.Printf("start arr1: %d\n", arr1) // start arr1: [1 2 3 4 5]
update(arr1)
fmt.Printf("end arr1: %d\n", arr1) // end arr1: [1 2 3 4 5]
fmt.Println("----------------------------------")
s1 := []int{1, 2, 3, 4, 5}
fmt.Printf("start s1: %d\n", s1) // start s1: [1 2 3 4 5]
update2(s1)
fmt.Printf("end s1: %d\n", s1) // end s1: [10 2 3 4 5]
}
// 数组是值类型的
func update(arr [5]int) {
fmt.Printf("start arr: %d\n", arr) // start arr: [1 2 3 4 5]
arr[0] = 10
fmt.Printf("end arr: %d\n", arr) // end arr: [10 2 3 4 5]
}
// 切片是引用类型的
func update2(s []int) {
fmt.Printf("start s: %d\n", s) // start s: [1 2 3 4 5]
s[0] = 10
fmt.Printf("end s: %d\n", s) // end s: [10 2 3 4 5]
}
注意:在使用函数的时候,一定要特别注意参数的问题。如果是值类型的,很多传递都会是无效的。值类型的参数,如果想用函数来进行修改对应值,需要使用指针,指针变量->指向原来变量的值。
map
map的定义
Map是一种无序的键值对的集合。
无序:map[key]
键值对:key-value
Map最重要的是通过key快速检索数据,key类似于索引,指向数据的值。
Map是一种集合,所以可以像迭代数组和切片一样迭代数组。
但是,Map是无序的,无法决定返回的顺序。Map也是引用类型。
package main
import "fmt"
// map 集合,保存数据的一种结构
func main() {
// 创建map变量,数据类型是map[key]value
var map1 map[int]string // 只是声明,但没有初始化,默认为nil,不能使用
if map1 == nil {
fmt.Println("map1==nil") // map1==nil
}
// 更多是使用make方法创建map变量
var map2 = make(map[string]string)
fmt.Println(map1) // map[]
fmt.Println(map2) // map[]
// 声明的时候初始化数据,map[string]int {key:value, key:value, ...}
var map3 = map[string]int{"Go": 100, "Java": 200, "Vue": 300}
fmt.Println(map3) // map[Go:100 Java:200 Vue:300]
fmt.Printf("%T\n", map3) // map[string]int
}
map的使用
创建并初始化map
map[key] = value 将value赋值给对应map的key
判断key是否存在,value, ok := map[key]
删除map中的数据,delete(map, key)
如果key不存在,新增数据 map[key] = value
如果key存在,修改数据 map[key] = value
查看map的大小,len(map)
package main
import "fmt"
func main() {
var map1 map[int]string // 只是声明,还没有初始化,不可使用
// 初始化map1,可赋值使用
map1 = make(map[int]string)
// 一个key对应一个value,如果key重复,就会覆盖
map1[100] = "Go"
map1[100] = "Vue"
map1[200] = "Java"
fmt.Println(map1) // map[100:Vue 200:Java]
// 获取数据 map[key]
fmt.Println(map1[200]) // Java
fmt.Println(map1[1]) // 不存在,默认值 string ""
// 在map中因为没有index下标,但取值需要判断key是否存在
// value = map[key] 隐藏的返回值 ok-idiom 可选参数
value, ok := map1[1]
if ok {
fmt.Println("map key 存在,value:", value)
} else {
fmt.Println("map key 不存在") // map key 不存在
}
// 如果key存在,修改数据
map1[200] = "Spring"
fmt.Println(map1) // map[100:Vue 200:Spring]
// 如果key不存在,新增数据
map1[300] = "Python"
fmt.Println(map1) // map[100:Vue 200:Spring 300:Python]
// map中的数据删除,delete
delete(map1, 300)
fmt.Println(map1) // map[100:Vue 200:Spring]
// map的大小
fmt.Println(len(map1)) // 2
}
map的遍历
map是无序的,可能每次循环打印出来的结果不一样,不能通过index来获取,只能通过key
map的长度不是固定的,是引用类型
len可以用于查询map中数据的数量,但cap无法使用
map的key可以是 布尔类型、整数、浮点数、字符串
package main
import "fmt"
func main() {
var map1 = map[string]int{"Go": 99, "Java": 52, "Vue": 66, "C": 14}
// map的循环遍历,只能通过for range
for key, value := range map1 {
fmt.Println(key, value)
}
}
map结合slice切片使用
需求:
创建map存储用户信息,name、age、gender、addr
将map中的每条数据存入slice中
打印这些数据
package main
import "fmt"
func main() {
user1 := make(map[string]string)
user1["name"] = "张三"
user1["age"] = "20"
user1["gender"] = "男"
user1["addr"] = "安徽"
user2 := make(map[string]string)
user2["name"] = "李四"
user2["age"] = "25"
user2["gender"] = "女"
user2["addr"] = "江苏"
var user3 = map[string]string{"name": "王五", "age": "18", "gender": "男", "addr": "北京"}
// 将map数据存入切片中,以便后续使用
userDatas := make([]map[string]string, 0, 3)
userDatas = append(userDatas, user1)
userDatas = append(userDatas, user2)
userDatas = append(userDatas, user3)
// [map[addr:安徽 age:20 gender:男 name:张三] map[addr:江苏 age:25 gender:女 name:李四] map[addr:北京 age:18 gender:男 name:王五]]
fmt.Println(userDatas)
for _, user := range userDatas {
// 张三 20 男 安徽
// 李四 25 女 江苏
// 王五 18 男 北京
fmt.Println(user["name"], user["age"], user["gender"], user["addr"])
}
}