feat(day02): 添加Go语言基础数据类型教程

新增map和slice详细教程,完善array和struct内容

- 新增map章节,详细介绍Go中map的概念、创建、操作和底层原理
- 新增slice章节,涵盖切片的创建、操作、引用特性和实际应用
- 完善array章节,添加数组作为函数参数、多维数组等内容
- 更新struct章节,修正标题错误并补充结构体定义说明
- 为各章节添加实践作业题目,增强学习效果
```
This commit is contained in:
yumaojun03 2026-01-11 13:56:26 +08:00
parent f1fbed05d2
commit 54415d45ba
7 changed files with 484 additions and 3 deletions

View File

@ -91,3 +91,32 @@ func main() {
```go ```go
``` ```
## 数组作为函数参数
## 多维数组
## 作业
请同学们完成以下数组练习题以巩固对Go语言数组的理解
1. **定义和初始化数组**
定义一个包含5个整数的数组初始化为1到5并打印数组内容。
2. **数组遍历**
使用for循环遍历一个字符串数组打印每个元素的索引和值。
3. **数组拷贝**
创建一个数组,拷贝到另一个数组,修改原数组的第一个元素,观察拷贝后的数组是否改变。
4. **多维数组**
定义一个3x3的整数二维数组初始化为九宫格形式1到9并打印出来。
5. **数组作为函数参数**
编写一个函数,接受一个整数数组作为参数,计算并返回数组中所有元素的和。
请将代码写在 `main.go` 文件中,并运行测试。

236
day02/map/README.md Normal file
View File

@ -0,0 +1,236 @@
# Map
Go中的map是一种无序的键值对集合底层基于哈希表实现。每个键必须是可比较的类型值可以是任意类型。map提供快速的查找、插入和删除操作。
## 核心概念
map的底层实现是哈希表通过键的哈希值快速定位值。键必须是可哈希的类型如int、string、指针等值可以是任意类型。map是引用类型赋值时共享底层数据。
```go
// map的零值是nil不能直接使用
var m map[string]int // m == nil
```
## 创建和初始化
**原理**使用make创建map指定初始容量可以提高性能。也可以使用字面量初始化。
```go
// 使用make创建
m1 := make(map[string]int) // 空map
m2 := make(map[string]int, 10) // 指定初始容量
// 字面量初始化
m3 := map[string]int{
"apple": 5,
"banana": 3,
}
fmt.Println(m3) // map[apple:5 banana:3]
```
## 访问元素
**原理**:通过键访问值,如果键不存在,返回零值。可以使用第二个返回值检查键是否存在。
```go
m := map[string]int{"a": 1, "b": 2}
value := m["a"] // 1
fmt.Println(value)
// 检查键是否存在
if val, exists := m["c"]; exists {
fmt.Println("存在:", val)
} else {
fmt.Println("不存在")
}
```
## 修改和添加元素
**原理**:直接赋值键即可添加或修改。如果键存在则修改,不存在则添加。
```go
m := make(map[string]int)
m["key1"] = 10 // 添加
m["key1"] = 20 // 修改
fmt.Println(m) // map[key1:20]
```
## 删除元素
**原理**使用delete函数删除键值对。如果键不存在delete不报错。
```go
m := map[string]int{"a": 1, "b": 2, "c": 3}
delete(m, "b") // 删除键"b"
fmt.Println(m) // map[a:1 c:3]
delete(m, "d") // 删除不存在的键,无错误
```
## 遍历map
**原理**使用range遍历键值对。遍历顺序是随机的不能依赖顺序。
```go
m := map[string]int{"a": 1, "b": 2, "c": 3}
for key, value := range m {
fmt.Printf("key: %s, value: %d\n", key, value)
}
// 只遍历键
for key := range m {
fmt.Println(key)
}
```
## map是引用类型
**原理**map是引用类型赋值时共享底层数据。修改一个会影响另一个。
```go
m1 := map[string]int{"a": 1}
m2 := m1 // 共享底层数据
m2["b"] = 2
fmt.Println(m1) // map[a:1 b:2] m1也被修改
fmt.Println(m2) // map[a:1 b:2]
```
## map作为函数参数
**原理**map作为参数传递时是引用传递函数内修改会影响调用者。
```go
func addEntry(m map[string]int, key string, value int) {
m[key] = value
}
func main() {
m := make(map[string]int)
addEntry(m, "new", 42)
fmt.Println(m) // map[new:42]
}
```
## 扩展 Map的底层原理
从Go 1.23开始map的底层实现从传统的哈希表改为基于Swiss table的高性能实现大幅提升了性能和内存效率。以下详细讲解其工作原理。
### 1. Swiss Table结构
Swiss table是一种现代的开放寻址哈希表设计具有以下特点
- 每个桶包含一个控制字节数组和数据数组
- 控制字节存储元数据(哈希值的高位、删除标记等)
- 数据与控制字节分离,减少缓存未命中
```
Swiss Table结构示意图
+-------------------+ +-------------------+
| 控制字节 (8字节) | | 数据槽位 (8个) |
| [H1|H2|H3|H4|...]| | [k1,v1] [k2,v2]...|
+-------------------+ +-------------------+
| 桶0: 控制元数据 | --> | 桶0: 键值对数据 |
+-------------------+ +-------------------+
| 桶1: 控制元数据 | --> | 桶1: 键值对数据 |
+-------------------+ +-------------------+
```
### 2. 哈希计算和定位
**改进的哈希计算**
- 使用更快的哈希函数AesHash或类似
- 哈希值分为高位和低位
- 高位用于SIMD比较低位用于定位
```
哈希计算过程:
Key -> hash(key) -> [高7位: SIMD比较] [低位: 位置索引]
```
### 3. 插入和查找过程
**查找优化**
1. 计算哈希值
2. 使用SIMD指令并行比较控制字节
3. 快速定位匹配的槽位
**插入优化**
1. 查找空闲槽位
2. 使用线性探测或二次探测
3. 写入控制字节和数据
```
查找示意图使用SIMD
Hash(Key) = 0xABCD1234
控制字节 = 0xAB (高位)
位置索引 = 0x1234 & 掩码
控制字节数组: [AB|CD|EF|00|...]
↑ SIMD比较找到匹配
数据数组: [k1,v1] [k2,v2] [k3,v3] ...
```
### 4. 扩容机制
**渐进式扩容**
- 当负载因子超过阈值时触发扩容
- 新表大小为2的幂次
- 每次操作迁移少量数据
- 支持就地扩容in-place rehashing
```
扩容过程:
旧表: 控制字节 [A|B|C|D] --> 数据 [k1,v1] [k2,v2] [k3,v3] [k4,v4]
新表: 控制字节 [A|0|B|0|C|0|D|0] --> 数据 [k1,v1] [k2,v2] [k3,v3] [k4,v4]
(插入空字节分隔)
```
### 5. 性能优势
**相比传统哈希表**
- **更快的查找**SIMD并行比较减少分支预测失败
- **更好的缓存利用**:控制字节和数据分离
- **更低的内存开销**:更紧凑的布局
- **抗哈希冲突**:更好的分布减少最坏情况
```
性能对比:
操作 | 传统哈希表 | Swiss Table
查找 | O(1) avg | O(1) faster
插入 | O(1) avg | O(1) faster
内存使用 | ~32字节/对 | ~24字节/对
```
## 总结
map是Go中常用的数据结构适合需要快速查找的场景。记住map是无序的键必须可哈希操作是引用传递的。
## 作业
请同学们完成以下map练习题以巩固对Go语言map的理解
1. **创建和初始化map**
创建一个map键为string值为int初始化包含3个键值对并打印map。
2. **访问元素**
给定map检查某个键是否存在如果存在打印值否则打印"不存在"。
3. **修改和添加**
从空map开始添加5个键值对然后修改其中一个值。
4. **删除元素**
删除map中的一个元素并打印删除前后的map。
5. **遍历map**
遍历map计算所有值的和。
6. **函数参数**
编写一个函数接受map作为参数添加一个新键值对。
请将代码写在 `main.go` 文件中,并运行测试。

205
day02/slice/README.md Normal file
View File

@ -0,0 +1,205 @@
# 切片
![alt text](image.png)
Go中的slice依赖于数组它的底层就是数组所以数组具有的优点, slice都有。 且slice支持可以通过append向slice中追加元素长度不够时会动态扩展通过再次slice切片可以得到得到更小的slice结构可以迭代、遍历等
// runtime/slice.go
type slice struct {
array unsafe.Pointer // 数组指针
len int // 长度
cap int // 容量
}
每一个slice结构都由3部分组成
+ 容量(capacity): 即底层数组的长度表示这个slice目前最多能扩展到这么长
+ 长度(length)表示slice当前的长度即当前容纳的元素个数
+ 数组指针(array): 指向底层数组的指针
比如创建一个长度为3容量为5int类型的切片
```go
s := make([]int, 3, 4)
fmt.Println(a, len(s), cap(s)) // [0 0 0] 3 5
```
![alt text](image-1.png)
## 创建和初始化
**原理**:切片是动态数组,底层基于固定大小的数组,可以根据需要动态扩容。创建时可以指定长度和容量。
```go
// 使用 make 创建切片make([]类型, 长度, 容量)
s1 := make([]int, 3, 5) // 长度3容量5
fmt.Println(s1, len(s1), cap(s1)) // [0 0 0] 3 5
// 直接初始化
s2 := []int{1, 2, 3} // 长度和容量都是3
fmt.Println(s2, len(s2), cap(s2)) // [1 2 3] 3 3
// 从数组创建切片
arr := [5]int{1, 2, 3, 4, 5}
s3 := arr[1:4] // 从索引1到3不包括4
fmt.Println(s3, len(s3), cap(s3)) // [2 3 4] 3 4
```
## 切片访问
**原理**切片通过索引访问元素索引从0开始。与数组类似但更灵活。
```go
s := []int{10, 20, 30, 40, 50}
// 通过索引访问元素
fmt.Println(s[0]) // 10
fmt.Println(s[2]) // 30
// 修改元素
s[1] = 25
fmt.Println(s) // [10 25 30 40 50]
```
## nil和空切片
**原理**nil切片表示未初始化的切片长度和容量都为0。空切片是已初始化的切片但不包含元素。两者在行为上略有不同。
```go
// nil 切片:未初始化的切片
var s1 []int
fmt.Println(s1 == nil) // true
fmt.Println(len(s1), cap(s1)) // 0 0
// 空切片:已初始化的空切片
s2 := []int{}
s3 := make([]int, 0)
fmt.Println(len(s2), cap(s2)) // 0 0
fmt.Println(len(s3), cap(s3)) // 0 0
```
## 往切片中添加元素
**原理**使用append函数添加元素。如果容量不足会自动扩容底层数组通常容量翻倍增长。
```go
s := []int{1, 2}
fmt.Println(s, len(s), cap(s)) // [1 2] 2 2
// 使用 append 添加元素
s = append(s, 3)
fmt.Println(s, len(s), cap(s)) // [1 2 3] 3 4 (容量自动扩容)
// 添加多个元素
s = append(s, 4, 5)
fmt.Println(s) // [1 2 3 4 5]
```
## 通过切片创建新的切片
**原理**切片操作创建新的切片视图共享底层数组。新切片的长度是high-low容量是原容量减去low。
```go
s := []int{0, 1, 2, 3, 4, 5}
// 创建子切片s[low:high] (不包括high)
sub1 := s[1:4] // [1, 2, 3]
fmt.Println(sub1)
// 省略索引
sub2 := s[:3] // [0, 1, 2]
sub3 := s[2:] // [2, 3, 4, 5]
sub4 := s[:] // [0, 1, 2, 3, 4, 5] 复制整个切片
```
## 遍历切片
**原理**可以使用传统for循环或range关键字遍历。range返回索引和值更简洁。
```go
s := []string{"apple", "banana", "cherry"}
// 方法1使用索引
for i := 0; i < len(s); i++ {
fmt.Println(i, s[i])
}
// 方法2使用 range推荐
for index, value := range s {
fmt.Println(index, value)
}
```
## slice是引用类型
**原理**:切片是引用类型,赋值时复制切片头,但共享底层数组。修改一个会影响另一个。
```go
s1 := []int{1, 2, 3}
s2 := s1 // s2 指向同一个底层数组
s2[0] = 99
fmt.Println(s1) // [99 2 3] s1 也被修改了
fmt.Println(s2) // [99 2 3]
```
## 切片拷贝
**原理**使用copy函数进行深拷贝创建独立的数据副本。修改拷贝后的切片不会影响原切片。
```go
s1 := []int{1, 2, 3}
s2 := make([]int, len(s1))
copy(s2, s1) // 拷贝元素到 s2
s2[0] = 99
fmt.Println(s1) // [1 2 3] s1 不变
fmt.Println(s2) // [99 2 3]
```
## 切片作为函数参数
**原理**:切片作为参数传递时,是引用传递,函数内修改会影响调用者。高效但需注意副作用。
```go
func sum(nums []int) int {
total := 0
for _, num := range nums {
total += num
}
return total
}
func main() {
s := []int{1, 2, 3, 4, 5}
result := sum(s)
fmt.Println(result) // 15
}
```
## 总结
切片是 Golang 中比较有特色的一种数据类型,既为我们操作集合类型的数据提供了便利的方式,是又能够高效的在函数间进行传递,因此在代码中切片类型被使用的相当广泛
## 作业
请同学们完成以下切片练习题以巩固对Go语言切片的理解
1. **创建和初始化切片**
创建一个字符串切片,包含"Go", "Python", "Java",并打印长度和容量。
2. **切片操作**
给定切片[]int{1,2,3,4,5}创建子切片包含第2到第4个元素不包括第4个并打印结果。
3. **添加元素**
从空切片开始使用append添加5个整数观察长度和容量的变化。
4. **遍历切片**
遍历一个整数切片,计算所有元素的和。
5. **切片拷贝**
创建一个切片,拷贝到另一个切片,修改原切片,验证拷贝是否独立。
6. **函数参数**
编写一个函数,接受字符串切片作为参数,将所有字符串转换为大写并返回新切片。
请将代码写在 `main.go` 文件中,并运行测试。

BIN
day02/slice/image-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
day02/slice/image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -1,7 +1,18 @@
# 结构体 # 结构体
![alt text](image.png)
## 子针数组(重要: 95%) 我们前面介绍的数组 只能保存同一种类型的数据, 当我们需要记录多种不同类型的数据时,我们该怎么办?
结构体就是用于解决这个问题的, 结构体是由一系列具有相同类型或不同类型的数据构成的数据集合, 方便容量我们的任意类型的数据
## 结构体的定义
## 指针数组(重要: 95%)
指针数组: 创建一个数组,里面存放指针,指针指向数组的某一个元素 指针数组: 创建一个数组,里面存放指针,指针指向数组的某一个元素
@ -16,5 +27,5 @@ type Person struct {}
``` ```
## 子针数组的copy ## 指针数组的拷贝

BIN
day02/struct/image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB