<返回目录     Powered by claud/xia兄

第6课: Map与结构体

Map(映射)

Map是Go语言中的键值对集合,类似于其他语言中的字典或哈希表。Map是引用类型。

Map的创建

package main

import "fmt"

func main() {
    // 方式1:使用make创建
    ages := make(map[string]int)
    ages["Alice"] = 25
    ages["Bob"] = 30
    fmt.Println(ages)  // map[Alice:25 Bob:30]

    // 方式2:字面量初始化
    scores := map[string]int{
        "math":    95,
        "english": 88,
        "physics": 92,
    }
    fmt.Println(scores)

    // 方式3:声明但不初始化(nil map)
    var m map[string]int
    fmt.Println(m == nil)  // true
    // m["key"] = 1  // 运行时panic!不能向nil map添加元素
}

Map的基本操作

func main() {
    m := make(map[string]int)

    // 添加/修改元素
    m["apple"] = 5
    m["banana"] = 3
    m["orange"] = 7

    // 访问元素
    fmt.Println(m["apple"])  // 5

    // 检查键是否存在
    value, exists := m["apple"]
    if exists {
        fmt.Println("apple的数量:", value)
    }

    // 访问不存在的键返回零值
    fmt.Println(m["grape"])  // 0

    // 删除元素
    delete(m, "banana")
    fmt.Println(m)  // map[apple:5 orange:7]

    // 获取长度
    fmt.Println("元素个数:", len(m))  // 2
}

遍历Map

func main() {
    scores := map[string]int{
        "Alice":   95,
        "Bob":     88,
        "Charlie": 92,
    }

    // 遍历键值对
    for name, score := range scores {
        fmt.Printf("%s: %d\n", name, score)
    }

    // 只遍历键
    for name := range scores {
        fmt.Println(name)
    }

    // 只遍历值
    for _, score := range scores {
        fmt.Println(score)
    }
}
注意:Map的遍历顺序是随机的!每次运行可能得到不同的顺序。如果需要有序遍历,应该先对键排序。

Map的有序遍历

package main

import (
    "fmt"
    "sort"
)

func main() {
    scores := map[string]int{
        "Alice":   95,
        "Bob":     88,
        "Charlie": 92,
        "David":   85,
    }

    // 提取所有键
    keys := make([]string, 0, len(scores))
    for k := range scores {
        keys = append(keys, k)
    }

    // 对键排序
    sort.Strings(keys)

    // 按排序后的键遍历
    for _, k := range keys {
        fmt.Printf("%s: %d\n", k, scores[k])
    }
}

Map作为函数参数

// Map是引用类型,函数内修改会影响原Map
func modifyMap(m map[string]int) {
    m["new"] = 100
}

func main() {
    scores := map[string]int{"old": 50}
    modifyMap(scores)
    fmt.Println(scores)  // map[new:100 old:50]
}

嵌套Map

func main() {
    // Map的值也可以是Map
    students := map[string]map[string]int{
        "Alice": {
            "math":    95,
            "english": 88,
        },
        "Bob": {
            "math":    85,
            "english": 92,
        },
    }

    fmt.Println(students["Alice"]["math"])  // 95

    // 添加新学生
    students["Charlie"] = make(map[string]int)
    students["Charlie"]["math"] = 90
    students["Charlie"]["english"] = 87
}

结构体(Struct)

结构体是一种聚合数据类型,可以将不同类型的数据组合在一起。

结构体定义与初始化

package main

import "fmt"

// 定义结构体
type Person struct {
    Name string
    Age  int
    City string
}

func main() {
    // 方式1:按字段顺序初始化
    p1 := Person{"Alice", 25, "Beijing"}
    fmt.Println(p1)

    // 方式2:使用字段名初始化(推荐)
    p2 := Person{
        Name: "Bob",
        Age:  30,
        City: "Shanghai",
    }
    fmt.Println(p2)

    // 方式3:部分初始化
    p3 := Person{Name: "Charlie"}
    fmt.Println(p3)  // {Charlie 0 } 未初始化字段为零值

    // 方式4:使用new
    p4 := new(Person)
    p4.Name = "David"
    p4.Age = 28
    fmt.Println(p4)  // &{David 28 }
}

访问和修改结构体字段

func main() {
    person := Person{
        Name: "Alice",
        Age:  25,
        City: "Beijing",
    }

    // 访问字段
    fmt.Println(person.Name)  // Alice
    fmt.Println(person.Age)   // 25

    // 修改字段
    person.Age = 26
    person.City = "Shanghai"
    fmt.Println(person)  // {Alice 26 Shanghai}
}

匿名结构体

func main() {
    // 定义并初始化匿名结构体
    person := struct {
        Name string
        Age  int
    }{
        Name: "Alice",
        Age:  25,
    }

    fmt.Println(person)
}

结构体指针

func main() {
    person := Person{Name: "Alice", Age: 25}

    // 获取指针
    ptr := &person

    // 通过指针访问字段(Go自动解引用)
    fmt.Println(ptr.Name)  // Alice
    ptr.Age = 26
    fmt.Println(person.Age)  // 26

    // 使用new创建指针
    p := new(Person)
    p.Name = "Bob"
    fmt.Println(p)  // &{Bob 0 }
}

结构体嵌套

type Address struct {
    City    string
    Street  string
    ZipCode string
}

type Employee struct {
    Name    string
    Age     int
    Address Address  // 嵌套结构体
}

func main() {
    emp := Employee{
        Name: "Alice",
        Age:  30,
        Address: Address{
            City:    "Beijing",
            Street:  "Changan Street",
            ZipCode: "100000",
        },
    }

    fmt.Println(emp.Name)
    fmt.Println(emp.Address.City)
}

