go语言学习路线:https://www.topgoer.com/
数组
什么是数组
- 数组(Array)是一段有固定长度的相同类型的数据的集合
- 数组的长度是固定的,不能进行修改
- 数组中的值的获取是通过数组的下标来获取,下标从0开始
- 数组是值类型,赋值和传参实际上是复制了一个新的数组,新数组值的改变不会影响到原数组的变化
数组的定义及初始化
定义格式:var 数组名 [数组长度]数组类型
// 定义一个长度为5,数据类型为int的数组
var arr [5]int
数组的初始化:
package main
import "fmt"
func main() {
// 从下标0开始赋值,未赋值的元素初始化为该类型的默认值
var arr1 [4]int = [4]int{1, 2, 3}
// 没有初始化长度,赋值的元素个数就是数组的长度
var arr2 = [...]int{1, 2, 3, 4, 5}
// 如果值是key:value的格式,key为数组的下标,根据下标指定赋值
var arr3 [4]int = [4]int{1: 3, 2: 8}
fmt.Println(arr1)
fmt.Println(arr2)
fmt.Println(arr3)
}
结果:
[1 2 3 0]
[1 2 3 4 5]
[0 3 8 0]
遍历数组元素
- 直接根据数组的下标获取元素,数组下标不能超过数组的长度-1,否则会报panic数组越界
package main
import "fmt"
func main() {
var arr [4]int = [4]int{1, 2, 3, 4}
fmt.Println("数组的第二个元素:",arr[1])
fmt.Println("数组的第三个元素:",arr[2])
}
结果:
数组的第二个元素: 2
数组的第三个元素: 3
- 根据for循环进行遍历:
package main
import "fmt"
func main() {
var arr [4]int = [4]int{1, 2, 3, 4}
for i := 0; i < len(arr); i++ {
fmt.Println("数组的第", i+1, "个元素:", arr[i])
}
}
结果:
数组的第 1 个元素: 1
数组的第 2 个元素: 2
数组的第 3 个元素: 3
数组的第 4 个元素: 4
- 根据for range循环遍历数组
package main
import "fmt"
func main() {
var arr [4]int = [4]int{1, 2, 3, 4}
for index, value := range arr {
fmt.Printf("数组的第%d个元素:%d", index+1, value)
fmt.Println()
}
}
结果:
数组的第1个元素:1
数组的第2个元素:2
数组的第3个元素:3
数组的第4个元素:4
数组是值类型
- 数组是值类型:赋值后的对象修改值后不影响原来的对象,作为函数参数传递时,数组修改不会影响原数组的变化
package main
import "fmt"
func main() {
arr1 := [4]int{1, 2, 3, 4}
arr2 := arr1
fmt.Println(arr1)
fmt.Println(arr2)
// 修改arr2观察arr1是否会变化
arr2[0] = 100
fmt.Println(arr1)
// 数组是值传递,拷贝一个新的内存空间
fmt.Println(arr2)
// 打印两个数组的地址:
fmt.Printf("数组arr1的地址:%p\n", &arr1)
fmt.Printf("数组arr2的地址:%p\n", &arr2)
}
结果:
[1 2 3 4]
[1 2 3 4]
[1 2 3 4]
[100 2 3 4]
数组arr1的地址:0xc0000161a0
数组arr2的地址:0xc0000161c0
可以看到:
- 改变数组arr2的值,arr1数组中的值不会发生任何改变
- arr1和arr2的地址不同
如果函数中数组作为参数传递:
package main
import "fmt"
func main() {
arr := [4]int{1, 2, 3, 4}
fmt.Println("函数执行前arr的值:", arr)
printArr(arr)
fmt.Println("函数执行后arr的值:", arr)
fmt.Printf("数组arr的地址:%p\n", &arr)
}
func printArr(arr [4]int) {
arr[0] = 100
fmt.Println("数组的值:", arr)
fmt.Printf("数组的地址:%p\n", &arr)
}
结果:
函数执行前arr的值: [1 2 3 4]
数组的值: [100 2 3 4]
数组的地址:0xc000016200
函数执行后arr的值: [1 2 3 4]
数组arr的地址:0xc0000161a0
多维数组
以二维数组举例:
package main
import "fmt"
func main() {
// 二维数组的定义
var arr [3][4]int = [3][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}
// 取值
// 1.从下标获取值
fmt.Println(arr[0][1])
fmt.Println(arr[1][2])
// 2.for循环获取
for i := 0; i < len(arr); i++ {
for j := 0; j < len(arr[i]); j++ {
fmt.Print(arr[i][j], " ")
}
fmt.Println()
}
// 3.for range循环获取
for _, v1 := range arr {
for _, v2 := range v1 {
fmt.Print(v2, " ")
}
fmt.Println()
}
}
结果:
2
7
1 2 3 4
5 6 7 8
9 10 11 12
1 2 3 4
5 6 7 8
9 10 11 12
切片
切片本质上是对数组的引用
- 切片的长度不固定,在切片中追加数据可能会引起切片的扩容
- 切片本身没有任何数据,他们只是对现有数组的引用
- 切片与数组相比,不需要设定长度,比较灵活
- 切片的遍历方式与数组相同
切片定义
格式:var 切片名 []数据类型
package main
import "fmt"
func main() {
var slice []int = []int{1, 2, 3, 4}
fmt.Println(slice)
fmt.Printf("数组的长度:%d\n", len(slice))
fmt.Printf("数组的容量:%d\n", cap(slice))
}
结果:
[1 2 3 4]
数组的长度:4
数组的容量:4
通过数组定义切片
格式:var 切片名 []数据类型 = 数组名[start:end]
-
意思把数组的第start+1个元素开始,包含start+1个元素,到第end元素为止,不包含end元素赋值给切片
-
start代表数组的起始长度,不写为从0开始
-
end代表数组的其实长度,不写为最后一个元素
package main
import "fmt"
func main() {
var arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
var slice0 []int = arr[2:6]
var slice1 []int = arr[2:]
var slice2 []int = arr[:6]
var slice3 []int = arr[:]
fmt.Println(slice0)
fmt.Println(slice1)
fmt.Println(slice2)
fmt.Println(slice3)
}
结果:
[2 3 4 5]
[2 3 4 5 6 7 8 9]
[0 1 2 3 4 5]
[0 1 2 3 4 5 6 7 8 9]
通过make创建切片
格式:切片名 := make([]数据类型,长度,容量)
- 容量可以不加,不加与长度相同
package main
import "fmt"
func main() {
// 创建一个长度为5,容量为10的切片
s1 := make([]int, 5, 10)
fmt.Println(s1)
fmt.Println(len(s1), cap(s1))
// 思考:容量为10,长度为5,我能存放6个数据吗?
s1[0] = 10
// 不能存放,报错index out of range [7] with length 5
s1[7] = 200
// 切片的底层还是数组
// 直接去赋值是不行的,不用用惯性思维思考
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...)
}
扩容的内存分析
- 每个切片引用了一个底层的数组
- 切片本身不存储任何数据,都是底层的数组来存储的,所以修改了切片也就是修改了这个数组中的数据
- 向切片中添加数据的时候,如果没有超过容量,直接添加,如果超过了这个容量,就会自动扩容,成倍的增加, copy
- 切片一旦扩容,就是重新指向一个新的底层数组。
package main
import "fmt"
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
}
切片是引用类型
- 与数组不同的是,切片是引用类型,赋值和传递时是把切片的地址拷贝给了另一个切片,切片的改变会影响到原切片的数据
package main
import "fmt"
func main() {
// 值类型,传递:copy
arr1 := [4]int{1, 2, 3, 4}
arr2 := arr1
fmt.Println(arr1, arr2)
arr1[0] = 100
fmt.Println(arr1, arr2)
// 切片:引用类型
s1 := []int{1, 2, 3, 4}
s2 := s1
fmt.Println(s1, s2)
s1[0] = 100
fmt.Println(s1, s2)
// 通过内存分析发现,s1,s2指向了同一个地址。
fmt.Printf("%p\n", s1)
fmt.Printf("%p\n", s2)
}
深拷贝、浅拷贝
深拷贝:拷贝是数据的本身
- 值类型的数据,默认都是深拷贝,array、int、float、string、bool、struct…
浅拷贝:拷贝是数据的地址,会导致多个变量指向同一块内存。
- 引用类型的数据: slice、map
- 因为切片是引用类的数据,直接拷贝的是这个地址
切片怎么实现深拷贝 copy
package main
import "fmt"
// 切片实现深拷贝
func main() {
// 将原来切片中的数据拷贝到新切片中
s1 := []int{1, 2, 3, 4}
s2 := make([]int, 0) // len:0 cap:0
for i := 0; i < len(s1); i++ {
s2 = append(s2, s1[i])
}
fmt.Println(s1)
fmt.Println(s2)
s1[0] = 100
fmt.Println(s1)
fmt.Println(s2)
// copy
s3 := []int{5, 6, 7}
fmt.Println(s2)
fmt.Println(s3)
// 将s3中的元素拷贝到s2中
//copy(s2, s3)
// 将s2中的元素拷贝到s3中
copy(s3, s2)
fmt.Println(s2)
fmt.Println(s3)
}
map
Map 是一种无序的键值对的集合。
- 无序 :map[key]
- 键值对:key - value /key - value key - value key - value
Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。
Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。
不过,Map 是无序的,我们无法决定它的返回顺序。
Map也是引用类型
map的定义
map[Key的数据类型]Value的数据类型
package main
import "fmt"
// map 集合,保存数据的一种结构
func main() {
// 创建一个map,也是一个变量,数据类型是 map
// map[key]value
var map1 map[int]string // 只是声明了但是没有初始化,不能使用 nil
if map1 == nil {
fmt.Println("map1==nil")
}
// 更多的时候使用的是make方法创建
var map2 = make(map[string]string) // 创建了map
fmt.Println(map1)
fmt.Println(map2)
// 在创建的时候,添加一些基础数据
// map[string]int nil
// map[string]int {key:value,key:value,......}
var map3 = map[string]int{"Go": 100, "Java": 10, "C": 60}
fmt.Println(map3)
// 关于map的类型,就如定义的一般 map[string]int
// 类型主要是传参要确定
fmt.Printf("%T\n", map3)
}
结果:
map1==nil
map[]
map[]
map[C:60 Go:100 Java:10]
map[string]int
map的遍历
- map是无序的,每次打印出来的map可能都不一样,它不能通过index获取,只能通过key来获取
- map的长度是不固定的,是引用类型的
- len可以用于map查看map中数据的数量,但是cap无法使用
- map的key 可以是 布尔类型,整数,浮点数,字符串
package main
import "fmt"
func main() {
var map1 = map[string]int{"Go": 100, "Java": 99, "C": 80, "Python": 60}
// 循环遍历,只能通过for range
// 返回 key 和 value
for k, v := range map1 {
fmt.Println(k, v)
}
}
结果:
C 80
Python 60
Go 100
Java 99
每次的结果可能都不一样
map结合slice使用
package main
import "fmt"
func main() {
user1 := make(map[string]string)
user1["name"] = "zhangsan"
user1["age"] = "18"
user1["sex"] = "男"
user2 := make(map[string]string)
user2["name"] = "lisi"
user2["age"] = "30"
user2["sex"] = "男"
user3 := map[string]string{"name": "小蓝", "age": "18", "sex": "男"}
fmt.Println(user3)
// 3个数据有了,存放到切片中,供我们使用
userDatas := make([]map[string]string, 0, 3)
userDatas = append(userDatas, user1)
userDatas = append(userDatas, user2)
userDatas = append(userDatas, user3)
fmt.Println(userDatas)
// 0 map[string]string
for _, user := range userDatas {
//fmt.Println(i)
fmt.Println(user["name"], user["age"], user["sex"])
}
}
结果:
map[age:18 name:小蓝 sex:男]
[map[age:18 name:zhangsan sex:男] map[age:30 name:lisi sex:男] map[age:18 name:小蓝 sex:男]]
zhangsan 18 男
lisi 30 男
小蓝 18 男