404 Not Found

404 Not Found


nginx

字符串处理

第19课:字符串处理

🎯 生活类比

想象你是一位图书管理员。每天你要处理大量的文字工作:

字符串处理就是程序中的"文字工作"——几乎所有程序都需要与文本打交道。


核心概念

Go语言提供了多个标准库来处理字符串:

用途 常用函数
strings 字符串查找、替换、拆分、拼接等 Contains, Replace, Split, Join, Trim
strconv 字符串与其他类型的转换 Atoi, Itoa, ParseBool, FormatFloat
unicode/utf8 UTF-8编码相关的操作 RuneCountInString, ValidString
strings.Builder 高效拼接大量字符串 WriteString, String

关键要点:

  1. Go中的字符串是不可变的(immutable),任何修改操作都会生成新字符串
  2. Go中的字符串底层是UTF-8编码的字节序列
  3. len(str) 返回的是字节数,不是字符数
  4. rune 是Go中表示Unicode码点的类型(本质上是 int32

基本语法与用法

1. strings 包

GO
package main

import (
    "fmt"
    "strings"
)

func main() {
    str := "Hello, Go语言!"

    // 查找
    fmt.Println(strings.Contains(str, "Go"))       // true
    fmt.Println(strings.HasPrefix(str, "Hello"))    // true
    fmt.Println(strings.HasSuffix(str, "!"))        // true
    fmt.Println(strings.Index(str, "Go"))           // 7

    // 替换
    result := strings.Replace(str, "Go", "Golang", 1)
    fmt.Println(result) // "Hello, Golang语言!"

    // 全部替换
    s := "aabbcc"
    fmt.Println(strings.ReplaceAll(s, "a", "x")) // "xxbbcc"

    // 拆分与拼接
    csv := "apple,banana,cherry"
    fruits := strings.Split(csv, ",")
    fmt.Println(fruits) // [apple banana cherry]

    joined := strings.Join(fruits, " | ")
    fmt.Println(joined) // "apple | banana | cherry"

    // 修剪
    padded := "  Hello World  "
    fmt.Println(strings.TrimSpace(padded))         // "Hello World"
    fmt.Println(strings.Trim("##Hello##", "#"))     // "Hello"
    fmt.Println(strings.TrimLeft("##Hello##", "#"))  // "Hello##"

    // 大小写转换
    fmt.Println(strings.ToUpper("hello")) // "HELLO"
    fmt.Println(strings.ToLower("HELLO")) // "hello"

    // 重复
    fmt.Println(strings.Repeat("Go", 3)) // "GoGoGo"

    // 计数
    fmt.Println(strings.Count("banana", "an")) // 2
}
💡 提示strings.Split 的分隔符为空字符串时,会将字符串拆分为单个字符的切片。

2. strconv 包

GO
package main

import (
    "fmt"
    "strconv"
)

func main() {
    // 字符串 → 整数
    num, err := strconv.Atoi("42")
    if err != nil {
        fmt.Println("转换失败:", err)
    }
    fmt.Println(num) // 42

    // 整数 → 字符串
    str := strconv.Itoa(42)
    fmt.Println(str) // "42"

    // 字符串 → 布尔值
    b, err := strconv.ParseBool("true")
    fmt.Println(b, err) // true <nil>

    // 字符串 → 浮点数
    f, err := strconv.ParseFloat("3.14", 64)
    fmt.Println(f, err) // 3.14 <nil>

    // 浮点数 → 字符串
    // 'f'表示普通格式, -1表示最少位数, 64表示float64
    s := strconv.FormatFloat(3.14, 'f', -1, 64)
    fmt.Println(s) // "3.14"

    // 格式化输出(类似C的sprintf)
    formatted := strconv.FormatInt(255, 16) // 十六进制
    fmt.Println(formatted) // "ff"
}
💡 提示strconv.Atoi 等价于 strconv.ParseInt(s, 10, 0),返回的是平台相关的int类型。

3. unicode/utf8 包

GO
package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {
    str := "Go语言编程"

    // len() 返回字节数
    fmt.Println(len(str)) // 14 (每个中文字符占3字节)

    // utf8.RuneCountInString() 返回字符数
    fmt.Println(utf8.RuneCountInString(str)) // 7

    // 判断是否为有效UTF-8
    fmt.Println(utf8.ValidString(str))  // true
    fmt.Println(utf8.ValidString("abc")) // true

    // 遍历字符串中的每个rune
    for i, r := range str {
        fmt.Printf("索引:%d 字符:%c Unicode:%U\n", i, r, r)
    }
}
💡 提示:用 range 遍历字符串时,Go会自动按rune(Unicode字符)遍历,而不是按字节遍历。

4. strings.Builder(高效拼接)

GO
package main

import (
    "fmt"
    "strings"
)

func main() {
    // ❌ 低效方式:每次拼接都创建新字符串
    // result := ""
    // for i := 0; i < 1000; i++ {
    //     result += "a"  // 每次都分配新内存
    // }

    // ✅ 高效方式:使用 strings.Builder
    var builder strings.Builder
    for i := 0; i < 1000; i++ {
        builder.WriteString("a")
    }
    result := builder.String()
    fmt.Println(len(result)) // 1000

    // 预分配容量,进一步提升性能
    var builder2 strings.Builder
    builder2.Grow(1000) // 预分配1000字节
    for i := 0; i < 1000; i++ {
        builder2.WriteString("b")
    }
    fmt.Println(builder2.Len()) // 1000
}
💡 提示strings.Builder 内部使用 []byte 切片,避免了字符串不可变性带来的频繁内存分配。


示例代码

示例:字符串统计与分析(难度⭐)

GO
package main

import (
    "fmt"
    "strings"
    "unicode"
)

// 统计字符串中的各类字符数量
func analyzeString(s string) (letters, digits, spaces, others int) {
    for _, r := range s {
        switch {
        case unicode.IsLetter(r):
            letters++
        case unicode.IsDigit(r):
            digits++
        case unicode.IsSpace(r):
            spaces++
        default:
            others++
        }
    }
    return
}

func main() {
    text := "Hello, Go语言! 2024年 Version 2.0"

    letters, digits, spaces, others := analyzeString(text)
    fmt.Printf("文本: %q\n", text)
    fmt.Printf("字母: %d\n", letters)
    fmt.Printf("数字: %d\n", digits)
    fmt.Printf("空格: %d\n", spaces)
    fmt.Printf("其他: %d\n", others)

    // 统计单词频率
    words := strings.Fields("the go the language the world")
    freq := make(map[string]int)
    for _, w := range words {
        freq[strings.ToLower(w)]++
    }
    fmt.Println("\n单词频率:", freq)
}
▶ 试一试

运行结果:

TEXT
文本: "Hello, Go语言! 2024年 Version 2.0"
字母: 17
数字: 6
空格: 5
其他: 3

单词频率: map[go:1 language:1 the:3 world:1]

示例:CSV解析器(难度⭐⭐)

GO
package main

import (
    "fmt"
    "strings"
)

// 简易CSV行解析器,支持引号包裹的字段
func parseCSVLine(line string) []string {
    var fields []string
    var current strings.Builder
    inQuotes := false

    for _, r := range line {
        switch {
        case r == '"' && !inQuotes:
            // 进入引号区域
            inQuotes = true
        case r == '"' && inQuotes:
            // 离开引号区域
            inQuotes = false
        case r == ',' && !inQuotes:
            // 遇到分隔符,保存当前字段
            fields = append(fields, current.String())
            current.Reset()
        default:
            // 普通字符
            current.WriteRune(r)
        }
    }
    // 保存最后一个字段
    fields = append(fields, current.String())

    return fields
}

// 清理并格式化字段
func cleanFields(fields []string) []string {
    cleaned := make([]string, len(fields))
    for i, f := range fields {
        cleaned[i] = strings.TrimSpace(f)
    }
    return cleaned
}

func main() {
    // 模拟CSV数据
    csvData := []string{
        `Alice,28,"Beijing, China"`,
        `Bob,35,"New York, USA"`,
        `Charlie,42,"London, UK"`,
    }

    fmt.Println("=== CSV解析结果 ===")
    for _, line := range csvData {
        fields := parseCSVLine(line)
        fields = cleanFields(fields)
        fmt.Printf("姓名: %-10s 年龄: %-4s 地点: %s\n",
            fields[0], fields[1], fields[2])
    }

    // 反向操作:将切片拼接为CSV行
    record := []string{"David", "30", "Shanghai, China"}
    csvLine := strings.Join(record, ",")
    fmt.Println("\n生成的CSV行:", csvLine)
}
▶ 试一试

运行结果:

TEXT
=== CSV解析结果 ===
姓名: Alice      年龄: 28   地点: Beijing, China
姓名: Bob        年龄: 35   地点: New York, USA
姓名: Charlie    年龄: 42   地点: London, UK

生成的CSV行: David,30,Shanghai, China

示例:模板引擎(难度⭐⭐⭐)

GO
package main

import (
    "fmt"
    "strconv"
    "strings"
)

// 简易模板引擎:将 {{key}} 替换为对应值
func renderTemplate(template string, data map[string]string) string {
    var result strings.Builder
    result.Grow(len(template) * 2) // 预估容量

    i := 0
    for i < len(template) {
        // 查找 "{{"
        if i+1 < len(template) && template[i] == '{' && template[i+1] == '{' {
            // 查找对应的 "}}"
            end := strings.Index(template[i+2:], "}}")
            if end != -1 {
                key := strings.TrimSpace(template[i+2 : i+2+end])
                if value, ok := data[key]; ok {
                    result.WriteString(value)
                } else {
                    // 键不存在,保留原样
                    result.WriteString("{{" + key + "}}")
                }
                i += end + 4 // 跳过 "}}"
                continue
            }
        }
        result.WriteByte(template[i])
        i++
    }

    return result.String()
}

// 格式化表格输出
func formatTable(headers []string, rows [][]string) string {
    // 计算每列最大宽度
    colWidths := make([]int, len(headers))
    for i, h := range headers {
        colWidths[i] = len(h)
    }
    for _, row := range rows {
        for i, cell := range row {
            if i < len(colWidths) && len(cell) > colWidths[i] {
                colWidths[i] = len(cell)
            }
        }
    }

    var b strings.Builder

    // 写入表头
    for i, h := range headers {
        b.WriteString(fmt.Sprintf("%-*s | ", colWidths[i], h))
    }
    b.WriteString("\n")

    // 写入分隔线
    for i := range headers {
        b.WriteString(strings.Repeat("-", colWidths[i]) + "-+-")
    }
    b.WriteString("\n")

    // 写入数据行
    for _, row := range rows {
        for i, cell := range row {
            if i < len(colWidths) {
                b.WriteString(fmt.Sprintf("%-*s | ", colWidths[i], cell))
            }
        }
        b.WriteString("\n")
    }

    return b.String()
}

// 将字符串转换为不同进制表示
func toBases(numStr string) (map[string]string, error) {
    num, err := strconv.ParseInt(numStr, 10, 64)
    if err != nil {
        return nil, err
    }

    return map[string]string{
        "decimal":     strconv.FormatInt(num, 10),
        "binary":      strconv.FormatInt(num, 2),
        "octal":       strconv.FormatInt(num, 8),
        "hexadecimal": strconv.FormatInt(num, 16),
    }, nil
}

func main() {
    // 1. 模板渲染
    fmt.Println("=== 模板渲染 ===")
    template := "Hello, {{name}}! Welcome to {{city}}. You have {{count}} new messages."
    data := map[string]string{
        "name":  "张三",
        "city":  "北京",
        "count": "5",
    }
    fmt.Println(renderTemplate(template, data))

    // 2. 表格格式化
    fmt.Println("\n=== 表格格式化 ===")
    headers := []string{"姓名", "年龄", "城市"}
    rows := [][]string{
        {"Alice", "28", "Beijing"},
        {"Bob", "35", "New York"},
        {"Charlie", "42", "London"},
    }
    fmt.Print(formatTable(headers, rows))

    // 3. 进制转换
    fmt.Println("\n=== 进制转换 ===")
    bases, _ := toBases("255")
    for name, value := range bases {
        fmt.Printf("%-12s: %s\n", name, value)
    }
}
▶ 试一试

运行结果:

TEXT
=== 模板渲染 ===
Hello, 张三! Welcome to 北京. You have 5 new messages.

=== 表格格式化 ===
姓名    | 年龄 | 城市    | 
--------+------+---------+-
Alice   | 28   | Beijing | 
Bob     | 35   | New York| 
Charlie | 42   | London  | 

=== 进制转换 ===
decimal     : 255
binary      : 11111111
octal       : 377
hexadecimal : ff

实际应用场景

场景1:日志分析器

GO
package main

import (
    "fmt"
    "strconv"
    "strings"
    "time"
)

// 日志条目结构
type LogEntry struct {
    Timestamp string
    Level     string
    Message   string
    Source    string
}

// 解析日志行
// 格式: [2024-01-15 10:30:00] [ERROR] Database connection failed (db-service)
func parseLogLine(line string) (*LogEntry, error) {
    entry := &LogEntry{}

    // 提取时间戳
    if start := strings.Index(line, "["); start != -1 {
        if end := strings.Index(line, "]"); end != -1 {
            entry.Timestamp = line[start+1 : end]
            line = line[end+1:]
        }
    }

    // 提取日志级别
    line = strings.TrimSpace(line)
    if start := strings.Index(line, "["); start != -1 {
        if end := strings.Index(line, "]"); end != -1 {
            entry.Level = line[start+1 : end]
            line = line[end+1:]
        }
    }

    // 提取消息和来源
    line = strings.TrimSpace(line)
    if parenStart := strings.LastIndex(line, "("); parenStart != -1 {
        if parenEnd := strings.LastIndex(line, ")"); parenEnd != -1 {
            entry.Source = line[parenStart+1 : parenEnd]
            entry.Message = strings.TrimSpace(line[:parenStart])
        }
    } else {
        entry.Message = line
    }

    return entry, nil
}

// 统计日志级别
func analyzeLogs(entries []LogEntry) map[string]int {
    stats := make(map[string]int)
    for _, e := range entries {
        stats[strings.ToUpper(e.Level)]++
    }
    return stats
}

// 过滤包含关键词的日志
func filterLogs(entries []LogEntry, keyword string) []LogEntry {
    var filtered []LogEntry
    keyword = strings.ToLower(keyword)
    for _, e := range entries {
        if strings.Contains(strings.ToLower(e.Message), keyword) {
            filtered = append(filtered, e)
        }
    }
    return filtered
}

func main() {
    // 模拟日志数据
    logLines := []string{
        "[2024-01-15 10:30:00] [INFO] Application started (main-service)",
        "[2024-01-15 10:30:05] [INFO] Connected to database (db-service)",
        "[2024-01-15 10:31:00] [WARN] High memory usage detected (monitor)",
        "[2024-01-15 10:32:00] [ERROR] Database connection timeout (db-service)",
        "[2024-01-15 10:32:01] [ERROR] Retry failed, switching to backup (db-service)",
        "[2024-01-15 10:33:00] [INFO] Backup database connected (db-service)",
        "[2024-01-15 10:35:00] [DEBUG] Cache cleared (cache-service)",
    }

    // 解析所有日志
    var entries []LogEntry
    for _, line := range logLines {
        entry, err := parseLogLine(line)
        if err == nil {
            entries = append(entries, *entry)
        }
    }

    // 统计日志级别
    fmt.Println("=== 日志级别统计 ===")
    stats := analyzeLogs(entries)
    for level, count := range stats {
        fmt.Printf("  %s: %d 条\n", level, count)
    }

    // 过滤错误日志
    fmt.Println("\n=== 错误日志 ===")
    for _, e := range entries {
        if strings.ToUpper(e.Level) == "ERROR" {
            fmt.Printf("  %s | %s | %s\n", e.Timestamp, e.Message, e.Source)
        }
    }

    // 搜索关键词
    fmt.Println("\n=== 包含 'database' 的日志 ===")
    filtered := filterLogs(entries, "database")
    for _, e := range filtered {
        fmt.Printf("  [%s] %s\n", e.Level, e.Message)
    }
}

运行结果:

TEXT
=== 日志级别统计 ===
  INFO: 3 条
  WARN: 1 条
  ERROR: 2 条
  DEBUG: 1 条

=== 错误日志 ===
  2024-01-15 10:32:00 | Database connection timeout | db-service
  2024-01-15 10:32:01 | Retry failed, switching to backup | db-service

=== 包含 'database' 的日志 ===
  [INFO] Connected to database
  [ERROR] Database connection timeout
  [INFO] Backup database connected

场景2:用户输入验证与清理

GO
package main

import (
    "fmt"
    "regexp"
    "strconv"
    "strings"
    "unicode"
)

// 清理用户输入的用户名
func sanitizeUsername(name string) (string, error) {
    // 去除首尾空格
    name = strings.TrimSpace(name)

    // 检查长度
    if len(name) < 3 {
        return "", fmt.Errorf("用户名太短(最少3个字符)")
    }
    if len(name) > 20 {
        return "", fmt.Errorf("用户名太长(最多20个字符)")
    }

    // 只允许字母、数字、下划线
    var cleaned strings.Builder
    for _, r := range name {
        if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' {
            cleaned.WriteRune(r)
        }
    }

    result := cleaned.String()
    if len(result) < 3 {
        return "", fmt.Errorf("有效字符太少")
    }

    return strings.ToLower(result), nil
}

