容器类型
容器类型
数组
声明定义、初始化、赋值、使用
与其他语言不同:吐槽,Go这个 [] 前置的语法丑得想吐,而且糖多得想吐
package main
import "fmt"
func main() {
    // 方式1: 声明定义,并赋值
    var scores [5]int	// 自动初始化对应类型的零值,这里全0
    scores[0] = 95
    scores[1] = 91
    scores[2] = 39
    scores[3] = 60
    scores[4] = 21
    
    // 方式2: 上面也可以改写成初始化的形式
    var arr1 [3]int = [3]int {3, 6, 9}		// 方式2.1
    var arr2 = [3]int {3, 6, 9}				// 方式2.2
    var arr3 = [...]int {3, 6, 9}			// 方式2.3
    var arr4 = [...]int {2:66, 0:33, 1:99}	// 方式2.4
    
    // 求和
    sum := 0
    for i := 0; i < len(scores); i++ {
        sum += scores[i]
    }
    
    // 平均数
    avg := sum / len(scores)
}方法
遍历
package main
import "fmt"
func main() {
    var scores [5]int	// 自动初始化对应类型的零值,这里全0
    
    // 存入成绩
    for i := 0; i < len(scores); i++ {
        fmt.Printf("录入第%d个学生的成绩", i+1)
        fmt.Scanln(&scores[i])
    }
    
    // 打印 - for 遍历版
    for i := 0; i < len(scores); i++ {
        fmt.Printf("第%d个学生的成绩: %d\n", i+1, scores[i])
    }
    
    // 打印 - for range 遍历版
    for key, value := range scores {	// key和value的名字可以换,如果不需要key可以替换成`_`
        fmt.Printf("第%d个学生的成绩: %d\n", key+1, value)
    }
}原理
与其他语言不同:
内存分析
和其他语言一样,略
注意事项 - Go数组的长度是类型的一部分
var arr1 = [3]int {3,6,9}
fmt.Printf("数组类型为: %T", arr1)	// 打印:[3]int注意事项 - Go数组属于值类型,默认值传递
func main() {	
    var arr3 = [3]int {3,6,9}
    test1(arr3)
    fmt.Println(arr3)	// [3,6,9]
}
func test1(arr [3]int) {
	arr[0] = 7
}如有需要使用指针传递
func main() {	
    var arr3 = [3]int {3,6,9}
    test1(&arr3)
    fmt.Println(arr3)	// [7,6,9]
}
func test1(arr *[3]int) {
    (*arr)arr[0] = 7
}扩展 - 二维数组
和其他语言基本相同,略
切片 (Slice)
与其他语言不同:
- 在其他语言C/C++/Java等中并不常见
 - 在Python也有但似乎不是一个独立的类型,最常用的还是是列表
 - 在Go中是非常常用的,主要使用的容器类型就是切片而非数组。
 
