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)
}

输出:

image.png

在这个例子中,我们创建了一个切片,对其进行了切片以获取部分内容,并修改了这部分内容。
值得注意的是,修改切片的一部分会影响到原始切片,因为它们引用的是同一底层数组的不同部分。


数组与切片示例

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)
    }
}

输出:

image.png

在这个程序中,我们首先声明了一个名为 numbers 的整型数组,其长度为 5
我们手动给数组的每个位置赋值,然后使用 forrange 来遍历数组,打印出每个元素的索引和值。
接着,我们声明了一个整型切片 slices 并直接在声明时使用了字面量语法来初始化它。
这是声明并初始化切片的快捷方式,这里的切片包含与前面数组相同的值。
同样,我们使用 forrange 遍历切片,并打印出每个元素的索引和值。

这个练习展示了数组和切片的不同:数组有固定的长度,而切片的长度是动态的,可以根据需要进行扩展。
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)
}

输出:

image.png

在这个例子中,我们创建了一个映射,对其进行了添加和删除操作,并展示了如何检查一个键是否存在映射中。

例子:

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)
    }
}

输出:

image.png

在这个例子中,我们创建了一个映射 wizards,它存储了一个字符串到另一个字符串的映射。
我们添加了一些键值对,表示不同角色属于不同的学院。
然后,我们使用一个角色的名字作为键来获取他们所属的学院。
最后,我们使用 forrange 来遍历映射中的所有键值对。

例子:

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
}

输出:

image.png

在这段代码中:

  • 我们首先使用 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)
}

输出:

image.png

这段代码中:

  • 我们定义了一个名为 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)
    }
}

输出:

image.png

在这个例子中:

  • 我们首先定义了一个 Wizard 结构体,它有三个字段:NameHouseStrength
  • 接着,我们创建了一个 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)
}

输出:

image.png

在这段代码中:

  • 定义了一个 Employee 结构体,它包含三个字段:NameIDPosition
  • 接着,我们创建了一个包含三个 Employee 实例的切片 employees,并直接在创建时初始化了它们的字段。
  • 然后,我们使用了 for 循环和 range 关键字来遍历切片中的每个 Employee 实例,并使用 fmt.Printf 函数格式化输出每个员工的全部信息。
  • 最后,我们演示了如何通过切片索引来访问和打印特定员工的信息。

通过这些基础的数据结构,你可以开始构建更加复杂的数据模型和逻辑来处理各种编程问题。


文章作者: Runfa Li
本文链接:
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Linux 小白鼠
GO GO 入门 go
觉得文章不错,打赏一点吧,1分也是爱~
打赏
微信 微信
支付宝 支付宝