// 验证并解析手机号(中国大陆)
func parsePhone(phone string) (string, error) {
    // 去除所有空格和连字符
    phone = strings.ReplaceAll(phone, " ", "")
    phone = strings.ReplaceAll(phone, "-", "")

    // 检查是否以+86开头
    if strings.HasPrefix(phone, "+86") {
        phone = phone[3:]
    } else if strings.HasPrefix(phone, "86") {
        phone = phone[2:]
    }

    // 验证长度
    if len(phone) != 11 {
        return "", fmt.Errorf("手机号长度不正确: %d位", len(phone))
    }

    // 验证是否全是数字
    for _, r := range phone {
        if !unicode.IsDigit(r) {
            return "", fmt.Errorf("手机号包含非数字字符: %c", r)
        }
    }

    // 验证是否以1开头
    if !strings.HasPrefix(phone, "1") {
        return "", fmt.Errorf("手机号必须以1开头")
    }

    return phone, nil
}

// 解析带单位的大小字符串
func parseSize(sizeStr string) (int64, error) {
    sizeStr = strings.TrimSpace(strings.ToUpper(sizeStr))

    // 提取数字部分和单位部分
    var numPart strings.Builder
    var unitPart strings.Builder

    for _, r := range sizeStr {
        if unicode.IsDigit(r) || r == '.' {
            numPart.WriteRune(r)
        } else if unicode.IsLetter(r) {
            unitPart.WriteRune(r)
        }
    }

    num, err := strconv.ParseFloat(numPart.String(), 64)
    if err != nil {
        return 0, fmt.Errorf("无效的数字: %s", numPart.String())
    }

    // 根据单位转换为字节
    unit := unitPart.String()
    multipliers := map[string]int64{
        "B":  1,
        "KB": 1024,
        "MB": 1024 * 1024,
        "GB": 1024 * 1024 * 1024,
        "TB": 1024 * 1024 * 1024 * 1024,
    }

    multiplier, ok := multipliers[unit]
    if !ok {
        return 0, fmt.Errorf("未知的单位: %s", unit)
    }

    return int64(num * float64(multiplier)), nil
}