声明定义、初始化、赋值、使用
创建方式非常多样灵活
直接创建空切片 (注意:无存值空间)
// 若不初始化,则相当于内部的内容指针为nil
slice := []int
fmt.Println(slice)			// 这里会报错切片不可声明后直接使用 (内容指针nil)。需要让切片引用一个内存 (方式很多):初始化、make创建、数组或切片转切片、切片修改方法等
原因:数组默认初始化的结果是值均0,切片默认初始化的结果是内容指针值为nil
初始化写法
// 区分:`[]`类型表示的是切片,可变长。而`[...]`会自动填数字,有数字的表示的是数组,定长
slice := []int {1, 4, 7}	// 其实这种方式本质上也相当于引用了一个数组
list := [...]int {3, 6, 9}用make创建切片
package main
import "fmt"
func main() {
    slice := make([]int, 4, 20)					// 类型[]int,长度4,容量20
    fmt.Println(slice, len(slice), cap(slice))	// 打印:值[0,0,0,0], 长度4, 容量20
    
    slice[0] = 66
    slice[1] = 88
}从数组创建切片
package main
import "fmt"
func main() {
    // 数组
    var intArr [6]int = [6]int {3, 6, 9, 1, 4, 7}
    fmt.Printf("值: %v", intArr)					// 值: [3, 6, 9, 1, 4, 7]
    fmt.Printf("6值对应的地址: %p", &intArr[1])	// 0xc308
    
    // 切片
    var slice []int = intArr[1:3]
    fmt.Println("值: %v, 个数: %v, 容量: %v",	// 值 [6, 9], 个数 2, 容量 5; 容量的值比较接近个数的两倍
                slice, len(slice), cap(slice))
    fmt.Printf("6值对应的地址: %p", &slice[0])	// 0xc308 (同数组下标1的值)
    
    // 语法糖
    var slice = arr[0:b]			等同 var slice = arr[:b]
    var slice = arr[a:len(arr)]		等同 var slice = arr[a:]
    var slice = arr[0:len(arr)]		等同 var slice = arr[:]
}从切片创建切片
例如
slice2 := slice[1:2]方法
遍历
都差不多
slice := make([]int, 4, 20)
slice[0] = 66
// 方式1: 普通for
for i:=0; i<len(slice); i++ {
    fmt.Print("slice[%v] == %v\n", i, slice[i])
}
// 方式2: for range
for i, v := range slice {
    fmt.Printf("slice[%v] == %v\n", i, v)
}增删改查
// 改
// 切片 -> 切片
slice2 := append(slice, 88, 50)		// 追加。底层会创建一个新数组+新切片。也可以赋值回给自己,但此时该切片不再能修改原数组
slice3 := append(slice, slice2...)	//     ^ 这里三个点类似TypeScript的用法,必须写
slice2 := slice[1:2]				// 减少
copy(slice2, slice1)				// 拷贝。将slice1数组复制到slice2的数组
// 其他 -> 切片
var slice []int = intArr[1:3]		// 数组转切片原理
(详见 ”数组 vs 切片 vs 其他容器“,本质是一个结构体实现的信息头,共用存值地址,可以用切片修改原数组的值)
数组 vs 切片 vs 其他非键值容器
对比 Go 中各种容器:
Malloc空间
- Go:不太确定,写到这里的时候还没学到,new 函数是类似的作用?但感觉 Go 一般不这样做,用得不多
 - 本质 
- 长度不可变
 - 内存:无额外内存占用
 - 实现:依赖系统接口,操作虚拟内存。需要注意不是真的无额外内存占用,而是语言层面上无 (获取不到,系统自动处理)。虚拟内存分配空闲段时头部是有长度等元信息的。以便仅需头指针即可释放内存
 
 
数组
- Go:没切片那么常用,类型例如:
[n]int或[...]int - 本质 
- 长度不可变
 - 内存:无额外内存占用
 - 实现:非常普通的连续内存,最简单且占用最小的容器
 
 - 与其他语言不同: 
- 注意Go数组有几个与其他语言不同的特殊点: 
- 基本类型
 - 值传递
 - 不同长度数组是不同的类型
 
 - 遍历 / len() 的本质: 
- C 数组:传递时往往要传递头指针+长度
 - C++ 容器:可以直接范围循环 (容器头存储了len信息)
 - Go数组:由于我们能用 
len()获取数组长度,自然也能直接遍历。而len()的本质类似C++的常量表达式,反正是编译时获取的 (毕竟定长,和获取int/byte等其他基本类似的长度没什么区别。所以说 Go 的数组为什么是基本类型) 
 
 - 注意Go数组有几个与其他语言不同的特殊点: 
 
