指针
两个符号
指针:数据在内存中的地址。
指针变量:存储的数据是指针。
| 符号 | 名称 | 作用 |
|---|---|---|
| & | 取址符 | 返回变量所在的地址 |
| * | 取值符 | 返回地址对应的值 |
package main
import "fmt"
func main() {
var i = 10
fmt.Println("i 的地址是:", &i) // 0xc00000a0e8
// p 就是指针变量
var p *int = &i
fmt.Println("p 的值:", p) // 0xc00000a0e8
fmt.Println("p 的地址对应的值", *p) // 10
}
格式化打印
%p 地址的格式化打印
var x = 10
fmt.Printf("%p\n", &x) // 0xc000094098
// x 的值变了
x = 100
fmt.Printf("%p\n", &x) // 0xc000094098
fmt.Println(*&x) // 100
指针的题目
var x = 10
// 这儿发生了值拷贝,x 和 y 互不影响
var y = x
// 地址拷贝,x 和 z 相互影响
// z 是一个指针变量
var z = &x
x = 20
fmt.Println(y) // 10
fmt.Println(*z) // 20
// 可以通过 *指针变量,来修改指针变量对应的值
*z = 30
fmt.Println(x) // 30
var x = 10
// 地址拷贝,x 和 y 相互影响
// y 是一个指针变量
var y = &x
// z => 10,z 是一个普通值
var z = *y
x = 20
fmt.Println(x) // 20
fmt.Println(*y) // 20
fmt.Println(z) // 10
var a = 100
// b 是一个指针变量,a 和 b 相互影响
var b = &a
// c 是一个指针变量,c 和 b 相互影响,把 b 的地址(注意是 b 的地址,而不是 b 的内容)给了 c
var c = &b
**c = 200
fmt.Println(a) // 200
关于 new 符号
new 的返回值是一个指针,用来分配内存,make 用来初始化 slice、map 和 channel。
基本数据类型声明之后有一个默认零值,但是指针没有,例如:
var p *int
// 默认是 nil,是不能直接赋值的
fmt.Println(p) // <nil>
// fmt.Println(*p) // 报错,说明并没有开始初始空间
*p = 10 // 报错
var p *int
// 如何解决?
// 通过 new 函数返回一个指向新分配的 int 类型为初始 0 值的指针
p = new(int)
fmt.Println(p) // 0xc000094098
*p = 10
fmt.Println(*p) // 10
数组
特点:数组需要是一组相同类型的数据的有序集合;定义的时候需要定义大小;数组一旦定义了大小不可以改变。
声明数组
var names [5]string
fmt.Println(names, reflect.TypeOf(names)) // [ ] [5]string
var ages [5]int
fmt.Println(ages, reflect.TypeOf(ages)) // [0 0 0 0 0] [5]int
// 注意下面的 x 和 y 的数据类型是不同的
var x [3]int
var y [5]int
初始化数组
先声明再赋值
package main
import "fmt"
func main() {
var arr1 [5]int
arr1[0] = 100
arr1[1] = 200
arr1[2] = 300
arr1[3] = 400
arr1[4] = 500
// 1. 打印数组
fmt.Println(arr1)
// 2. 打印数组的第某一项
fmt.Println(arr1[1]) // 200
// 3. 获取数组长度
fmt.Println("数组的长度:", len(arr1)) // 5
// 4. 获取数组容量
fmt.Println("数组的容量:", cap(arr1)) // 5
// 5. 修改数组中的元素
arr1[1] = 10
fmt.Println(arr1[1]) // 10
}
声明和赋值一起
package main
import "fmt"
func main() {
// 1. 在定义数组的时候就直接初始化
var arr1 = [5]int{1, 2, 3, 4, 5}
fmt.Println(arr1)
// 2. 使用 := 快速初始化
arr2 := [5]int{1, 2, 3, 4, 5}
fmt.Println(arr2)
}
自动统计长度
// 3. Go 的编译器会自动根据数组的长度来给 ... 赋值,自动推导长度
var arr3 = [...]int{1, 2, 3, 4, 5, 5, 6, 7, 8, 8, 8}
fmt.Println(len(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]
通过切片来取数组中的值
var names = [...]string{"张三", "李四", "王五", "赵六", "孙七"}
// 切片取值
fmt.Println(names[0:4]) // [张三 李四 王五 赵六]
fmt.Println(names[0:]) // [张三 李四 王五 赵六 孙七]
fmt.Println(names[:3]) // [张三 李四 王五]
遍历数组的两种方式
package main
import "fmt"
func main() {
var arr1 = [5]int{1, 2, 3, 4, 5}
// 1. 遍历数组
for i := 0; i < len(arr1); i++ {
fmt.Println(arr1[i])
}
// 2. for range
// Goland 快捷方式 数组.for,推荐
for _, value := range arr1 {
fmt.Println(value)
}
}
两种方式的注意点?如何理解 for range 循环时候的 value 是值的副本。
var arr1 = [5]int{1, 2, 3, 4, 5}
// 1. 遍历数组
for i := 0; i < len(arr1); i++ {
arr1[i] = 8
}
fmt.Println(arr1) // [8 8 8 8 8]
// 2. for range
// Goland 快捷方式 数组.for,推荐
for _, value := range arr1 {
value = 7
fmt.Println(value)
}
fmt.Println(arr1) // [8 8 8 8 8]
数组是值类型特点
体现:赋值和函数传参。
package main
import "fmt"
func main() {
// 数组类型的样子 [size]type
arr1 := [4]int{1, 2, 3, 4}
// 数组的值传递和 int 等基本类型一致
arr2 := arr1
arr2[0] = 12
// 修改 arr2 后发现 arr1 并没有变化
fmt.Println(arr1) // [1 2 3 4]
}
数组的冒泡排序
package main
import "fmt"
// 冒泡:两两比较,大的往后移或者前移,每次可以筛选出一个最大或者最小的数
func main() {
arr := [...]int{1, 3, 4, 2, 0}
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() {
// 1. 如何定义二维数组?
// 长度是 3,里面数组的长度是 4 的 int 型
arr := [3][4]int{
{0, 1, 2, 3},
{4, 5, 6, 7},
{8, 9, 10, 11},
}
// 2. 如何遍历二维数组
// 2.1. 方式1
for i := 0; i < len(arr); i++ {
for j := 0; j < len(arr[i]); j++ {
fmt.Println(arr[i][j])
}
}
// 2.2 for range
for _, v := range arr {
for i := range v {
fmt.Println(v[i])
}
}
}
切片
基本概念
// 1. 切片有点像动态数组,所以,定义切片的时候不需要指定长度
// 2. 它是对现有数组的引用
// 3. 切片本身不存储任何数据,都是底层的数组来存储的,所以修改了切片也就是修改了这个数组中的数据
// 从概念上面来说 slice 像一个结构体,这个结构体包含了三个元素:
// 1. 指针:「指向数组中」 slice 指定的开始位置
// 2. 长度:即 slice 的长度
// 3. 最大长度:也就是 slice 开始位置到数组的最后位置的长度
声明切片的两种方式
从数组或切片上取得数据;直接声明切片。
package main
import "fmt"
func main() {
// 1. 定长的是数组
arr := [4]int{1, 2, 3, 4}
fmt.Println(arr)
// 2. 不定长的是切片
var s1 []int // 变长,长度是可变的
fmt.Println(s1)
// 3. 如何判断切片为空?
// 初始的切片中,默认是 nil
if s1 == nil {
fmt.Println("切片是空的")
}
// 这样是会报错的,因为上面只是声明了切片,并没有初始化,可以通过后面的 make 进行初始化
// s1[0] = 3
// 4. 定义切换的时候可以指定初始数据
s2 := []int{1, 2, 3, 4}
fmt.Println(s2)
fmt.Printf("%T, %T\n", arr, s2) // [4]int, []int
// 5. 获取切片中的某个数据
fmt.Println(s2[1])
}
思考数组和切片的关系。
var arr = [5]int{1, 2, 3, 4, 5}
var s1 = arr[1:4]
fmt.Println(s1, reflect.TypeOf(s1)) // [2 3 4] []int
var s2 = arr[2:5]
fmt.Println(s2, reflect.TypeOf(s2)) // [3 4 5] []int
var s3 = s2[0:2]
s3[0] = 100
fmt.Println(s1, s2, s3) // [2 100 4] [100 4 5] [100 4]
通过 make 初始化切片
思考:容量为 10,长度为 5,我能存放 6 个数据吗?
package main
import "fmt"
func main() {
// 创建一个切片,长度,容量
// make([]Type,length,capacity)
s1 := make([]int, 5, 10)
fmt.Println(len(s1), cap(s1)) // 5 10
// 思考:容量为 10,长度为 5,我能存放 6 个数据吗?
s1[0] = 10
// 直接赋值的方式不行
// s1[5] = 200
// 通过 append 操作会自动扩容,这样是可以的
// 切片的底层还是数组
fmt.Println(s1)
}
append 添加会自动扩容
package main
import "fmt"
func main() {
s1 := make([]int, 0, 5)
s1 = append(s1, 1, 2)
// 问题:容量只有 5 个,那能放超过 5 个的吗?
// 可以,通过 append 操作切片是会自动扩容的
s1 = append(s1, 3, 4, 5, 6, 7)
fmt.Println(s1) // [1 2 3 4 5 6 7]
}
如何合并两个切片
package main
import "fmt"
func main() {
s1 := []int{1, 2, 3}
s2 := []int{4, 5, 6}
s1 = append(s1, s2...)
fmt.Println(s1) // [1 2 3 4 5 6]
}
遍历切片的两种方式
package main
import "fmt"
func main() {
s1 := []int{1, 2, 3, 4}
// 方式 1
for i := 0; i < len(s1); i++ {
fmt.Println(s1[i])
}
// 方式 2
for i, v := range s1 {
fmt.Println(s1[i], v)
}
}
append 操作切片扩容分析(重)
- 向切片中添加数据的时候,如果没有超过容量,直接添加,如果超过了这个容量,就会自动扩容,cap 成倍的增加,拷贝。
- 切片一旦
扩容(超出了原来的容量),就是重新指向一个新的底层数组。
package main
import "fmt"
func main() {
s1 := []int{1, 2, 3}
fmt.Printf("len:%d,cap:%d\n", len(s1), cap(s1)) // len:3,cap:3
fmt.Printf("%p\n", s1) // 0xc00012a000
s1 = append(s1, 4, 5)
fmt.Printf("len:%d,cap:%d\n", len(s1), cap(s1)) // len:5,cap:6
// 扩容后,s1 重新指向了新地址
fmt.Printf("%p\n", s1) // 0xc000132000
s1 = append(s1, 6, 7, 8)
fmt.Printf("len:%d,cap:%d\n", len(s1), cap(s1)) // len:8,cap:12
// 扩容后,s1 重新指向了新地址
fmt.Printf("%p\n", s1) // 0xc0001220c0
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) // 0xc0001220c0
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) // 0xc000134000
}
通过数组来创建切片
package main
import "fmt"
func main() {
// 数组 [0,10)
// 1. 通过数组来创建切片,先定义一个数组
arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
fmt.Printf("原数组:%p\n", &arr) // 原数组 0xc0000160f0
// 2. 数组截取的结果其实一个切片
s1 := arr[:5] // [1 2 3 4 5]
s2 := arr[3:8] // [4 5 6 7 8]
s3 := arr[5:] // [6 7 8 9 10]
s4 := arr[:] // [1 2 3 4 5 6 7 8 9 10]
// 3. 查看容量和长度
fmt.Printf("s1 len:%d, cap:%d\n", len(s1), cap(s1)) // s1 len:5, cap:10
fmt.Printf("s2 len:%d, cap:%d\n", len(s2), cap(s2)) // s2 len:5, cap:7,从3开始截取的,所以容量是 7
fmt.Printf("s3 len:%d, cap:%d\n", len(s3), cap(s3)) // s3 len:5, cap:5
fmt.Printf("s4 len:%d, cap:%d\n", len(s4), cap(s4)) // s4 len:10, cap:10
// 3. 查看切片的内存地址
fmt.Printf("%p\n", s1) // 指向了原数组,0xc0000160f0
fmt.Printf("%p\n", s2) // 数组是有序的,由于截断了,指向了新数组,0xc000016118
fmt.Printf("%p\n", s3) // 数组是有序的,由于截断了,指向了新数组,0xc000016118
fmt.Printf("%p\n", s4) // 指向了原数组,0xc0000160f0
}
切片的修改和扩容
package main
import "fmt"
func main() {
// 数组 [0,10)
// 1. 通过数组来创建切片,先定义一个数组
arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
fmt.Printf("原数组:%p\n", &arr) // 原数组 0xc0000160f0
// 2. 通过截取数组生成切片
s1 := arr[:5] // [1 2 3 4 5]
s2 := arr[3:8] // [4 5 6 7 8]
// 3. 修改数组的内容,切片也会随之发生了变化,因为切片并不保存数据,是对原数组的引用
arr[2] = 100
fmt.Println(s1) // [1 2 100 4 5]
// 4. 修改切片的内容,数组也随之发生了变化
s2[2] = 80
fmt.Println(arr) // [1 2 100 4 5 80 7 8 9 10]
// 5. 切片扩容,注意这儿添加了 6 个,添加完毕后超过了数组原来的容量
// 如果在切片容量内添加,会导致原来的数组数据发生修改
// 如果添加后超过了切片的容量,原数组的数据不会发生变化,但其实会导致底层数组拷贝产生一个新的切片
s1 = append(s1, 11, 12, 13, 14, 15, 16)
fmt.Println("切片扩容之后的新数组:", arr) // [1 2 100 4 5 80 7 8 9 10]
// 7. 扩容后的新切片,指向,拷贝原数组的数据生成的一个新数组,和原来的数组不再是同一个地址了
fmt.Printf("切片扩容之后的 s1 地址:%p\n", s1) // 0xc000132000
}
切片是引用类型的特点
package main
import "fmt"
func main() {
arr := [2]int{1, 2}
s1 := []int{1, 2, 3, 4}
s2 := s1
s1[0] = 100
// 虽然修改的是 s1,发现 s2 也发生了变化
fmt.Println(s2) // [100 2 3 4]
// s1 和 s2 指向同一个地址
// 由于切片是引用类型,可以直接输出获取地址,数组是值类型,要通过 & 获取地址
fmt.Printf("%p, %p, %p\n", s1, s2, &arr)
}
深拷贝和浅拷贝
-
值类型的数据,默认都是深拷贝,例如 array、int、float、string、bool、struct 等。
-
引用类型的数据,默认都是浅拷贝,例如 slice、map、chan,拷贝的是地址,会导致多个变量指向同一块内存。
如何实现切片的深拷贝?
package main
import "fmt"
// 方法 1,通过 for 循环
func main() {
s1 := []int{1, 2, 3, 4}
s2 := make([]int, 0)
for i := 0; i < len(s1); i++ {
s2 = append(s2, s1[i])
}
fmt.Printf("%p, %p\n", s1, s2)
//s2 = append(s2, s1...)
//fmt.Printf("%p, %p\n", s1, s2)
}
package main
import "fmt"
// 方法 2,通过 copy 方法
func main() {
s1 := []int{1, 2, 3, 4}
s2 := make([]int, len(s1)) // 关键:目标切片长度必须 >= 源切片长度
copy(s2, s1) // 将 s1 的内容拷贝到 s2
fmt.Println(s2) // 输出 [1 2 3 4]
}
映射
基本概念
Map 是一种无序的键值对的集合,所以我们可以像迭代数组和切片那样迭代它,Map 也是引用类型。
package main
import "fmt"
func main() {
// 1. 基本操作,map[key]value
var map1 map[int]string // 只是声明了但是没有初始化,默认是 nil
if map1 == nil {
fmt.Println("map 没有初始化默认是 nil")
}
// 2. 需要进一步使用 make 方法创建
var map2 = make(map[string]string)
fmt.Println(map1) // map[],不能赋值,例如 map1[1] = "value",这时候会报错 panic
fmt.Println(map2) // map[]
// 3. 声明和初始化可以一起
var map3 = map[string]int{"Go": 100, "Java": 10, "C": 60}
fmt.Println(map3)
fmt.Printf("%T\n", map3) // map[string][int]
}
操作 Map
package main
import "fmt"
func main() {
// 1. 创建 map
var map1 map[int]string // nil,不能使用的,只是声明了,还没有初始化
// 2. 进一步通过 make 初始化
map1 = make(map[int]string)
map1[100] = "JavaScript"
map1[200] = "Java"
fmt.Println(map1)
fmt.Println(map1[200])
fmt.Println(map1[1]) // 不存在,默认值 string ""
// 3. 取值的时候可以判断
value, ok := map1[1]
if ok {
fmt.Println("map key 存在,value 是:", value)
} else {
fmt.Println("map key 不存在!")
}
// 4. 下面这句话表示,如果存在 key 就是修改数据,否则就是创建新的 key 和数据
map1[100] = "xxx"
// 5. 删除 map 中的数据
delete(map1, 100)
// 6. map 的大小
fmt.Println(len(map1))
}
遍历 Map
package main
import "fmt"
/*
1. map 是无序的,每次打印出来的值可能都不一样,它不能通过 index 获取,只能通过 key 来获取
2. map 的长度是不固定的,是引用类型
3. len 可以用于 map 查看 map 中数据的数量,但是 cap 无法使用
4. map 的 key 可以是布尔类型、整数、浮点数、字符串
*/
func main() {
var map1 = map[string]int{"Go": 50, "Java": 89, "C": 87, "Python": 90}
for k, v := range map1 {
fmt.Println(k, v)
}
}
将 Map 放到切片中
package main
import "fmt"
func main() {
// 1. 创建 3 个人对象
user1 := make(map[string]string)
user1["name"] = "ifer"
user1["age"] = "19"
user2 := make(map[string]string)
user2["name"] = "elser"
user2["age"] = "32"
user3 := map[string]string{"name": "xxx", "age": "88"}
// 2. 创建有 Map 组成的切片
// 默认长度=0,容量=3,没有问题!
// 初始不能指定长度等于 3,否则会添加三个空数据
userDatas := make([]map[string]string, 0, 3)
// 3. 扩容切片
userDatas = append(userDatas, user1)
userDatas = append(userDatas, user2)
userDatas = append(userDatas, user3)
// 4. 遍历切片,输出一个一个的 Map
for _, user := range userDatas {
fmt.Println(user)
}
}