func main() {
    // 用户名清理测试
    fmt.Println("=== 用户名验证 ===")
    usernames := []string{"  Alice_123  ", "ab", "A!@#B", "GoDeveloper2024"}
    for _, u := range usernames {
        result, err := sanitizeUsername(u)
        if err != nil {
            fmt.Printf("  %q → 错误: %v\n", u, err)
        } else {
            fmt.Printf("  %q → %q\n", u, result)
        }
    }

    // 手机号解析测试
    fmt.Println("\n=== 手机号解析 ===")
    phones := []string{"138 0013 8000", "+86-138-0013-8000", "12345", "23800138000"}
    for _, p := range phones {
        result, err := parsePhone(p)
        if err != nil {
            fmt.Printf("  %q → 错误: %v\n", p, err)
        } else {
            fmt.Printf("  %q → %s\n", p, result)
        }
    }

    // 文件大小解析测试
    fmt.Println("\n=== 文件大小解析 ===")
    sizes := []string{"1.5GB", "512MB", "1024KB", "100B", "2TB"}
    for _, s := range sizes {
        bytes, err := parseSize(s)
        if err != nil {
            fmt.Printf("  %s → 错误: %v\n", s, err)
        } else {
            fmt.Printf("  %s → %d 字节\n", s, bytes)
        }
    }
}