匿名字段(嵌入)

type Person struct {
    Name string
    Age  int
}

type Student struct {
    Person  // 匿名字段(嵌入)
    School string
}

func main() {
    s := Student{
        Person: Person{
            Name: "Alice",
            Age:  20,
        },
        School: "清华大学",
    }

    // 可以直接访问嵌入结构体的字段
    fmt.Println(s.Name)    // Alice
    fmt.Println(s.Age)     // 20
    fmt.Println(s.School)  // 清华大学
}

结构体方法

type Rectangle struct {
    Width  float64
    Height float64
}

// 值接收者方法
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

// 指针接收者方法
func (r *Rectangle) Scale(factor float64) {
    r.Width *= factor
    r.Height *= factor
}

func main() {
    rect := Rectangle{Width: 10, Height: 5}

    fmt.Println("面积:", rect.Area())  // 50

    rect.Scale(2)
    fmt.Println("缩放后:", rect)  // {20 10}
    fmt.Println("新面积:", rect.Area())  // 200
}

结构体标签(Tag)

package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    Name  string `json:"name"`
    Age   int    `json:"age"`
    Email string `json:"email,omitempty"`
}

func main() {
    user := User{
        Name:  "Alice",
        Age:   25,
        Email: "alice@example.com",
    }

    // 序列化为JSON
    jsonData, _ := json.Marshal(user)
    fmt.Println(string(jsonData))
    // {"name":"Alice","age":25,"email":"alice@example.com"}

    // 反序列化
    jsonStr := `{"name":"Bob","age":30}`
    var user2 User
    json.Unmarshal([]byte(jsonStr), &user2)
    fmt.Println(user2)  // {Bob 30 }
}

结构体比较

type Point struct {
    X, Y int
}

func main() {
    p1 := Point{1, 2}
    p2 := Point{1, 2}
    p3 := Point{2, 3}

    fmt.Println(p1 == p2)  // true
    fmt.Println(p1 == p3)  // false

    // 注意:包含不可比较类型(如切片、map)的结构体不能比较
}

综合示例

示例1:学生管理系统

package main

import "fmt"

type Student struct {
    ID     int
    Name   string
    Scores map[string]int
}

func NewStudent(id int, name string) *Student {
    return &Student{
        ID:     id,
        Name:   name,
        Scores: make(map[string]int),
    }
}

func (s *Student) AddScore(subject string, score int) {
    s.Scores[subject] = score
}

func (s *Student) Average() float64 {
    if len(s.Scores) == 0 {
        return 0
    }

    total := 0
    for _, score := range s.Scores {
        total += score
    }

    return float64(total) / float64(len(s.Scores))
}

func main() {
    student := NewStudent(1, "Alice")
    student.AddScore("数学", 95)
    student.AddScore("英语", 88)
    student.AddScore("物理", 92)

    fmt.Printf("学生: %s\n", student.Name)
    fmt.Printf("平均分: %.2f\n", student.Average())
}

示例2:词频统计

package main

import (
    "fmt"
    "strings"
)

func wordCount(text string) map[string]int {
    words := strings.Fields(text)
    counts := make(map[string]int)

    for _, word := range words {
        word = strings.ToLower(word)
        counts[word]++
    }

    return counts
}

func main() {
    text := "Go is great Go is simple Go is fast"
    counts := wordCount(text)

    for word, count := range counts {
        fmt.Printf("%s: %d\n", word, count)
    }
}

示例3:缓存系统

package main

import (
    "fmt"
    "sync"
)

type Cache struct {
    data map[string]string
    mu   sync.RWMutex
}

func NewCache() *Cache {
    return &Cache{
        data: make(map[string]string),
    }
}

func (c *Cache) Set(key, value string) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.data[key] = value
}

func (c *Cache) Get(key string) (string, bool) {
    c.mu.RLock()
    defer c.mu.RUnlock()
    value, exists := c.data[key]
    return value, exists
}

func (c *Cache) Delete(key string) {
    c.mu.Lock()
    defer c.mu.Unlock()
    delete(c.data, key)
}

func main() {
    cache := NewCache()
    cache.Set("name", "Alice")
    cache.Set("age", "25")

    if value, exists := cache.Get("name"); exists {
        fmt.Println("name:", value)
    }

    cache.Delete("age")
}

示例4:配置管理

package main

import "fmt"

type Config struct {
    Host     string
    Port     int
    Database string
    Options  map[string]interface{}
}

func NewConfig() *Config {
    return &Config{
        Host:     "localhost",
        Port:     3306,
        Database: "mydb",
        Options:  make(map[string]interface{}),
    }
}

func (c *Config) SetOption(key string, value interface{}) {
    c.Options[key] = value
}

func (c *Config) GetOption(key string) (interface{}, bool) {
    value, exists := c.Options[key]
    return value, exists
}

func main() {
    config := NewConfig()
    config.SetOption("timeout", 30)
    config.SetOption("maxConnections", 100)
    config.SetOption("ssl", true)

    fmt.Printf("配置: %+v\n", config)
}
Map vs 结构体选择:

实践练习

  1. 实现一个电话簿程序,使用Map存储姓名和电话号码
  2. 创建一个图书结构体,包含书名、作者、价格等字段,并实现打折方法
  3. 编写程序,统计一段文本中每个字符出现的次数
  4. 实现一个简单的购物车系统(使用结构体和Map)
  5. 创建一个员工管理系统,支持添加、删除、查询员工信息
  6. 实现Map的深拷贝函数
  7. 编写程序,合并两个Map
  8. 创建一个配置文件解析器(使用嵌套结构体)
常见陷阱: