Skip to content

Go 语言范围(Range)

range 是 Go 语言中用于遍历各种数据结构的关键字。它提供了一种简洁、统一的方式来迭代数组、切片、字符串、映射和通道。

📋 Range 基础语法

基本语法格式

go
// 基本格式
for index, value := range collection {
    // 处理 index 和 value
}

// 只需要索引
for index := range collection {
    // 只处理 index
}

// 只需要值
for _, value := range collection {
    // 只处理 value,使用 _ 忽略索引
}

// 只迭代,不需要索引和值
for range collection {
    // 只是迭代,不使用索引和值
}

📚 遍历数组和切片

遍历数组

go
package main

import "fmt"

func main() {
    // 定义数组
    numbers := [5]int{10, 20, 30, 40, 50}
    fruits := [3]string{"苹果", "香蕉", "橙子"}
    
    // 完整遍历:索引和值
    fmt.Println("=== 遍历数字数组 ===")
    for index, value := range numbers {
        fmt.Printf("索引: %d, 值: %d\n", index, value)
    }
    
    // 只遍历值
    fmt.Println("\n=== 只遍历值 ===")
    for _, fruit := range fruits {
        fmt.Printf("水果: %s\n", fruit)
    }
    
    // 计算数组元素总和
    sum := 0
    for _, num := range numbers {
        sum += num
    }
    fmt.Printf("\n数组总和: %d\n", sum)
    
    // 查找特定元素
    target := 30
    for i, num := range numbers {
        if num == target {
            fmt.Printf("找到 %d 在索引 %d\n", target, i)
            break
        }
    }
}

遍历切片

go
package main

import "fmt"

func main() {
    // 创建切片
    slice1 := []int{1, 2, 3, 4, 5}
    slice2 := []string{"Go", "Python", "Java"}
    
    // 遍历整数切片
    fmt.Println("=== 遍历整数切片 ===")
    for i, v := range slice1 {
        fmt.Printf("slice1[%d] = %d\n", i, v)
    }
    
    // 动态修改切片
    fmt.Println("\n=== 修改切片元素 ===")
    for i := range slice1 {
        slice1[i] = slice1[i] * 2 // 将每个元素乘以2
    }
    
    fmt.Printf("修改后的切片: %v\n", slice1)
    
    // 过滤切片元素
    var evenNumbers []int
    for _, num := range slice1 {
        if num%4 == 0 { // 找出能被4整除的数
            evenNumbers = append(evenNumbers, num)
        }
    }
    fmt.Printf("能被4整除的数: %v\n", evenNumbers)
}

🔤 遍历字符串

字符串遍历

go
package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {
    // 英文字符串
    english := "Hello"
    fmt.Println("=== 遍历英文字符串 ===")
    for i, char := range english {
        fmt.Printf("索引: %d, 字符: %c, Unicode: %U\n", i, char, char)
    }
    
    // 中文字符串
    chinese := "你好世界"
    fmt.Println("\n=== 遍历中文字符串 ===")
    for i, char := range chinese {
        fmt.Printf("字节索引: %d, 字符: %c, Unicode: %U\n", i, char, char)
    }
    
    // 字符串统计
    text := "Go语言"
    fmt.Printf("\n字符串: %s\n", text)
    fmt.Printf("字节长度: %d\n", len(text))
    fmt.Printf("字符长度: %d\n", utf8.RuneCountInString(text))
    
    // 查找特定字符
    target := ''
    for i, char := range text {
        if char == target {
            fmt.Printf("找到字符 '%c' 在字节位置 %d\n", target, i)
            break
        }
    }
}

字符串处理示例

go
package main

import (
    "fmt"
    "unicode"
)

// 统计字符串中各种字符的数量
func analyzeString(s string) {
    var letters, digits, spaces int
    
    for _, char := range s {
        switch {
        case unicode.IsLetter(char):
            letters++
        case unicode.IsDigit(char):
            digits++
        case unicode.IsSpace(char):
            spaces++
        }
    }
    
    fmt.Printf("字符串: \"%s\"\n", s)
    fmt.Printf("字母: %d, 数字: %d, 空格: %d\n", letters, digits, spaces)
}

// 反转字符串(支持中文)
func reverseString(s string) string {
    runes := []rune(s)
    for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
        runes[i], runes[j] = runes[j], runes[i]
    }
    return string(runes)
}

func main() {
    // 字符串分析
    analyzeString("Hello World 123!")
    analyzeString("Go语言编程 2023年")
    
    // 字符串反转
    fmt.Println("\n=== 字符串反转 ===")
    texts := []string{"Hello", "Go语言", "12345"}
    for _, text := range texts {
        reversed := reverseString(text)
        fmt.Printf("原文: %s -> 反转: %s\n", text, reversed)
    }
}

🗺️ 遍历映射(Map)

基本映射遍历

go
package main

import (
    "fmt"
    "sort"
)

func main() {
    // 创建映射
    scores := map[string]int{
        "Alice":   95,
        "Bob":     87,
        "Charlie": 92,
        "Diana":   98,
    }
    
    // 基本遍历:键和值
    fmt.Println("=== 遍历学生成绩 ===")
    for name, score := range scores {
        fmt.Printf("学生: %s, 成绩: %d\n", name, score)
    }
    
    // 计算统计信息
    var total, count int
    var highest int
    var topStudent string
    
    for name, score := range scores {
        total += score
        count++
        
        if score > highest {
            highest = score
            topStudent = name
        }
    }
    
    average := float64(total) / float64(count)
    fmt.Printf("\n总分: %d, 平均分: %.2f\n", total, average)
    fmt.Printf("最高分: %d (%s)\n", highest, topStudent)
    
    // 按键排序遍历
    fmt.Println("\n=== 按姓名排序 ===")
    var names []string
    for name := range scores {
        names = append(names, name)
    }
    sort.Strings(names)
    
    for _, name := range names {
        fmt.Printf("学生: %s, 成绩: %d\n", name, scores[name])
    }
}

📡 遍历通道(Channel)

通道遍历

go
package main

import (
    "fmt"
    "time"
)

func main() {
    // 创建通道
    ch := make(chan int, 5)
    
    // 发送数据到通道
    go func() {
        for i := 1; i <= 5; i++ {
            ch <- i
            fmt.Printf("发送: %d\n", i)
            time.Sleep(100 * time.Millisecond)
        }
        close(ch) // 关闭通道
    }()
    
    // 使用 range 遍历通道
    fmt.Println("=== 遍历通道 ===")
    for value := range ch {
        fmt.Printf("接收: %d\n", value)
    }
    
    fmt.Println("通道已关闭,遍历结束")
}

🎯 Range 的陷阱和注意事项

值拷贝陷阱

go
package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

func main() {
    people := []Person{
        {"Alice", 25},
        {"Bob", 30},
    }
    
    fmt.Println("=== 值拷贝陷阱 ===")
    
    // 错误的做法:尝试修改拷贝的值
    fmt.Println("错误方式(修改拷贝):")
    for _, person := range people {
        person.Age += 1 // 修改的是拷贝,不是原始数据
    }
    
    fmt.Println("原始数据未改变:")
    for _, person := range people {
        fmt.Printf("%s, 年龄: %d\n", person.Name, person.Age)
    }
    
    // 正确的做法:使用索引修改
    fmt.Println("\n正确方式(使用索引):")
    for i := range people {
        people[i].Age += 1
    }
    
    fmt.Println("原始数据已改变:")
    for _, person := range people {
        fmt.Printf("%s, 年龄: %d\n", person.Name, person.Age)
    }
}

闭包陷阱