运行结果:

TEXT
=== 用户名验证 ===
"  Alice_123  " → "alice_123"
"ab" → 错误: 用户名太短(最少3个字符)
"A!@#B" → 错误: 有效字符太少
"GoDeveloper2024" → "godeveloper2024"

=== 手机号解析 ===
"138 0013 8000" → 13800138000
"+86-138-0013-8000" → 13800138000
"12345" → 错误: 手机号长度不正确: 5位
"23800138000" → 错误: 手机号必须以1开头

=== 文件大小解析 ===
1.5GB → 1610612736 字节
512MB → 536870912 字节
1024KB → 1048576 字节
100B → 100 字节
2TB → 2199023255552 字节

❓ 常见问题

Q1:为什么 len("Go语言") 返回 8 而不是 4?

GO
package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {
    s := "Go语言"

    // len() 返回字节数,不是字符数
    fmt.Println("len():", len(s)) // 8

    // 中文字符在UTF-8中占3字节
    // G(1) + o(1) + 语(3) + 言(3) = 8

    // 正确获取字符数的方法
    fmt.Println("RuneCountInString():", utf8.RuneCountInString(s)) // 4

    // 或者使用 range 遍历计数
    count := 0
    for range s {
        count++
    }
    fmt.Println("range计数:", count) // 4
}

