Files
golang/golang-learning/02-control-flow/04-range.go
2025-08-24 01:01:26 +08:00

698 lines
16 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
04-range.go - Go 语言 range 关键字详解
学习目标:
1. 掌握 range 的基本用法
2. 理解 range 在不同数据类型上的应用
3. 学会处理 range 返回的索引和值
4. 了解 range 的性能特点
5. 掌握 range 的实际应用场景
知识点:
- range 遍历数组和切片
- range 遍历字符串
- range 遍历映射 (map)
- range 遍历通道 (channel)
- range 的值拷贝特性
- 忽略索引或值的技巧
- range 的性能考虑
*/
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
fmt.Println("=== Go 语言 range 关键字详解 ===\n")
// 演示 range 遍历数组和切片
demonstrateRangeArraySlice()
// 演示 range 遍历字符串
demonstrateRangeString()
// 演示 range 遍历映射
demonstrateRangeMap()
// 演示 range 遍历通道
demonstrateRangeChannel()
// 演示 range 的值拷贝特性
demonstrateRangeValueCopy()
// 演示忽略索引或值
demonstrateIgnoreIndexValue()
// 演示 range 的实际应用
demonstratePracticalExamples()
// 演示 range 的性能考虑
demonstratePerformanceConsiderations()
}
// demonstrateRangeArraySlice 演示 range 遍历数组和切片
func demonstrateRangeArraySlice() {
fmt.Println("1. range 遍历数组和切片:")
// 遍历数组
fmt.Printf(" 遍历数组:\n")
arr := [5]int{10, 20, 30, 40, 50}
for index, value := range arr {
fmt.Printf(" arr[%d] = %d\n", index, value)
}
// 遍历切片
fmt.Printf(" 遍历切片:\n")
slice := []string{"Go", "Python", "Java", "JavaScript"}
for i, lang := range slice {
fmt.Printf(" 语言 %d: %s\n", i+1, lang)
}
// 遍历空切片
fmt.Printf(" 遍历空切片:\n")
var emptySlice []int
count := 0
for range emptySlice {
count++
}
fmt.Printf(" 空切片遍历次数: %d\n", count)
// 遍历 nil 切片
fmt.Printf(" 遍历 nil 切片:\n")
var nilSlice []int = nil
count = 0
for range nilSlice {
count++
}
fmt.Printf(" nil 切片遍历次数: %d\n", count)
// 多维切片遍历
fmt.Printf(" 遍历二维切片:\n")
matrix := [][]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
for i, row := range matrix {
for j, value := range row {
fmt.Printf(" matrix[%d][%d] = %d\n", i, j, value)
}
}
fmt.Println()
}
// demonstrateRangeString 演示 range 遍历字符串
func demonstrateRangeString() {
fmt.Println("2. range 遍历字符串:")
// 基本字符串遍历
fmt.Printf(" 遍历 ASCII 字符串:\n")
str1 := "Hello"
for index, char := range str1 {
fmt.Printf(" str1[%d] = '%c' (Unicode: %d)\n", index, char, char)
}
// 遍历包含中文的字符串
fmt.Printf(" 遍历包含中文的字符串:\n")
str2 := "Hello世界"
for index, char := range str2 {
fmt.Printf(" 位置 %d: '%c' (Unicode: %d)\n", index, char, char)
}
// 注意index 是字节位置,不是字符位置
fmt.Printf(" 注意: range 返回的索引是字节位置,不是字符位置\n")
// 字符串长度对比
fmt.Printf(" 字符串长度对比:\n")
fmt.Printf(" 字节长度 len(\"%s\"): %d\n", str2, len(str2))
fmt.Printf(" 字符长度 utf8.RuneCountInString(\"%s\"): %d\n",
str2, utf8.RuneCountInString(str2))
// 遍历字符串的字节
fmt.Printf(" 按字节遍历字符串:\n")
for i := 0; i < len(str2); i++ {
fmt.Printf(" 字节 %d: %d ('%c')\n", i, str2[i], str2[i])
if i >= 7 { // 限制输出
fmt.Printf(" ...\n")
break
}
}
// 统计字符类型
fmt.Printf(" 统计字符类型:\n")
text := "Hello, 世界! 123"
ascii, unicode, digit, other := 0, 0, 0, 0
for _, char := range text {
switch {
case char >= 'A' && char <= 'Z' || char >= 'a' && char <= 'z':
ascii++
case char >= '0' && char <= '9':
digit++
case char > 127:
unicode++
default:
other++
}
}
fmt.Printf(" 文本: \"%s\"\n", text)
fmt.Printf(" ASCII字母: %d, Unicode字符: %d, 数字: %d, 其他: %d\n",
ascii, unicode, digit, other)
fmt.Println()
}
// demonstrateRangeMap 演示 range 遍历映射
func demonstrateRangeMap() {
fmt.Println("3. range 遍历映射 (map):")
// 基本映射遍历
fmt.Printf(" 遍历基本映射:\n")
scores := map[string]int{
"Alice": 95,
"Bob": 87,
"Charlie": 92,
"David": 78,
}
for name, score := range scores {
fmt.Printf(" %s: %d分\n", name, score)
}
// 注意:映射遍历顺序是随机的
fmt.Printf(" 注意: 映射的遍历顺序是随机的\n")
// 遍历复杂映射
fmt.Printf(" 遍历复杂映射:\n")
students := map[string]map[string]interface{}{
"student1": {
"name": "张三",
"age": 20,
"grade": "A",
},
"student2": {
"name": "李四",
"age": 21,
"grade": "B",
},
}
for id, info := range students {
fmt.Printf(" 学生ID: %s\n", id)
for key, value := range info {
fmt.Printf(" %s: %v\n", key, value)
}
}
// 统计映射信息
fmt.Printf(" 统计映射信息:\n")
wordCount := map[string]int{
"go": 10,
"python": 8,
"java": 15,
"rust": 5,
}
totalWords := 0
maxCount := 0
mostPopular := ""
for word, count := range wordCount {
totalWords += count
if count > maxCount {
maxCount = count
mostPopular = word
}
}
fmt.Printf(" 词汇统计: %v\n", wordCount)
fmt.Printf(" 总词数: %d\n", totalWords)
fmt.Printf(" 最受欢迎的词: %s (%d次)\n", mostPopular, maxCount)
// 遍历空映射
fmt.Printf(" 遍历空映射:\n")
emptyMap := make(map[string]int)
count := 0
for range emptyMap {
count++
}
fmt.Printf(" 空映射遍历次数: %d\n", count)
fmt.Println()
}
// demonstrateRangeChannel 演示 range 遍历通道
func demonstrateRangeChannel() {
fmt.Println("4. range 遍历通道 (channel):")
fmt.Printf(" 基本通道遍历:\n")
// 创建一个通道并发送数据
ch := make(chan int, 5)
// 发送数据到通道
for i := 1; i <= 5; i++ {
ch <- i * 10
}
close(ch) // 关闭通道range 才能正常结束
// 使用 range 遍历通道
for value := range ch {
fmt.Printf(" 从通道接收: %d\n", value)
}
// 字符串通道示例
fmt.Printf(" 字符串通道遍历:\n")
strCh := make(chan string, 3)
messages := []string{"Hello", "World", "Go"}
for _, msg := range messages {
strCh <- msg
}
close(strCh)
for message := range strCh {
fmt.Printf(" 消息: %s\n", message)
}
// 注意如果通道没有关闭range 会一直等待
fmt.Printf(" 注意: 通道必须关闭,否则 range 会一直等待\n")
fmt.Println()
}
// demonstrateRangeValueCopy 演示 range 的值拷贝特性
func demonstrateRangeValueCopy() {
fmt.Println("5. range 的值拷贝特性:")
fmt.Printf(" range 会拷贝值,修改拷贝不会影响原始数据:\n")
// 结构体切片示例
type Person struct {
Name string
Age int
}
people := []Person{
{"Alice", 25},
{"Bob", 30},
{"Charlie", 35},
}
fmt.Printf(" 原始数据:\n")
for i, person := range people {
fmt.Printf(" people[%d]: %+v\n", i, person)
}
// 尝试修改 range 返回的值(不会影响原始数据)
fmt.Printf(" 尝试修改 range 返回的值:\n")
for i, person := range people {
person.Age += 10 // 这不会修改原始数据
fmt.Printf(" 修改后的拷贝 people[%d]: %+v\n", i, person)
}
fmt.Printf(" 原始数据(未改变):\n")
for i, person := range people {
fmt.Printf(" people[%d]: %+v\n", i, person)
}
// 正确的修改方式:使用索引
fmt.Printf(" 正确的修改方式(使用索引):\n")
for i := range people {
people[i].Age += 5
}
for i, person := range people {
fmt.Printf(" people[%d]: %+v\n", i, person)
}
// 指针切片的情况
fmt.Printf(" 指针切片的情况:\n")
ptrPeople := []*Person{
{"David", 28},
{"Eve", 32},
}
// 通过指针修改(会影响原始数据)
for _, personPtr := range ptrPeople {
personPtr.Age += 2
}
for i, personPtr := range ptrPeople {
fmt.Printf(" ptrPeople[%d]: %+v\n", i, *personPtr)
}
fmt.Println()
}
// demonstrateIgnoreIndexValue 演示忽略索引或值
func demonstrateIgnoreIndexValue() {
fmt.Println("6. 忽略索引或值:")
// 只需要值,忽略索引
fmt.Printf(" 只需要值,忽略索引(使用 _ :\n")
numbers := []int{1, 4, 9, 16, 25}
sum := 0
for _, value := range numbers {
sum += value
}
fmt.Printf(" 数组 %v 的和: %d\n", numbers, sum)
// 只需要索引,忽略值
fmt.Printf(" 只需要索引,忽略值:\n")
items := []string{"apple", "banana", "cherry", "date"}
for index := range items {
fmt.Printf(" 索引 %d\n", index)
}
// 既不需要索引也不需要值(只是计数)
fmt.Printf(" 只计算元素个数:\n")
data := []float64{1.1, 2.2, 3.3, 4.4, 5.5}
count := 0
for range data {
count++
}
fmt.Printf(" 元素个数: %d\n", count)
// 映射中忽略键或值
fmt.Printf(" 映射中忽略键或值:\n")
grades := map[string]int{
"Math": 90,
"English": 85,
"Science": 92,
}
// 只要值
fmt.Printf(" 所有分数: ")
for _, score := range grades {
fmt.Printf("%d ", score)
}
fmt.Printf("\n")
// 只要键
fmt.Printf(" 所有科目: ")
for subject := range grades {
fmt.Printf("%s ", subject)
}
fmt.Printf("\n")
fmt.Println()
}
// demonstratePracticalExamples 演示 range 的实际应用
func demonstratePracticalExamples() {
fmt.Println("7. range 的实际应用:")
// 示例1: 数据统计
fmt.Printf(" 示例1 - 销售数据统计:\n")
sales := map[string][]int{
"Q1": {100, 120, 110, 130},
"Q2": {140, 135, 150, 145},
"Q3": {160, 155, 170, 165},
"Q4": {180, 175, 190, 185},
}
yearTotal := 0
for quarter, monthlySales := range sales {
quarterTotal := 0
for _, monthSale := range monthlySales {
quarterTotal += monthSale
}
yearTotal += quarterTotal
fmt.Printf(" %s 季度总销售额: %d\n", quarter, quarterTotal)
}
fmt.Printf(" 全年总销售额: %d\n", yearTotal)
// 示例2: 配置文件处理
fmt.Printf(" 示例2 - 配置文件处理:\n")
config := map[string]interface{}{
"server_port": 8080,
"debug_mode": true,
"database_url": "localhost:5432",
"max_connections": 100,
"timeout": 30.5,
}
for key, value := range config {
fmt.Printf(" 配置项 %s: ", key)
switch v := value.(type) {
case int:
fmt.Printf("%d (整数)\n", v)
case bool:
fmt.Printf("%t (布尔值)\n", v)
case string:
fmt.Printf("\"%s\" (字符串)\n", v)
case float64:
fmt.Printf("%.1f (浮点数)\n", v)
default:
fmt.Printf("%v (未知类型)\n", v)
}
}
// 示例3: 文本处理
fmt.Printf(" 示例3 - 单词频率统计:\n")
text := "go is great go is simple go is fast"
words := []string{"go", "is", "great", "go", "is", "simple", "go", "is", "fast"}
wordFreq := make(map[string]int)
for _, word := range words {
wordFreq[word]++
}
fmt.Printf(" 文本: \"%s\"\n", text)
fmt.Printf(" 词频统计:\n")
for word, freq := range wordFreq {
fmt.Printf(" \"%s\": %d次\n", word, freq)
}
// 示例4: 数据验证
fmt.Printf(" 示例4 - 用户数据验证:\n")
users := []map[string]string{
{"name": "Alice", "email": "alice@example.com", "age": "25"},
{"name": "", "email": "bob@test.com", "age": "30"},
{"name": "Charlie", "email": "invalid-email", "age": "abc"},
{"name": "David", "email": "david@domain.org", "age": "28"},
}
validUsers := 0
for i, user := range users {
fmt.Printf(" 用户 %d: ", i+1)
isValid := true
// 验证姓名
if user["name"] == "" {
fmt.Printf("姓名为空 ")
isValid = false
}
// 验证邮箱(简单验证)
email := user["email"]
if !contains(email, "@") || !contains(email, ".") {
fmt.Printf("邮箱无效 ")
isValid = false
}
// 验证年龄
age := user["age"]
if !isNumeric(age) {
fmt.Printf("年龄无效 ")
isValid = false
}
if isValid {
fmt.Printf("✓ 有效用户\n")
validUsers++
} else {
fmt.Printf("✗ 无效用户\n")
}
}
fmt.Printf(" 有效用户数: %d/%d\n", validUsers, len(users))
// 示例5: 矩阵操作
fmt.Printf(" 示例5 - 矩阵转置:\n")
matrix := [][]int{
{1, 2, 3},
{4, 5, 6},
}
fmt.Printf(" 原矩阵:\n")
for i, row := range matrix {
fmt.Printf(" 行 %d: %v\n", i, row)
}
// 创建转置矩阵
if len(matrix) > 0 {
transposed := make([][]int, len(matrix[0]))
for i := range transposed {
transposed[i] = make([]int, len(matrix))
}
for i, row := range matrix {
for j, value := range row {
transposed[j][i] = value
}
}
fmt.Printf(" 转置矩阵:\n")
for i, row := range transposed {
fmt.Printf(" 行 %d: %v\n", i, row)
}
}
fmt.Println()
}
// demonstratePerformanceConsiderations 演示 range 的性能考虑
func demonstratePerformanceConsiderations() {
fmt.Println("8. range 的性能考虑:")
// 大切片的遍历
fmt.Printf(" 大切片的遍历性能:\n")
largeSlice := make([]int, 1000)
for i := range largeSlice {
largeSlice[i] = i
}
// 使用 range推荐
sum1 := 0
for _, value := range largeSlice {
sum1 += value
if sum1 > 100 { // 提前退出演示
break
}
}
fmt.Printf(" 使用 range 遍历(推荐): 部分和 = %d\n", sum1)
// 使用传统 for 循环
sum2 := 0
for i := 0; i < len(largeSlice); i++ {
sum2 += largeSlice[i]
if sum2 > 100 {
break
}
}
fmt.Printf(" 使用传统 for 循环: 部分和 = %d\n", sum2)
// 字符串遍历的性能
fmt.Printf(" 字符串遍历的性能考虑:\n")
longString := "这是一个包含中文字符的长字符串,用于演示遍历性能"
// 使用 range处理 Unicode 正确)
charCount1 := 0
for range longString {
charCount1++
}
fmt.Printf(" 使用 range 统计字符数: %d\n", charCount1)
// 使用 len字节数不是字符数
byteCount := len(longString)
fmt.Printf(" 使用 len 统计字节数: %d\n", byteCount)
// 使用 utf8.RuneCountInString正确的字符数
charCount2 := utf8.RuneCountInString(longString)
fmt.Printf(" 使用 utf8.RuneCountInString 统计字符数: %d\n", charCount2)
// 映射遍历的注意事项
fmt.Printf(" 映射遍历的注意事项:\n")
largeMap := make(map[int]string)
for i := 0; i < 10; i++ {
largeMap[i] = fmt.Sprintf("value_%d", i)
}
fmt.Printf(" 映射大小: %d\n", len(largeMap))
fmt.Printf(" 映射遍历顺序是随机的,每次运行可能不同\n")
// 显示前几个元素
count := 0
for key, value := range largeMap {
fmt.Printf(" %d: %s\n", key, value)
count++
if count >= 3 {
fmt.Printf(" ...\n")
break
}
}
fmt.Println()
}
// 辅助函数
func contains(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}
func isNumeric(s string) bool {
if len(s) == 0 {
return false
}
for _, char := range s {
if char < '0' || char > '9' {
return false
}
}
return true
}
/*
运行这个程序:
go run 04-range.go
学习要点:
1. range 是 Go 中遍历集合类型的标准方式
2. range 可以用于数组、切片、字符串、映射和通道
3. range 返回索引/键和值,可以用 _ 忽略不需要的部分
4. range 会拷贝值,修改拷贝不会影响原始数据
5. 字符串的 range 遍历按 Unicode 字符,索引是字节位置
range 的语法:
- for index, value := range collection { ... }
- for index := range collection { ... } // 只要索引/键
- for _, value := range collection { ... } // 只要值
- for range collection { ... } // 只计数
不同类型的 range
1. 数组/切片: 返回索引和值
2. 字符串: 返回字节位置和 Unicode 字符
3. 映射: 返回键和值(顺序随机)
4. 通道: 返回接收到的值(通道需要关闭)
性能考虑:
1. range 通常比传统 for 循环更高效和安全
2. 字符串的 range 正确处理 Unicode 字符
3. 大集合的遍历考虑提前退出
4. 映射遍历顺序是随机的
最佳实践:
1. 优先使用 range 遍历集合
2. 使用 _ 忽略不需要的返回值
3. 注意 range 的值拷贝特性
4. 字符串处理时考虑 Unicode 字符
5. 通道遍历前确保通道会被关闭
常见应用场景:
1. 数据处理和统计
2. 配置文件解析
3. 文本分析
4. 数据验证
5. 矩阵操作
6. 集合操作
*/