go
package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println("=== 闭包陷阱 ===")
    
    // 错误的做法:闭包捕获循环变量
    var funcs []func()
    
    for _, name := range []string{"Alice", "Bob", "Charlie"} {
        funcs = append(funcs, func() {
            fmt.Printf("错误方式: %s\n", name) // 所有闭包都会打印最后一个值
        })
    }
    
    fmt.Println("错误的闭包结果:")
    for _, f := range funcs {
        f()
    }
    
    // 正确的做法:传递参数
    funcs = nil
    for _, name := range []string{"Alice", "Bob", "Charlie"} {
        funcs = append(funcs, func(n string) func() {
            return func() {
                fmt.Printf("正确方式: %s\n", n)
            }
        }(name))
    }
    
    fmt.Println("\n正确的闭包结果:")
    for _, f := range funcs {
        f()
    }
    
    // Goroutine 闭包陷阱
    fmt.Println("\nGoroutine 中的应用:")
    
    // 正确方式:传递参数给 goroutine
    for _, name := range []string{"Alice", "Bob", "Charlie"} {
        go func(n string) {
            time.Sleep(10 * time.Millisecond)
            fmt.Printf("Goroutine: %s\n", n)
        }(name)
    }
    
    time.Sleep(50 * time.Millisecond)
}

🛠️ Range 实际应用示例

数据统计分析

go
package main

import "fmt"

// 学生成绩统计
func analyzeScores(scores map[string]int) {
    if len(scores) == 0 {
        fmt.Println("没有成绩数据")
        return
    }
    
    var total, count int
    var max, min int
    var maxStudent, minStudent string
    
    // 初始化最大最小值
    first := true
    for name, score := range scores {
        if first {
            max, min = score, score
            maxStudent, minStudent = name, name
            first = false
        }
        
        total += score
        count++
        
        if score > max {
            max = score
            maxStudent = name
        }
        if score < min {
            min = score
            minStudent = name
        }
    }
    
    average := float64(total) / float64(count)
    
    fmt.Println("=== 成绩统计 ===")
    fmt.Printf("学生人数: %d\n", count)
    fmt.Printf("平均分: %.2f\n", average)
    fmt.Printf("最高分: %d (%s)\n", max, maxStudent)
    fmt.Printf("最低分: %d (%s)\n", min, minStudent)
    
    // 分级统计
    excellent, good, pass, fail := 0, 0, 0, 0
    for _, score := range scores {
        switch {
        case score >= 90:
            excellent++
        case score >= 80:
            good++
        case score >= 60:
            pass++
        default:
            fail++
        }
    }
    
    fmt.Printf("优秀(90+): %d\n", excellent)
    fmt.Printf("良好(80-89): %d\n", good)
    fmt.Printf("及格(60-79): %d\n", pass)
    fmt.Printf("不及格(<60): %d\n", fail)
}

// 单词频率统计
func wordFrequency(text string) map[rune]int {
    frequency := make(map[rune]int)
    
    for _, char := range text {
        if char != ' ' { // 忽略空格
            frequency[char]++
        }
    }
    
    return frequency
}

func main() {
    // 成绩统计示例
    scores := map[string]int{
        "张三": 95,
        "李四": 87,
        "王五": 92,
        "赵六": 78,
        "钱七": 96,
    }
    
    analyzeScores(scores)
    
    // 字符频率统计
    fmt.Println("\n=== 字符频率统计 ===")
    text := "Hello World Go语言"
    freq := wordFrequency(text)
    
    fmt.Printf("文本: %s\n", text)
    fmt.Println("字符频率:")
    for char, count := range freq {
        fmt.Printf("  '%c': %d\n", char, count)
    }
}

🎓 小结

本章我们全面学习了 Go 语言的 range 关键字:

  • 基础语法:for-range 的各种使用形式
  • 数据遍历:数组、切片、字符串、映射、通道
  • 字符串处理:Unicode 字符遍历和处理
  • 常见陷阱:值拷贝和闭包陷阱
  • 实际应用:数据统计分析案例

range 是 Go 语言中非常重要和常用的特性,掌握其正确使用方法对于编写高效的 Go 代码至关重要。


接下来,我们将学习 Go 语言递归函数,探索递归编程的强大功能。

Range 使用建议

  • 注意值拷贝问题,需要修改原数据时使用索引
  • 避免在闭包中直接引用循环变量
  • 遍历字符串时注意 Unicode 字符的字节索引
  • 合理使用 range 提高代码可读性

本站内容仅供学习和研究使用。