要点:处理中文等多字节字符时,务必使用 utf8.RuneCountInString()range 来获取真正的字符数。

Q2:字符串拼接用 + 还是 strings.Builder

GO
package main

import (
    "fmt"
    "strings"
)

func main() {
    // 少量拼接:用 + 即可(编译器会优化)
    s := "Hello" + " " + "World"
    fmt.Println(s)

    // 大量拼接:必须用 strings.Builder
    var builder strings.Builder
    for i := 0; i < 10000; i++ {
        builder.WriteString("a")
    }
    fmt.Println("长度:", builder.Len())

    // 预分配可以进一步提升性能
    var builder2 strings.Builder
    builder2.Grow(10000) // 预分配10000字节
    for i := 0; i < 10000; i++ {
        builder2.WriteString("b")
    }
    fmt.Println("长度:", builder2.Len())
}

经验法则

场景 推荐方式
2-3个字符串拼接 +fmt.Sprintf
循环中拼接(次数已知) strings.Builder + Grow()
循环中拼接(次数未知) strings.Builder

Q3:如何判断字符串是否只包含特定字符?

GO
package main

import (
    "fmt"
    "strings"
    "unicode"
)

func main() {
    s := "Hello123"

    // 检查是否只包含字母和数字
    isAlphanumeric := true
    for _, r := range s {
        if !unicode.IsLetter(r) && !unicode.IsDigit(r) {
            isAlphanumeric = false
            break
        }
    }
    fmt.Println("仅字母数字:", isAlphanumeric)

    // 检查是否只包含ASCII字母
    isASCII := true
    for _, r := range s {
        if r > 127 {
            isASCII = false
            break
        }
    }
    fmt.Println("仅ASCII:", isASCII)

    // 检查是否包含特定字符集
    allowed := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    containsOnlyAllowed := true
    for _, r := range s {
        if !strings.ContainsRune(allowed, r) {
            containsOnlyAllowed = false
            break
        }
    }
    fmt.Println("在允许范围内:", containsOnlyAllowed)
}

