容器
数组¶
Go语言中的数组(Array)与传统编程语言中的数组有所不同, 主要特点如下:
1. 基本概念:
- 数组是同一种数据类型的固定长度的序列.
- 数组的长度在定义时必须是常量, 并且是数组类型的组成部分. 一旦定义, 长度不能改变.
- 例如,
var a [5]int和var a [10]int是两种不同的数组类型, 因为它们的长度不同.
2. 声明与初始化:
- 声明:
var 变量名 [长度]类型, 如var a [5]int. - 初始化:
- 完全初始化:
var arr1 = [5]int{1, 2, 3, 4, 5} - 部分初始化:
var arr0 [5]int = [5]int{1, 2, 3}(未初始化的元素默认为零值) - 自动推断长度:
var arr2 = [...]int{1, 2, 3, 4, 5, 6}(使用...让编译器根据初始化值推断数组长度) - 指定索引初始化:
var str = [5]string{3: "hello world", 4: "tom"}(只初始化特定索引的元素) - 结构体数组初始化: 可以初始化包含结构体的数组, 如
d := [...]struct { name string; age uint8 }{ {"user1", 10}, {"user2", 20} }
- 完全初始化:
3. 访问:
- 数组元素通过下标访问, 下标从
0开始, 最后一个元素的下标是len-1. - 如果下标超出数组合法范围, 会触发
panic(访问越界). - 可以使用
for循环和for range循环遍历数组.
4. 值类型:
- Go 语言中的数组是值类型. 这意味着当数组进行赋值或作为函数参数传递时, 会复制整个数组, 而不是传递指针. 因此, 对副本的修改不会影响原始数组.
- 为了避免性能问题(特别是对于大型数组), 通常建议使用切片(slice)或数组指针
*[n]T来传递数组.
5. 比较操作符:
- 数组支持
==和!=操作符, 因为它们的内存总是被初始化过的.
6. 指针数组与数组指针:
- 指针数组
[n]*T: 数组的元素是指针. - 数组指针
*[n]T: 指向数组的指针.
7. 内置函数:
len(a): 返回数组的长度(元素数量).cap(a): 对于数组,cap也返回数组的长度.
8. 多维数组:
- Go 语言也支持多维数组, 声明和初始化方式类似一维数组.
- 例如:
var arr1 [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}}. - 遍历多维数组可以使用嵌套的
for range循环.
切片¶
Go 语言中的切片(Slice)是对数组的引用, 因此它是引用类型, 但其自身是结构体, 在传递时是值拷贝.
以下是关于 Go 切片的一些关键点:
- 可变长度: 切片的长度是可变的, 可以看作是一个可变的数组.
- 长度与容量:
len()函数返回切片中可用元素的数量, 读写操作不能超过此限制.cap()函数返回切片的最大扩展容量, 不能超出其底层数组的限制.- 切片的长度始终小于或等于容量:
0 <= len(slice) <= cap(array).
- nil 切片: 如果一个切片为
nil, 那么它的len和cap都等于 0. - 创建方式:
- 声明切片:
var s1 []int - 短声明:
s2 := []int{} - 使用
make()函数:var s3 []int = make([]int, 0)slice := make([]type, len)slice := make([]type, len, cap)
- 初始化赋值:
s5 := []int{1, 2, 3} - 从数组切片:
arr := [5]int{1, 2, 3, 4, 5}; s6 = arr[1:4](前闭后开区间)
- 声明切片:
- 切片初始化(全局和局部): 可以通过
arr[start:end],arr[:end],arr[start:],arr[:]等方式从数组中创建切片. - 内存布局: 切片操作的实际目标是底层数组, 需要注意索引号的差异.
append函数:- 用于向切片尾部添加数据, 并返回新的切片对象.
- 如果追加数据后超出原切片的容量 (
cap) 限制, Go 会重新分配一个更大的底层数组, 并将原有数据复制过去(通常以2倍容量重新分配). - 为了减少内存分配和数据复制开销, 在大批量添加数据时, 建议一次性分配足够大的空间, 或初始化足够长的
len属性, 然后通过索引操作. - 及时释放不再使用的切片对象, 避免持有过期数组导致垃圾回收无法进行.
cap重新分配规律: 当切片容量不足时, 通常会以2倍的方式进行扩容.copy函数:- 在两个切片之间复制数据, 复制长度以较小的切片长度为准.
- 两个切片可以指向同一个底层数组, 允许元素区间重叠.
- 建议及时将所需数据复制到较小的切片中, 以便释放超大号底层数组的内存.
- 切片遍历: 使用
for index, value := range slice结构进行遍历. - 切片调整大小(
resize): 可以通过重新切片来调整切片的大小, 例如b := a[1:2]或c := b[0:3]. - 字符串与切片:
- Go 语言中字符串的底层是
byte数组, 因此可以进行切片操作. - 字符串本身是不可变的. 要修改字符串中的字符, 需要先将其转换为
[]byte(英文字符串) 或[]rune(包含中文字符的字符串), 修改后再转换回string.
- Go 语言中字符串的底层是
- 三索引切片 (
slice[x:y:z]):slice[x:y:z]表示切片内容为[x:y], 切片长度为y-x, 切片容量为z-x.- 例如
slice[:6:8]表示切片内容从索引 0 到 6(不包含6), 长度为 6, 容量为 8.
- 数组或切片转字符串: 可以使用
strings.Replace(strings.Trim(fmt.Sprint(array_or_slice), "[]"), " ", ",", -1)的方式将数组或切片转换为逗号分隔的字符串.
指针¶
包括以下几个方面:
-
指针的概念:
- Go 语言中的指针与 C/C++ 不同, 是"安全指针", 不能进行偏移和运算.
- 理解指针需要掌握三个概念: 指针地址, 指针类型和指针取值.
-
Go 语言中的指针:
- Go 语言函数传参是值拷贝, 如果想修改变量, 可以使用指向该变量地址的指针变量.
- 使用指针传递数据可以避免拷贝数据.
- Go 语言中的指针操作主要使用
&(取地址) 和*(根据地址取值) 两个符号.
-
指针地址和指针类型:
- 每个变量在运行时都有一个内存地址.
&操作符用于获取变量的地址.- 值类型 (int, float, bool, string, array, struct) 都有对应的指针类型, 例如
*int,*string. - 通过
ptr := &v语法可以获取变量v的地址,ptr的类型为*T(T 的指针类型).
-
指针取值:
- 对普通变量使用
&获取指针后, 可以使用*操作符对指针进行取值, 获取指针指向的原变量的值. &和*是一对互补的操作符.- 总结了变量, 指针地址, 指针变量, 取地址, 取值的相互关系和特性.
- 通过
modify1和modify2函数的例子, 说明了值传递和指针传递的区别.
- 对普通变量使用
-
空指针:
- 当指针未分配任何变量时, 其值为
nil. - 提供了判断空指针的示例代码.
- 当指针未分配任何变量时, 其值为
-
new 和 make:
- 在 Go 语言中, 引用类型变量在使用前需要声明并分配内存空间.
new和make是 Go 语言内置的两个用于分配内存的函数.
-
new 函数:
new(Type)函数接受一个类型作为参数, 返回一个指向该类型内存地址的指针.new函数返回的指针所指向的值是该类型的零值.- 示例代码展示了
new函数的使用, 以及如何对指针进行初始化和赋值.
-
make 函数:
make函数用于为slice,map和chan这三种引用类型创建内存.make函数返回的是这三种类型本身, 而不是它们的指针类型.make函数是不可替代的, 在使用slice,map和channel时必须使用make进行初始化.- 示例代码展示了
make函数如何初始化map并进行赋值.
-
new 与 make 的区别:
- 两者都用于内存分配.
make只用于slice,map和channel的初始化, 返回的是引用类型本身.new用于类型的内存分配, 返回的是指向类型零值的指针.
map¶
在 Go 语言中, map 是一种无序的键值对数据结构, 类似于其他语言中的字典或哈希表. 它是一个引用类型, 这意味着在声明 map 变量后, 必须使用 make() 函数进行初始化才能使用.
以下是关于 Go 语言 map 的详细解释:
1. map 的定义
map 的定义语法如下:
KeyType: 表示键的类型, 可以是任何可比较的类型(如整型, 浮点型, 字符串, 数组, 结构体等).ValueType: 表示键对应的值的类型, 可以是任何类型.
2. map 的初始化
map 类型的变量默认初始值为 nil, 需要使用 make() 函数来分配内存并初始化. 语法为:
cap: 表示map的容量. 这是一个可选参数, 但建议在初始化map时指定一个合适的容量, 以优化性能.
示例:
map 也支持在声明时直接填充元素:
3. map 的基本使用
-
添加/修改元素:
-
获取元素:
-
判断键是否存在:
Go 语言提供了一种特殊的语法来判断
map中键是否存在:value: 如果键存在,value为对应的值; 如果键不存在,value为值类型的零值.ok: 一个布尔值, 如果键存在则为true, 否则为false.
示例:
4. map 的遍历
使用 for range 遍历 map:
for k, v := range scoreMap {
fmt.Println(k, v) // 遍历键和值
}
for k := range scoreMap {
fmt.Println(k) // 只遍历键
}
**注意: ** 遍历 map 时的元素顺序与添加键值对的顺序无关, 因为 map 是无序的.
5. 删除键值对
使用 delete() 内建函数从 map 中删除一组键值对:
map: 要删除键值对的map.key: 要删除的键值对的键.
示例:
6. 按照指定顺序遍历 map
由于 map 是无序的, 如果需要按照特定顺序遍历 map, 可以先将 map 的所有键取出放入一个切片中, 然后对切片进行排序, 最后按照排序后的键遍历 map.
示例:
// ... (生成 scoreMap 的代码)
var keys = make([]string, 0, 200)
for key := range scoreMap {
keys = append(keys, key)
}
sort.Strings(keys) // 对切片进行排序
for _, key := range keys {
fmt.Println(key, scoreMap[key]) // 按照排序后的键遍历 map
}
7. 元素为 map 类型的切片
可以创建一个切片, 其元素类型为 map. 在使用前需要对切片中的每个 map 元素进行初始化.
var mapSlice = make([]map[string]string, 3) // 创建一个包含3个map的切片
// 对切片中的map元素进行初始化
mapSlice[0] = make(map[string]string, 10)
mapSlice[0]["name"] = "王五"
// ...
8. 值为切片类型的 map
可以创建一个 map, 其值类型为切片.
var sliceMap = make(map[string][]string, 3) // 创建一个值为切片的map
key := "中国"
value, ok := sliceMap[key]
if !ok {
value = make([]string, 0, 2) // 如果键不存在, 初始化一个切片
}
value = append(value, "北京", "上海")
sliceMap[key] = value
结构体¶
Go语言中的结构体(struct)是一种自定义数据类型, 它允许你将多个不同类型的数据组合成一个单一的复合类型. 这与面向对象编程中的"类"概念相似, 但Go语言本身不直接支持"类"的继承等面向对象特性, 而是通过结构体的内嵌和接口来实现更高的扩展性和灵活性.
以下是该文档中关于Go语言结构体的核心概念:
1. 类型别名和自定义类型
- 自定义类型: 使用
type关键字可以定义一个全新的类型, 它基于已有的基本类型(如int,string等)或通过struct定义. - 类型别名: Go 1.9 引入的新功能, 使用
type TypeAlias = Type定义. 类型别名只是原有类型的一个别名, 本质上两者是同一个类型. 例如byte = uint8和rune = int32. - 区别: 自定义类型会创建一个新的类型, 而类型别名只是给现有类型起一个新名字. 编译后, 类型别名不会作为独立类型存在.
2. 结构体的定义
- 使用
type和struct关键字定义结构体:TypeName: 结构体的名称, 在一个包内必须唯一.FieldName: 结构体字段的名称, 在一个结构体中必须唯一.FieldType: 结构体字段的具体类型.
- 示例: 相同类型的字段也可以写在一行:
- 结构体是一种聚合型数据类型, 用于描述一组值(如一个人的姓名, 年龄, 城市).
3. 结构体的实例化
- 结构体只有在实例化后才会分配内存并能使用其字段.
- 基本实例化:
通过
.访问结构体的字段. - 匿名结构体: 用于定义临时数据结构, 不需要单独命名.
- 创建指针类型结构体: 使用
new关键字或&操作符获取结构体的地址. Go语言支持直接通过结构体指针使用.访问成员 (如p2.name), 这是一种语法糖, 底层会自动解引用.
4. 结构体初始化
- 默认零值初始化:
- 使用键值对初始化:
- 使用值的列表初始化 (简写):
- 必须初始化所有字段.
- 初始值顺序必须与字段声明顺序一致.
- 不能与键值对初始化混用.
5. 结构体内存布局
- 结构体字段在内存中是连续存储的.
6. 构造函数
- Go语言没有内置的构造函数, 但可以自定义函数来返回结构体实例或指针. 通常返回结构体指针以避免大数据结构的值拷贝开销.
7. 方法和接收者
- 方法 (Method): 作用于特定类型变量的函数, 这个特定类型变量称为接收者 (Receiver).
- 定义格式:
- 接收者变量名通常使用接收者类型名的第一个小写字母.
- 接收者类型可以是值类型或指针类型.
- 指针类型的接收者:
- 可以修改接收者本身的成员变量.
- 类似于其他语言中的
this或self.
- 值类型的接收者:
- 方法内部对接收者成员的修改只作用于副本, 不会影响原始变量.
- 何时使用指针类型接收者:
- 需要修改接收者中的值.
- 接收者是较大的对象, 值拷贝开销大.
- 为保持一致性, 如果某个方法使用指针接收者, 其他方法也应如此.
- 任意类型添加方法: 除了结构体, 任何自定义类型都可以拥有方法.
- 注意: 不能给其他包的类型定义方法.
8. 结构体的匿名字段
- 结构体成员字段可以只有类型而没有字段名. 匿名字段默认使用类型名作为字段名, 因此一个结构体中同种类型的匿名字段只能有一个.
9. 嵌套结构体
- 一个结构体可以包含另一个结构体或结构体指针作为其字段.
- 嵌套匿名结构体: 可以直接访问匿名结构体的字段. 当访问结构体成员时, 会优先在当前结构体中查找, 找不到再去匿名结构体中查找.
- 字段名冲突: 当嵌套结构体有相同字段名时, 需要明确指定内嵌结构体的字段以避免歧义.
- 结构体的"继承": Go语言通过嵌套匿名结构体实现类似其他语言的继承特性.
这样
type Animal struct { name string } func (a *Animal) move() { /* ... */ } type Dog struct { Feet int8 *Animal // 嵌套匿名结构体指针实现"继承" } func (d *Dog) wang() { /* ... */ }Dog类型就拥有了Animal的move方法. -
内存管理和共享::
- 当嵌套的是值类型结构体时 (Address Address), 每次创建外部结构体 (Person) 的实例, 都会为内部结构体 (Address) 拷贝一份新的数据.
- 当嵌套的是指针类型结构体时 (Address *Address), 外部结构体 (PersonWithAddressPtr) 只存储一个指向内部结构体 (Address) 的内存地址. 这意味着多个外部结构体实例可以共享同一个内部结构体实例.
10. 结构体字段的可见性
- 字段名首字母大写表示可公开访问(导出).
- 字段名首字母小写表示私有(仅在定义当前结构体的包中可访问).
11. 结构体与 JSON 序列化
- Go语言的
json包可以将结构体与 JSON 格式的字符串进行相互转换. json.Marshal: 结构体 -> JSON 字符串json.Unmarshal: JSON 字符串 -> 结构体
12. 结构体标签 (Tag)
- Tag 是结构体的元信息, 通过反射机制在运行时读取.
- 定义格式:
`key1:"value1" key2:"value2"` - 键值对之间用空格分隔, 键与值用冒号分隔, 值用双引号括起来.
- 常用于 JSON 序列化时指定字段的键名, 例如: