公司注销网站备案申请表,从网站建设到网站运营,网站建设需要会,中国建设人才信息网站有基础转Go语言学习笔记#xff08;2. 基本数据结构篇#xff09;
1. 数组和切片#xff08;Array Slice#xff09;
在Go语言中#xff0c;数组#xff08;Array#xff09;和切片#xff08;Slice#xff09;是基础且常用的数据结构#xff0c;它们有相似之…有基础转Go语言学习笔记2. 基本数据结构篇
1. 数组和切片Array Slice
在Go语言中数组Array和切片Slice是基础且常用的数据结构它们有相似之处但也有关键的区别。
数组Array
数组是具有固定大小的数据结构用于存储同类型的元素集合。数组的长度在声明时确定并且不能改变。
var arr [5]int // 声明一个包含5个整数的数组
arr[0] 1 // 数组的索引从0开始特点
固定长度数组的长度是其类型的一部分一旦定义就不能改变。值类型在Go中数组是值类型这意味着当它们被赋值给一个新变量或作为参数传递时将进行整个数组的复制。内存分配数组在编译时分配内存大小固定。
切片Slice
切片是对数组的抽象提供了更加灵活、强大和方便的接口来处理数据序列。切片本身不存储任何数据它只是对底层数组的引用。
slice : []int{1, 2, 3, 4, 5} // 创建一个切片
newSlice : slice[1:3] // 创建一个新的切片包含元素slice[1], slice[2]特点
动态大小切片的长度是可变的可以随时添加或删除元素在其容量范围内或通过重新分配来扩展容量。引用类型切片是引用类型当它们被赋值给一个新变量时两个变量将引用相同的底层数组。自动管理容量切片的容量可以根据需要自动增长。append函数用于添加元素到切片中如有必要会自动扩容。
遍历
在Go语言中遍历数组和切片主要有以下几种方法
1. 使用传统的 for循环
使用传统的 for循环通过索引来访问每个元素。
slice : []int{1, 2, 3, 4, 5}
for i : 0; i len(slice); i {fmt.Println(slice[i])
}这种方法在需要索引时非常有用例如当你需要修改原始数组或切片时。
3. 使用切片操作
在某些情况下你可能想要遍历切片的一部分。这时可以结合切片操作和 for循环。
for _, value : range slice[1:4] {fmt.Println(value)
}这段代码将遍历切片中的第二个到第四个元素。
2. 使用 range关键字
range提供了一种更简洁的方式来遍历数组和切片。
for index, value : range slice {fmt.Printf(Index: %d, Value: %d\n, index, value)
}如果你只需要值可以忽略索引
for _, value : range slice {fmt.Println(value)
}同样如果只需要索引
for index : range slice {fmt.Println(index)
}使用 range通常是更为“惯用”的Go代码风格。
4. 使用递归
虽然不常见但在某些特定场景下你可能会通过递归的方式遍历数组或切片。
func recursivePrint(slice []int, index int) {if index len(slice) {fmt.Println(slice[index])recursivePrint(slice, index1)}
}recursivePrint(slice, 0)这种方法在处理树形结构或需要特殊遍历逻辑时可能会有用。
传参
在Go语言中将数组作为参数传递给函数时需要注意几个关键点
1. 数组是值类型
数组在Go中是值类型这意味着当数组作为参数传递给函数时实际上传递的是数组的副本而不是它的引用。
func modifyArray(arr [5]int) {arr[0] 10 // 这只会修改数组的副本
}func main() {array : [5]int{1, 2, 3, 4, 5}modifyArray(array)fmt.Println(array) // 输出: [1 2 3 4 5]原始数组未被修改
}由于数组是值传递所以任何在函数内部对数组的修改都不会影响原始数组。
2. 使用指针传递数组
为了在函数内部修改数组本身你可以传递一个指向数组的指针。
func modifyArray(arr *[5]int) {arr[0] 10 // 现在这会修改原始数组
}func main() {array : [5]int{1, 2, 3, 4, 5}modifyArray(array)fmt.Println(array) // 输出: [10 2 3 4 5]
}这种方式允许你在不复制整个数组的情况下修改数组。
3. 使用切片代替数组
在Go中切片比数组更常用因为它们更加灵活且易于使用。当你传递切片到函数时实际上传递的是切片的引用这意味着在函数内部对切片的修改会影响原始切片。
func modifySlice(s []int) {s[0] 10
}func main() {slice : []int{1, 2, 3, 4, 5}modifySlice(slice)fmt.Println(slice) // 输出: [10 2 3 4 5]
}2. 切片Slice
在Go语言中切片Slice是一个灵活、功能强大的数据结构用于表示可变长的序列。切片是对数组的封装提供了更高层次的抽象。以下是切片的多种声明方式和使用方法
声明切片 使用 make函数 slice : make([]int, 5) // 创建一个长度和容量都是5的切片直接声明并初始化 slice : []int{1, 2, 3, 4, 5} // 创建并初始化切片声明一个nil切片 var slice []int // 声明一个nil切片使用现有数组或切片创建新切片 array : [5]int{1, 2, 3, 4, 5}
slice : array[1:4] // 基于数组创建切片包含元素2、3、4使用切片 添加元素 使用 append函数向切片添加元素。如果切片的容量不够append会自动扩容。 slice append(slice, 6) // 添加元素到切片遍历切片 使用 for循环和 range关键字遍历切片。 for index, value : range slice {fmt.Println(index, value)
}修改切片元素 直接通过索引修改。 slice[0] 10 // 修改第一个元素切片截取 切片可以被进一步切割。 subSlice : slice[1:3] // 创建一个新的切片包含原切片的第2个和第3个元素切片的容量和长度 使用 len获取切片的长度cap获取切片的容量。 length : len(slice)
capacity : cap(slice)len 与 cap
切片Slice是一个非常灵活的数据结构它提供了对底层数组的引用。理解切片的长度len和容量cap对于正确地使用切片至关重要。
切片的长度Length
定义切片的长度是切片中当前元素的数量。获取方式使用内置的 len函数获取如 len(slice)。特点 长度反映了切片中元素的实际数量。长度永远不会超过容量。可以通过 append函数增加切片的长度在容量允许的范围内。切片长度的修改不会影响底层数组的大小。
切片的容量Capacity
定义切片的容量是从切片的第一个元素开始到其底层数组末尾的元素数量。获取方式使用内置的 cap函数获取如 cap(slice)。特点 容量表示切片可以增长到的最大元素数量不需要重新分配内存。当使用 append函数增加切片长度超过其容量时系统会分配一个新的底层数组并复制原有元素到新数组中通常是原容量的两倍从而增加切片的容量。切片的容量在创建时确定之后可以通过重新分配底层数组来改变。切片操作可以创建新的切片新切片的容量取决于原切片的容量和切片操作的起始位置。
示例
array : [5]int{1, 2, 3, 4, 5}
slice : array[1:3] // 创建一个新切片包括数组的第2个和第3个元素fmt.Println(len(slice)) // 输出: 2
fmt.Println(cap(slice)) // 输出: 4在上述示例中切片 slice的长度为2包含两个元素而其容量为4因为它从底层数组的第二个元素开始直到数组的末尾。
func main() { slice : make([]int, 2, 3) // 初始长度为2容量为3 fmt.Println(Initial Slice:, slice, Length:, len(slice), Capacity:, cap(slice)) // 向切片中添加元素直到超出其容量 for i : 0; i 5; i { slice append(slice, i) fmt.Println(Updated Slice:, slice, Length:, len(slice), Capacity:, cap(slice)) }
}在这个示例中我们首先创建了一个长度为2容量为3的切片。然后我们通过循环向切片中添加元素并在每次追加后打印切片的当前状态包括其长度和容量。
Initial Slice: [0 0] Length: 2 Capacity: 3
Updated Slice: [0 0 0] Length: 3 Capacity: 3
Updated Slice: [0 0 0 1] Length: 4 Capacity: 6
Updated Slice: [0 0 0 1 2] Length: 5 Capacity: 6
Updated Slice: [0 0 0 1 2 3] Length: 6 Capacity: 6
Updated Slice: [0 0 0 1 2 3 4] Length: 7 Capacity: 12初始状态切片的长度是2容量是3。当切片长度达到容量即 len等于 cap时再向切片添加新元素将导致切片的容量增加通常翻倍以便容纳更多的元素。
切片截取
Go语言中的切片截取和Python中的差不多。这样可以从现有切片或数组中生成新的切片。切片截取的基本语法是 slice[low:high]其中 low是起始索引包含high是终止索引不包含。 在Go语言中切片的截取是通过指定起始索引和终止索引来完成的这样可以从现有切片或数组中生成新的切片。切片截取的基本语法是 slice[low:high]其中 low是起始索引包含high是终止索引不包含。
基本示例
originalSlice : []int{1, 2, 3, 4, 5}// 截取索引1包含到索引3不包含
subSlice : originalSlice[1:3] // 结果是[2, 3]示例省略边界
// 从开始到索引3不包含
subSlice1 : originalSlice[:3] // 结果是[1, 2, 3]// 从索引2包含到结束
subSlice2 : originalSlice[2:] // 结果是[3, 4, 5]// 完整的切片
subSlice3 : originalSlice[:] // 结果是[1, 2, 3, 4, 5]注意事项
切片截取是基于原切片的它们共享相同的底层数组。因此修改新切片中的元素也会影响原切片。超出原切片边界的截取将导致运行时错误。切片截取不会复制数据它仅仅创建了一个新的切片头指向原来的底层数组。
深拷贝
深拷贝deep copy指的是创建一个新的对象其内容是对原对象的完整复制。与浅拷贝shallow copy不同深拷贝确保原始数据和复制数据不会共享任何子对象。在Go中对于基本数据类型赋值操作本身就是深拷贝。但对于复合数据类型如结构体、切片、映射等需要特别处理以实现深拷贝。
对于切片和数组可以使用 copy函数实现深拷贝
originalSlice : []int{1, 2, 3}
copiedSlice : make([]int, len(originalSlice))
copy(copiedSlice, originalSlice)深拷贝 截取
使用 copy函数可以实现切片的深拷贝并且可以结合切片截取来只复制切片的一部分。这种方法可以创建一个新的切片它包含原始切片指定范围内的元素的副本与原切片拥有独立的底层数组。
示例深拷贝切片的一部分
假设我们有一个切片我们想要复制这个切片的一部分到一个新的切片中
originalSlice : []int{1, 2, 3, 4, 5}// 定义要复制的切片范围
start : 1 // 起始索引包含
end : 4 // 结束索引不包含// 创建一个新的切片用于存放复制的元素
// 长度是要复制元素的数量
newSlice : make([]int, end-start)// 使用copy进行深拷贝
copy(newSlice, originalSlice[start:end])// 输出结果
fmt.Println(Original Slice:, originalSlice)
fmt.Println(Copied Slice:, newSlice)在这个示例中我们创建了一个长度为 end-start的新切片 newSlice然后使用 copy函数将 originalSlice中从索引 start到 end的元素复制到 newSlice中。
注意事项
确保新切片的长度足以容纳要复制的元素。如果新切片太小copy会只复制适合新切片大小的部分元素。copy函数返回复制的元素数量可以用于检查是否所有需要的元素都被复制了。如果 start或 end索引超出原切片的边界程序将抛出运行时错误。
3. 映射Map
映射Map是一种内置的数据结构提供了基于键值对的存储机制。映射在Go中非常有用用于存储和检索数据。
定义和初始化 定义一个Map var myMap map[string]int这里定义了一个键为 string类型值为 int类型的映射。 初始化 使用 make函数初始化映射 myMap make(map[string]int)初始化带有值的Map myMap : map[string]int{one: 1, two: 2}增加和修改元素
向映射中添加或修改元素myMap[three] 3访问元素 访问映射中的元素 value : myMap[three]访问不存在的键时将返回值类型的零值。例如对于 int类型零值是 0。
检查元素是否存在
使用两个值来接收返回结果其中第二个值是一个布尔值表示键是否存在于映射中value, ok : myMap[three]删除元素
使用内置的 delete函数删除元素delete(myMap, three)遍历Map
使用 for循环和 range关键字遍历映射for key, value : range myMap {fmt.Println(Key:, key, Value:, value)
}注意事项
映射在Go中是引用类型意味着当它们被赋值给新的变量或作为参数传递时新变量和原始映射将引用同一个底层数据。映射不是并发安全的。在多线程环境下使用映射时需要使用同步机制如互斥锁。映射的顺序是不确定的每次遍历映射的顺序可能不同。
4. 结构体struct
结构体Struct是一种自定义的数据类型允许你组合不同类型的数据项。结构体在Go中非常重要通常用于表示具有多个属性的复杂数据结构。以下是有关Go中结构体的基本知识点
定义结构体
使用 type和 struct关键字定义结构体。
type Person struct {Name stringAge int
}这里定义了一个 Person类型的结构体有两个字段Name和 Age。
创建结构体实例
可以通过多种方式创建结构体实例。 使用结构体字面量 person : Person{Alice, 30}指定字段名称 person : Person{Name: Alice, Age: 30}使用 new关键字 personPtr : new(Person) // 创建一个指向Person类型的指针访问和修改字段
访问或修改结构体的字段使用点.操作符。
fmt.Println(person.Name) // 访问
person.Age 31 // 修改方法
在Go中可以给结构体类型定义方法。
func (p Person) SayHello() {fmt.Println(Hello, my name is, p.Name)
}这里定义了一个 SayHello方法它绑定到 Person类型。
指针和结构体
可以使用结构体指针来避免在函数调用时复制结构体的数据。
func (p *Person) SetAge(age int) {p.Age age
}这个方法使用指针接收器允许在不复制结构体的情况下修改它。
匿名字段和嵌入结构体
结构体可以包含匿名或嵌入字段这允许一种简单的形式的继承。
type Employee struct {PersonPosition string
}这里 Employee结构体嵌入了 Person因此继承了 Person的所有字段。
导出和非导出字段
如果结构体字段的名称以大写字母开头则该字段被导出即在包外可见。以小写字母开头的字段是非导出的仅在其定义的包内可见。
总结
结构体是Go中处理复杂数据的基石允许将相关的数据组合在一起。通过使用结构体和其方法Go语言提供了一种简单而强大的方式来表示和操作复杂的数据结构。