Q4:strings.Contains 和正则表达式如何选择?

GO
package main

import (
    "fmt"
    "regexp"
    "strings"
)

func main() {
    text := "我的邮箱是test@example.com,电话是13800138000"

    // 简单查找 → 用 strings 包(更快)
    fmt.Println(strings.Contains(text, "example.com")) // true

    // 模式匹配 → 用正则表达式
    emailRegex := regexp.MustCompile(`[\w.]+@[\w.]+\.\w+`)
    fmt.Println("邮箱:", emailRegex.FindString(text))

    phoneRegex := regexp.MustCompile(`1[3-9]\d{9}`)
    fmt.Println("电话:", phoneRegex.FindString(text))
}

选择建议


📖 小节

主题 关键内容
strings 包 Contains, HasPrefix, HasSuffix, Index, Replace, Split, Join, Trim, ToUpper, ToLower, Count, Repeat, Fields
strconv 包 Atoi, Itoa, ParseBool, ParseFloat, FormatInt, FormatFloat
unicode/utf8 RuneCountInString, ValidString, unicode.IsLetter/IsDigit/IsSpace
strings.Builder WriteString, WriteRune, WriteByte, Grow, String, Len
核心原则 字符串不可变、len返回字节数、range按rune遍历、大量拼接用Builder

