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函数格式化输出每个员工的全部信息。 - 最后,我们演示了如何通过切片索引来访问和打印特定员工的信息。
 
通过这些基础的数据结构,你可以开始构建更加复杂的数据模型和逻辑来处理各种编程问题。
              微信
            
              支付宝