Go学习第7天:Map集合 + 递归函数 + 类型转换

Go 语言:Map集合、递归函数、类型转换

  • [一、Map 集合](#一、Map 集合)
    • [1.1 核心特性](#1.1 核心特性)
    • [1.2 Map 创建方式](#1.2 Map 创建方式)
      • [方式1:make 函数创建(工程首选)](#方式1:make 函数创建(工程首选))
      • 方式2:字面量直接初始化
      • [方式3:仅声明(nil Map)](#方式3:仅声明(nil Map))
    • [1.3 Map 基础操作](#1.3 Map 基础操作)
      • [1. 增 / 改 元素](#1. 增 / 改 元素)
      • [2. 查询元素](#2. 查询元素)
      • [3. 获取长度](#3. 获取长度)
      • [4. 遍历 Map](#4. 遍历 Map)
      • [5. 删除元素](#5. 删除元素)
    • [1.4 Map 高频踩坑](#1.4 Map 高频踩坑)
  • 二、递归函数
    • [2.1 递归两大必要条件](#2.1 递归两大必要条件)
    • [2.2 基础语法](#2.2 基础语法)
    • [2.3 经典递归示例](#2.3 经典递归示例)
    • [2.4 递归优缺点 & 对比迭代](#2.4 递归优缺点 & 对比迭代)
    • [2.5 递归高频踩坑](#2.5 递归高频踩坑)
  • 三、类型转换
    • [3.1 基础数值类型转换](#3.1 基础数值类型转换)
    • [3.2 字符串与基本类型互转](#3.2 字符串与基本类型互转)
      • [1. 字符串 ↔ 整型](#1. 字符串 ↔ 整型)
      • [2. 字符串 ↔ 浮点型](#2. 字符串 ↔ 浮点型)
      • 踩坑
    • [3.3 接口类型转换(类型断言)](#3.3 接口类型转换(类型断言))
    • [3.4 类型转换通用踩坑](#3.4 类型转换通用踩坑)
  • 四、知识点速记

延续前文学习体系,本文讲解 Map 集合、递归函数、类型转换 三大核心知识点,每部分包含 语法说明、完整示例、核心规则、高频踩坑 ,代码可直接在 VSCode + Go Modules 环境运行。

目录

  1. Map 集合
  2. 递归函数
  3. 类型转换
  4. 综合练习 & 知识点速记

一、Map 集合

Map 是 Go 语言无序键值对(key-value)集合 ,属于引用类型 ,底层采用哈希表实现。通过 key 快速查询、修改、删除对应 value,是开发高频容器。

1.1 核心特性

  1. 无序性:遍历 Map 时,键值对输出顺序不固定,和写入顺序无关;
  2. 键唯一性 :同一个 Map 中 key 不可重复,重复赋值会覆盖原有 value;
  3. 零值规则:查询不存在的 key,返回 value 对应类型的零值;
  4. 引用类型:Map 赋值、函数传参时,多个变量指向同一底层数据,修改互相影响;
  5. 键类型限制key 必须是可比较类型(整型、字符串、布尔、数组等),切片、Map、函数不可作为 key

1.2 Map 创建方式

方式1:make 函数创建(工程首选)

语法:

go 复制代码
make(map[键类型]值类型 [初始容量])
  • 初始容量为可选参数,达到容量后 Map 自动扩容;
  • 仅声明未使用 make 初始化的 Map 为 nil无法直接增删元素

示例:

go 复制代码
package main
import "fmt"

func main() {
    // 无初始容量
    m1 := make(map[string]int)
    // 指定初始容量 10
    m2 := make(map[string]int, 10)

    m1["苹果"] = 1
    fmt.Println(m1)
}

方式2:字面量直接初始化

适合已知初始键值对的场景:

go 复制代码
package main
import "fmt"

func main() {
    // 初始化并赋值
    m := map[string]int{
        "apple":  1,
        "banana": 2,
        "orange": 3,
    }
    fmt.Println(m)
}

方式3:仅声明(nil Map)

go 复制代码
var m map[string]string // 默认为 nil,不能直接存数据
// m["test"] = "123"  // 运行报错

1.3 Map 基础操作

1. 增 / 改 元素

直接通过 map[key] = value 赋值:

  • key 不存在 → 新增键值对;
  • key 已存在 → 覆盖原有 value。
go 复制代码
siteMap := make(map[string]string)
siteMap["Runoob"] = "菜鸟教程" // 新增
siteMap["Runoob"] = "新名称"   // 修改覆盖

2. 查询元素

两种写法:

  1. 单返回值:直接取值,key 不存在返回零值,无法判断 key 是否存在
  2. 双返回值:value, ok := map[key]ok 为布尔值,true 表示 key 存在。
go 复制代码
package main
import "fmt"

func main() {
    m := map[string]int{"a": 10}
    // 写法1:单值获取
    v1 := m["a"]
    fmt.Println(v1)

    // 写法2:判断键是否存在(推荐)
    v2, ok := m["b"]
    if ok {
        fmt.Println("存在,值:", v2)
    } else {
        fmt.Println("键不存在")
    }
}

3. 获取长度

使用 len(map) 获取当前键值对总数:

go 复制代码
m := map[string]int{"a":1, "b":2}
fmt.Println(len(m)) // 输出 2

4. 遍历 Map

搭配 for-range 遍历,遍历顺序随机:

go 复制代码
package main
import "fmt"

func main() {
    m := map[string]string{
        "France": "巴黎",
        "Japan":  "东京",
    }
    // 遍历 key + value
    for k, v := range m {
        fmt.Printf("%s => %s\n", k, v)
    }
    // 只遍历 key
    for k := range m {
        fmt.Println(k)
    }
}

5. 删除元素

使用内置函数 delete(map, key)key 不存在不会报错

go 复制代码
package main
import "fmt"

func main() {
    m := map[string]string{"a": "111", "b": "222"}
    delete(m, "a") // 删除键 a
    fmt.Println(m)
    delete(m, "c") // 键不存在,无任何异常
}

1.4 Map 高频踩坑

  1. nil Map 直接赋值var m map[k]v 声明后未 make,直接 m[k]=v 运行 panic;
  2. 混淆"零值"和"真实值" :仅单值查询时,零值无法区分 key 不存在 & value 本身就是零值,必须用 ok 判断;
  3. key 类型非法:切片、Map、函数不能作为 Map 的键;
  4. 遍历中增删元素:遍历 Map 时修改/删除元素,会导致遍历结果异常;
  5. 引用传递特性:Map 传参/赋值后,多个变量共享底层数据,一处修改全局生效;
  6. Map 不支持下标遍历 :不能使用 for i := 0; i<len(m);i++ 方式遍历。

二、递归函数

递归是函数直接或间接调用自身的编程方式,适合拆解为重复子问题的场景(阶乘、数列、目录遍历等)。

2.1 递归两大必要条件

  1. 基准条件(终止条件) :递归停止的出口,必须设置,否则无限递归、栈溢出;
  2. 递归体:函数调用自身,将大问题拆解为同类型小问题。

2.2 基础语法

go 复制代码
func 函数名(参数) 返回值 {
    // 1. 基准条件:终止递归
    if 终止判断 {
        return 结果
    }
    // 2. 递归体:调用自身
    return 函数名(新参数)
}

2.3 经典递归示例

示例1:计算阶乘 n!

规则:0! = 1n! = n * (n-1)!

go 复制代码
package main
import "fmt"

// 递归计算阶乘
func factorial(n int) int {
    // 基准条件
    if n == 0 {
        return 1
    }
    // 递归调用自身
    return n * factorial(n-1)
}

func main() {
    fmt.Println(factorial(5)) // 输出 120
}

示例2:斐波那契数列

规则:f(0)=0,f(1)=1,f(n)=f(n-2)+f(n-1)

go 复制代码
package main
import "fmt"

func fibonacci(n int) int {
    if n < 2 {
        return n
    }
    return fibonacci(n-2) + fibonacci(n-1)
}

func main() {
    for i := 0; i < 10; i++ {
        fmt.Printf("%d ", fibonacci(i))
    }
    // 输出:0 1 1 2 3 5 8 13 21 34
}

示例3:递归遍历文件目录

go 复制代码
package main
import (
    "fmt"
    "os"
    "path/filepath"
)

// 递归遍历目录
func walkDir(dir string, indent string) {
    entries, err := os.ReadDir(dir)
    if err != nil {
        return
    }
    for _, entry := range entries {
        fmt.Println(indent + entry.Name())
        // 如果是文件夹,递归进入子目录
        if entry.IsDir() {
            walkDir(filepath.Join(dir, entry.Name()), indent+"  ")
        }
    }
}

func main() {
    walkDir(".", "")
}

2.4 递归优缺点 & 对比迭代

对比项 递归 迭代(循环)
代码 简洁、逻辑贴近问题本身 代码偏冗长
性能 函数调用开销大,深度递归易栈溢出 执行效率高,内存占用小
调试 调用链路深,调试困难 流程直观,易调试

适用场景

  • 优先递归:树、图遍历、分治算法、目录遍历;
  • 优先迭代:大数据、深度循环、性能敏感场景。

2.5 递归高频踩坑

  1. 缺少终止条件 :无限递归,触发 goroutine stack exceeds 栈溢出崩溃;
  2. 终止条件错误:递归无法正常退出,结果异常;
  3. 递归深度过大:Go 栈空间有限,深度递归直接栈溢出;
  4. 重复计算:如斐波那契递归写法,存在大量重复运算,性能极差。

三、类型转换

Go 是强类型语言,不支持隐式类型转换,不同类型之间赋值、运算必须手动显式转换。分为:基础数值转换、字符串互转、接口类型断言/转换三大类。

3.1 基础数值类型转换

语法

go 复制代码
目标类型(表达式/变量)

示例

go 复制代码
package main
import "fmt"

func main() {
    var a int = 17
    var b int = 5
    // 整型转浮点型后再运算,避免整数除法
    res := float32(a) / float32(b)
    fmt.Println(res) // 3.4
}

规则 & 踩坑

  1. 不同位数整型(int/int32/int64)必须手动转换,直接赋值编译报错;
  2. 大范围转小范围会截断数据(精度丢失);
  3. 浮点转整型会直接舍弃小数部分,不四舍五入。

错误示例:

go 复制代码
var a int64 = 10
var b int32
// b = a  编译报错,不支持隐式转换
b = int32(a) // 正确显式转换

3.2 字符串与基本类型互转

依赖标准库 strconv 包,不能直接用基础类型转换语法。

1. 字符串 ↔ 整型

  • strconv.Atoi(s):字符串转 int,返回 (数值, 错误)
  • strconv.Itoa(n):int 转字符串。
go 复制代码
package main
import (
    "fmt"
    "strconv"
)

func main() {
    // 字符串转整型
    str1 := "123"
    num, err := strconv.Atoi(str1)
    if err == nil {
        fmt.Println(num)
    }

    // 整型转字符串
    num2 := 456
    str2 := strconv.Itoa(num2)
    fmt.Println(str2)
}

2. 字符串 ↔ 浮点型

  • strconv.ParseFloat(字符串, 精度):字符串转浮点;
  • strconv.FormatFloat(浮点数, 格式, 小数位数, 精度):浮点转字符串。
go 复制代码
package main
import (
    "fmt"
    "strconv"
)

func main() {
    // 字符串转 float64
    s := "3.14"
    f, _ := strconv.ParseFloat(s, 64)
    fmt.Println(f)

    // float64 转字符串,保留2位小数
    f2 := 3.1415
    s2 := strconv.FormatFloat(f2, 'f', 2, 64)
    fmt.Println(s2)
}

踩坑

  1. 非数字字符串调用 Atoi/ParseFloat,会返回转换错误;
  2. 不能直接用 string(数字) 做数字转字符串(会转为 ASCII 字符,不是数字文本)。

3.3 接口类型转换(类型断言)

空接口 interface{} 可接收任意类型值,使用类型断言还原原始类型。

基础语法

go 复制代码
// 写法1:判断类型并取值
值, 布尔标识 := 接口变量.(目标类型)

// 写法2:类型选择(switch 批量判断类型)
switch 变量 := 接口变量.(type) {
    case 类型1:
        // 对应逻辑
    case 类型2:
        // 对应逻辑
    default:
}

示例1:基础类型断言

go 复制代码
package main
import "fmt"

func main() {
    var i interface{} = "Go语言"
    // 断言为字符串类型
    str, ok := i.(string)
    if ok {
        fmt.Println("转换成功:", str)
    } else {
        fmt.Println("类型不匹配")
    }
}

示例2:类型选择(多类型判断)

go 复制代码
package main
import "fmt"

func printVal(v interface{}) {
    switch v := v.(type) {
    case int:
        fmt.Println("整型:", v)
    case string:
        fmt.Println("字符串:", v)
    case float64:
        fmt.Println("浮点型:", v)
    default:
        fmt.Println("未知类型")
    }
}

func main() {
    printVal(100)
    printVal("test")
    print(3.14)
}

3.4 类型转换通用踩坑

  1. 禁止隐式转换:所有不同类型必须手动转换,编译强制校验;
  2. 数值精度丢失:大类型转小类型、浮点转整型会丢失数据;
  3. 字符串数字转换:非数字文本转换必然报错,必须捕获 error;
  4. 类型断言失败 :不使用 ok 直接断言,类型不匹配会触发运行 panic;
  5. 区分转换语法 :数值用类型(),字符串/数字互转用strconv包,接口用类型断言。

四、知识点速记

模块 核心要点 高频坑点
Map 无序键值对、引用类型;make初始化;delete删除;range遍历 nil Map直接赋值;仅用单值判断键是否存在;切片/Map做key
递归 必须有终止条件;函数调用自身;适合分治/遍历 无终止条件导致栈溢出;深度递归性能差
类型转换 强类型、无隐式转换;数值直接转;字符串用strconv;接口用类型断言 类型不匹配直接赋值;断言不判断ok;非法字符串转数字
相关推荐
何以解忧,唯有..1 小时前
Go语言变量的声明方式详解
开发语言·后端·golang
me8321 小时前
【AI】Langchain4j开发学习笔记
人工智能·笔记·学习
LuminousCPP1 小时前
数据结构 - 单链表第一篇:单链表基础操作
c语言·数据结构·经验分享·笔记·学习
wubba lubba dub dub7501 小时前
【无标题】
学习
半夜燃烧的香烟1 小时前
springboot3.0 集成minio上传文件,支持多个桶名
java·开发语言·spring boot
不会C语言的男孩1 小时前
Linux 系统编程 · 第 1 章:Linux 系统概述
c语言·开发语言
YM52e1 小时前
鸿蒙PC ArkTS 异常处理深度解析与最佳实践
学习·华为·harmonyos
码云骑士1 小时前
05-Python字典底层原理-Hash表与有序性的真相
开发语言·python·哈希算法
J2虾虾1 小时前
Android支持Java语言的标准
android·java·开发语言