📝 作业

练习1:基础 - 字符串反转

编写一个函数 reverseString(s string) string,反转一个字符串。要求正确处理中文字符。

提示:不能简单地将字符串转为 []byte 反转,因为中文字符占多个字节。

GO
// 期望结果
reverseString("Hello")   // "olleH"
reverseString("Go语言")  // "言语oG"

练习2:进阶 - 驼峰与下划线互转

编写两个函数:

提示:利用 unicode.IsUpper 判断大写字母位置。

GO
// 期望结果
camelToSnake("helloWorld")     // "hello_world"
camelToSnake("HTTPResponse")   // "http_response"
snakeToCamel("hello_world")    // "helloWorld"
snakeToCamel("http_response")  // "httpResponse"

练习3:挑战 - 简易Markdown标题提取器

编写一个函数 extractHeadings(md string) []string,提取Markdown文档中的所有标题。

提示:标题以 # 开头,# 的数量表示标题级别。

GO
// 输入
md := `# 一级标题
这是正文
## 二级标题
### 三级标题
## 另一个二级`

// 期望输出
// ["# 一级标题", "## 二级标题", "### 三级标题", "## 另一个二级"]

下一课

恭喜你完成了字符串处理的学习!在下一课中,我们将学习 文件I/O操作——如何读写文件、处理目录、以及使用缓冲I/O提高性能。

👉 第20课:文件I/O

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