03.数据结构
在 Go
语言中,数据结构是存储和组织数据的方式,使我们能够高效地访问和修改数据。
常见的数据结构包括数组、切片、映射(maps
)和结构体(structs
)。📦🧩
数组
数组是一种固定大小的数据结构,用于存储同类型的元素序列。
创建一个数组的语法如下:
var arrayName [size]DataType
例如,一个可以存储五个整数的数组:
var numbers [5]int
输出为:[0 0 0 0 0]
你可以在声明时初始化数组:
numbers := [5]int{1, 2, 3, 4, 5}
输出为:[1 2 3 4 5]
另一种数组的定义方法
// ... 自动识别数组长度
var a = [...]string{"bj","sh","gz"}
输出为:[bj sh gz]
数组的索引(位置编号)从 0
开始。
要访问或修改数组中的元素,可以使用索引:
numbers[0] = 10 // 将第一个元素设置为 10
fmt.Println(numbers[1]) // 输出第二个元素
切片
切片是数组的一个可变长的序列,更加灵活,是 Go
中使用最频繁的数据结构之一。
创建切片不需要指定大小:
var sliceName []DataType
例如,创建一个整数切片并初始化:
numbers := []int{1, 2, 3, 4, 5}
切片可以通过 append
函数动态增长:
numbers = append(numbers, 6) // 添加元素 6 到切片
切片也支持“切片”操作,可以基于现有的切片创建新的切片视图:
subNumbers := numbers[1:3] // 创建一个包含元素 2 和 3 的新切片
例子:
package main
import "fmt"
func main() {
// 创建一个整数切片
numbers := []int{2, 3, 5, 7, 11, 13}
// 打印切片
fmt.Println("切片包含的数字:", numbers)
// 访问切片的部分内容,即切片的切片
sliceOfSlice := numbers[1:4] // 从索引 1 到 3,不包括索引 4
fmt.Println("切片的部分内容:", sliceOfSlice)
// 更改切片的内容,会影响原始切片
sliceOfSlice[0] = 23
fmt.Println("修改后的原始切片:", numbers)
// 切片合并
num1 := []int{1, 2, 3}
num2 := []int{4, 5, 6}
num1 = append(num1, num2...)
fmt.Println(num1)
// 切片删除
num1 = append(num1[:2], num1[3:]...)
fmt.Println(num1)
}
输出:
在这个例子中,我们创建了一个切片,对其进行了切片以获取部分内容,并修改了这部分内容。
值得注意的是,修改切片的一部分会影响到原始切片,因为它们引用的是同一底层数组的不同部分。
数组与切片示例
package main
import "fmt"
func main() {
// 声明一个整型数组,长度为5
var numbers [5]int
// 初始化数组元素
numbers[0] = 10
numbers[1] = 20
numbers[2] = 30
numbers[3] = 40
numbers[4] = 50
// 使用for循环遍历数组
for i, number := range numbers {
// 打印数组的索引和值
fmt.Println("数组的第", i, "个数字是:", number)
}
// string 将其他数据类型转换为 string 类型
// :2 切片,表示数组中第二个元素往后的所有数据
// s_rune 不是一个字符串,已经变成一个 rune 切片
// 将字符串转换为 rune 的 byte 数组,通过切片获取原始数据,通过 string 方法把 byte 数组转换回字符串,实现修改字符串内容
s := "天下第一"
s_rune := []rune(s)
fmt.Println("地上" + string(s_rune[2:]))
// 声明并初始化一个整型切片
// 使用数组的值来初始化切片
slices := []int{10, 20, 30, 40, 50}
// 使用for循环遍历切片
for i, slice := range slices {
// 打印切片的索引和值
fmt.Println("切片的第", i, "个数字是:", slice)
}
}
输出:
在这个程序中,我们首先声明了一个名为 numbers
的整型数组,其长度为 5
。
我们手动给数组的每个位置赋值,然后使用 for
和 range
来遍历数组,打印出每个元素的索引和值。
接着,我们声明了一个整型切片 slices
并直接在声明时使用了字面量语法来初始化它。
这是声明并初始化切片的快捷方式,这里的切片包含与前面数组相同的值。
同样,我们使用 for
和 range
遍历切片,并打印出每个元素的索引和值。
这个练习展示了数组和切片的不同:数组有固定的长度,而切片的长度是动态的,可以根据需要进行扩展。
在 Go
语言中,切片的使用比数组更为频繁,因为它们提供了更大的灵活性和便利的内置函数,如 append
,可以用来动态地增加元素。
映射
**映射(Maps)**是存储键值对的无序集合。
创建和初始化一个映射:
personAge := map[string]int{
"Alice": 25,
"Bob": 30,
}
要添加或修改映射中的元素:
personAge["Charlie"] = 35 // 添加新的键值对
访问映射中的元素:
age := personAge["Alice"] // 获取 Alice 的年龄
检查键是否存在:
age, ok := personAge["Dave"]
if ok {
fmt.Println(age)
} else {
fmt.Println("Dave 不在映射中。")
}
例子:
package main
import "fmt"
func main() {
// 创建一个映射,键为string类型,值为int类型
ages := map[string]int{
"Alice": 31,
"Charlie": 34,
}
// 打印映射
fmt.Println("人员年龄:", ages)
// 添加新的键值对
ages["Bob"] = 28
// 删除键值对
delete(ages, "Alice")
// 使用两个值来检索映射
age, exists := ages["Bob"]
if exists {
fmt.Println("Bob的年龄是:", age)
} else {
fmt.Println("Bob不在映射中")
}
// 打印修改后的映射
fmt.Println("更新后的人员年龄:", ages)
}
输出:
在这个例子中,我们创建了一个映射,对其进行了添加和删除操作,并展示了如何检查一个键是否存在映射中。
例子:
package main
import "fmt"
func main() {
// 创建一个映射,键和值都是字符串类型
wizards := make(map[string]string)
// 将键值对添加到映射中
wizards["Harry"] = "Gryffindor"
wizards["Hermione"] = "Gryffindor"
wizards["Draco"] = "Slytherin"
// 使用键来获取值
house := wizards["Harry"]
fmt.Println("Harry belongs to", house)
// 遍历映射中的所有键值对
for name, house := range wizards {
fmt.Println(name, "belongs to", house)
}
}
输出:
在这个例子中,我们创建了一个映射 wizards
,它存储了一个字符串到另一个字符串的映射。
我们添加了一些键值对,表示不同角色属于不同的学院。
然后,我们使用一个角色的名字作为键来获取他们所属的学院。
最后,我们使用 for
和 range
来遍历映射中的所有键值对。
例子:
package main
import "fmt"
func main() {
// []int 指定数据类型是一个 int 切片
// 切片默认长度为 3,默认容量为 10
// make 对内置数据类型进行申请内存,返回数据值本身
a := make([]int, 3, 10)
fmt.Println(a)
fmt.Println(len(a), cap(a))
// 通过 new 方法申请内存空间
// 给 struct 等非内置数据类型申请空间,返回一个指针类型
b := new([]int)
fmt.Printf("make: %T --- new: %T \n", a, b)
// 使用 make 函数创建一个映射,其中键(key)是 string 类型,值(value)是 int 类型
// 映射用于存储员工的姓名和对应的唯一ID
employees := make(map[string]int)
// 向映射中添加键值对,表示员工的姓名和ID
employees["Alice"] = 1001
employees["Bob"] = 1002
employees["Charlie"] = 1003
// 遍历映射中的所有键值对
// 使用 range 关键字可以同时获取到键(name)和值(id)
for name, id := range employees {
// 打印每个员工的姓名和ID
fmt.Printf("员工的姓名:%s, 员工ID:%d\n", name, id)
}
// 如果需要查找特定员工的ID,可以直接使用键(员工姓名)来获取
// 下面的代码会打印出员工Bob的ID
fmt.Println("Bob的员工ID是:", employees["Bob"])
// 如果尝试获取一个不存在的键,例如 "Dave",将会得到值类型的零值,在这里是int的零值 0
fmt.Println("Dave的员工ID是:", employees["Dave"]) // 将会打印出 0
}
输出:
在这段代码中:
- 我们首先使用
make
函数创建了一个映射employees
,这个映射的键类型为string
,值类型为int
。这里的键和值分别对应员工的姓名和员工ID
。 - 然后,我们向
employees
映射中添加了三个员工的信息。 - 接着,我们使用
for
循环和range
关键字遍历了映射中的所有键值对,并打印出来。 - 我们还展示了如何通过键来访问映射中的特定值,以及如果键不存在时会发生什么。
切片与映射示例
package main
import "fmt"
// 使用切片
func main() {
// 创建一个空的整数切片
var numbers []int
// 使用 append 函数向切片添加元素
numbers = append(numbers, 10) // 现在切片包含一个元素 [10]
numbers = append(numbers, 20, 30, 40) // 可以一次性添加多个元素
// 显示切片的内容
fmt.Println("切片中的数字:", numbers)
// 创建一个映射,键和值都是字符串类型
friendsBirthdays := make(map[string]string)
// 添加键值对到映射中
friendsBirthdays["Alice"] = "1990-01-01"
friendsBirthdays["Bob"] = "1991-02-02"
// 显示映射的内容
fmt.Println("朋友及其生日:", friendsBirthdays)
// 调用函数添加新的键值对
addBirthday(friendsBirthdays, "Charlie", "1992-03-03")
fmt.Println("添加后的朋友及其生日:", friendsBirthdays)
// 调用函数删除键值对
deleteBirthday(friendsBirthdays, "Alice")
fmt.Println("删除后的朋友及其生日:", friendsBirthdays)
}
// addBirthday 函数添加新的生日到映射中
// 参数是映射的引用、姓名和生日
func addBirthday(bdays map[string]string, name, birthday string) {
bdays[name] = birthday
}
// deleteBirthday 函数从映射中删除一个生日
// 参数是映射的引用和要删除的姓名
func deleteBirthday(bdays map[string]string, name string) {
delete(bdays, name)
}
输出:
这段代码中:
- 我们定义了一个名为
main
的函数,它创建了一个空的整数切片numbers
并使用append
函数向其中添加了一些元素。 - 也创建了一个映射
friendsBirthdays
,其中存储了朋友的名字和对应的生日。 addBirthday
函数接受映射、名字和生日作为参数,并将新的键值对添加到映射中。deleteBirthday
函数接受映射和一个名字作为参数,并从映射中删除与该名字关联的键值对。
在使用这段代码时,你将看到切片和映射的内容被打印到控制台,并且可以观察到添加和删除操作对数据结构的影响。
结构体
**结构体(Structs)**允许我们定义和组合多种不同的数据类型。
它是创建自定义类型的一种方式:
type Person struct {
Name string
Age int
}
创建结构体的实例并初始化:
alice := Person{Name: "Alice", Age: 25}
访问结构体的字段:
fmt.Println(alice.Name) // 输出 Alice 的名字
例子:
package main
import "fmt"
// 定义一个结构体 `Wizard`,它包含了一个巫师的几个属性
type Wizard struct {
Name string // 巫师的名字
House string // 巫师所属的学院
Strength int // 巫师的力量值
}
func main() {
// 通过 var 关键字实例化结构体
var w1 Wizard
w1.Name = "张三"
w1.House = "社会学院"
w1.Strength = 100
fmt.Println(w1)
fmt.Println(w1.Name)
fmt.Println(w1.House)
fmt.Println(w1.Strength)
// new 方法实例化结构体,返回指针类型
var w2 = new(Wizard)
w2.Name = "李四"
w2.House = "社会大学"
w2.Strength = 120
fmt.Println(w2)
fmt.Println(w2.Name)
fmt.Println(w2.House)
fmt.Println(w2.Strength)
// := 键值对初始化
// 创建一个 `Wizard` 结构体的实例,指定了每个字段的值
harry := Wizard{Name: "Harry", House: "Gryffindor", Strength: 85}
// 打印这个结构体实例的字段
fmt.Println("Wizard:", harry.Name)
fmt.Println("House:", harry.House)
fmt.Println("Strength:", harry.Strength)
// 创建结构体切片,包含多个巫师
wizards := []Wizard{
{Name: "Harry", House: "Gryffindor", Strength: 85},
{Name: "Hermione", House: "Gryffindor", Strength: 95},
{Name: "Draco", House: "Slytherin", Strength: 80},
}
// 遍历切片,打印每个巫师的信息
for _, wizard := range wizards {
fmt.Printf("%s from %s has strength %d\n", wizard.Name, wizard.House, wizard.Strength)
}
}
输出:
在这个例子中:
- 我们首先定义了一个
Wizard
结构体,它有三个字段:Name
、House
和Strength
。 - 接着,我们创建了一个
Wizard
类型的变量harry
,并为其字段赋值。 - 我们使用
fmt.Println
来打印harry
变量的各个字段的值。 - 然后,我们创建了一个
Wizard
结构体的切片wizards
,它包含了多个Wizard
实例。 - 最后,我们遍历这个切片,并打印出每个
Wizard
实例的详细信息。
例子:
package main
import "fmt"
// Employee 结构体定义,代表了员工的一个数据模型
type Employee struct {
Name string // 员工的名字
ID int // 员工的ID
Position string // 员工的职位
}
func main() {
// 初始化一个 Employee 类型的切片,直接在创建时填充数据
employees := []Employee{
// 创建第一个 Employee 实例,并填充字段
{Name: "Alice", ID: 1001, Position: "Engineer"},
// 创建第二个 Employee 实例,并填充字段
{Name: "Bob", ID: 1002, Position: "Sales"},
// 创建第三个 Employee 实例,并填充字段
{Name: "Charlie", ID: 1003, Position: "Manager"},
}
// 遍历切片,对每个 Employee 实例进行操作
for _, emp := range employees {
// 使用 Printf 来格式化输出员工信息
// %s 代表字符串,%d 代表十进制整数
fmt.Printf("员工姓名:%s, 员工ID:%d, 职位:%s\n", emp.Name, emp.ID, emp.Position)
}
// 如果需要单独访问某个员工的信息,可以通过索引访问切片元素
// 下面的代码访问并打印了切片中第一个员工的信息
fmt.Println("第一个员工的姓名:", employees[0].Name)
fmt.Println("第一个员工的ID:", employees[0].ID)
fmt.Println("第一个员工的职位:", employees[0].Position)
}
输出:
在这段代码中:
- 定义了一个
Employee
结构体,它包含三个字段:Name
、ID
和Position
。 - 接着,我们创建了一个包含三个
Employee
实例的切片employees
,并直接在创建时初始化了它们的字段。 - 然后,我们使用了
for
循环和range
关键字来遍历切片中的每个Employee
实例,并使用fmt.Printf
函数格式化输出每个员工的全部信息。 - 最后,我们演示了如何通过切片索引来访问和打印特定员工的信息。
通过这些基础的数据结构,你可以开始构建更加复杂的数据模型和逻辑来处理各种编程问题。