切片
Go:非常更常用,类型例如:
[]int,使用例如:可变数量参数的本质就是切片、r := []rune(str)遍历经常先转再遍历切片本质
仅元信息/信息头。是在数组类型上的抽象,对数组一个连续片段的引用 (整个数组/子集),提供了一个数组相关的动态窗口
内存:仅信息头,切片类型自身不包含存值内存。
特别的:切片不可声明后直接使用 (内容指针nil)。需要让切片引用一个内存 (方式很多):初始化、make创建、数组或切片转切片、切片修改方法等
实现:除存值内存外,还有信息头,信息头是包含这些内容的结构体:
- 长度(切片长度,等于结束下标减头下标,包括头下标而不包括结束下标对应的值)
 - 容量(可以动态改变)
 - 内容头指针(直接创建则为nil,需要由数组创建以与原数组共用存值的内存,或用make创建对应内存)
 
再高级的其他容器
- Go: 还没学到
 - 本质 
- 往往都有额外占用来存储数据,且地址与值内存分离 (若要支持扩容这一点是必须的)
 - 一般即有元信息/信息头,又有存值内存的所有权
 
 
映射 Map (键值对)
key、value 可以是:bool、数字、string、指针、channel、前面对应的接口、结构体、数组。不过key通常为int、string(slice、map、function 不可以)
与其他语言不同:
不同语言的键值对类型:
- C++:std
map (有序)  - Java:HashMap (无序)、TreeMap (有序)
 - Python <= 3.6:dict (无序)、collections.OrderedDict (有序)
 - Python >= 3.7:无 (无序)、dict (有序)
 - Go:map (无序)、第三方库或自定义结构 (有序)
 
底层实现:
- 有序键值对 
- 平衡二叉树 (AVL树)
 - 平衡二叉树 (红黑树),(Java、C++ 用)
 - 排序数组 (不常见,增删慢,查找二分快)
 - 不常用 
- B/B+树
 - 前缀树 (Trie)
 
 
 - 无序键值对 
- 哈希表
 - 链表
 - 开发寻址表 (Open Addressing Table)
 
 - 通用 
- 布隆过滤器 (Bloom Filter)
 
 
声明定义、初始化、赋值、使用
和slice一样,直接声明是没分配空间的。
创建方式也是非常多的
直接创建空map (注意:无存值空间)
var 变量名 map[keyType]valueType
// 声明定义
var a map[int]string初始化写法
c := map[int]string {
    2021: "a",
    2023: "b",
	2024: "c",
}用make创建map
func main() {
    var a map[int]string
    a = make(map[int]string, 10)	// 可以存10个键值对
    a[2012] = "a"
    a[2013] = "b"
    a[2014] = "c"
    fmt.Println(a)					// 打印:map[2013:b 2012:a 2014:c]
    
    b := make(map[int]string)		// 参数二可以不写自动分配
}方法
遍历
用 for range 遍历
for k, v := range myMap {
    fmt.Printf("key:%v, value:%v \t", k, v)
}增删改查
// 查
value, bool = myMap["key"]			// 查找。value为值, bool为是否返回
mapLen := len(myMap)				// 获取长度。键值对的个数
// 增/改
myMap["key"] = "66"					// 增改。很简单,如果没有对应的数据就会自动新增
// 删
delete(myMap, "key")				// 删除。不存在不报错
myMap = make(...) 					// 清空。一种方式是遍历删除,另一种方式是make一个新的,让原来的成为垃圾被GC回收扩展 - 嵌套map
value是map类型 (嵌套key) 的遍历:
a := make(map[string]map[int]string)	// 所以说Go不加小括号有时挺难看的
a["班级1"] = make(map[int]string, 3)
a["班级1"]["张三"] = "21"
a["班级1"]["李四"] = "20"
a["班级2"] = make(map[int]string, 3)
a["班级2"]["王五"] = "19"
a["班级2"]["老六"] = "18"
for k1,v1 := range a {
    fmt.Println(k1)
    for k2,v2 := range v1 {
        fmt.Printf("班级 %v, 名字 %v, 年龄 %v\t", k1, k2, v2)
    }
}管道 (Channel)
(详见 “协程” 一章)
链接到当前文件 0
没有文件链接到当前文件