# Go 语言字符串

字符串是 Go 语言中最常用的数据类型之一。Go 的字符串是 UTF-8 编码的不可变字节序列，原生支持 Unicode。理解字符串的本质和操作方式，是高效处理文本数据的基础。

## 字符串的本质

Go 中字符串的本质是**不可变的字节序列**。一旦创建，字符串的内容不能被修改。

```go
package main

import "fmt"

func main() {
    s := "Hello, Go"
    // s[0] = 'h' // 编译错误！字符串是不可变的

    fmt.Println(s)
    fmt.Printf("类型: %T\n", s) // string
}
```

字符串的底层结构包含两个字段：

```go
// runtime 中的简化定义
type StringHeader struct {
    Data uintptr // 指向字节数组的指针
    Len  int     // 字节长度
}
```

## 字符串创建

### 双引号字面量

双引号包裹的字符串支持转义字符，是最常用的创建方式。

```go
package main

import "fmt"

func main() {
    s1 := "Hello, World"
    s2 := "Go\n语言"      // \n 会被解释为换行符
    s3 := "中文\u0041"     // \u0041 是 Unicode 转义，表示 'A'

    fmt.Println(s1) // Hello, World
    fmt.Println(s2) // Go
                  // 语言
    fmt.Println(s3) // 中文A
}
```

### 反引号原始字符串

反引号（`` ` ``）包裹的字符串是原始字符串，不支持转义，可以跨行，常用于多行文本和正则表达式。

```go
package main

import "fmt"

func main() {
    // 原始字符串，\n 不会被转义
    s1 := `Hello\nWorld`
    fmt.Println(s1) // Hello\nWorld

    // 多行字符串
    sql := `SELECT id, name, email
FROM users
WHERE status = 'active'
ORDER BY name`
    fmt.Println(sql)

    // HTML 模板
    html := `<div class="container">
    <h1>标题</h1>
    <p>这是一段 HTML 代码</p>
</div>`
    fmt.Println(html)
}
```

## 字符串与字节（byte / []byte）

`byte` 是 `uint8` 的别名，表示一个字节。字符串可以转换为字节切片，反之亦然。

```go
package main

import "fmt"

func main() {
    s := "Hello"

    // 字符串转字节切片
    b := []byte(s)
    fmt.Println(b) // [72 101 108 108 111]

    // 字节切片转字符串
    s2 := string(b)
    fmt.Println(s2) // Hello

    // 通过字节切片修改字符串内容
    b[0] = 'h'
    s3 := string(b)
    fmt.Println(s3) // hello
}
```

注意：对于包含中文的字符串，按字节操作可能会截断字符。

```go
package main

import "fmt"

func main() {
    s := "你好世界"
    b := []byte(s)

    fmt.Printf("字节长度: %d\n", len(b)) // 12（每个中文字符 3 字节）
    fmt.Printf("字节切片: %v\n", b)

    // 按字节截取可能产生乱码
    fmt.Println(string(b[:3])) // 你（恰好是完整字符）
    fmt.Println(string(b[:2])) // 乱码
}
```

## 字符串与 Unicode（rune）

`rune` 是 `int32` 的别名，表示一个 Unicode 码点。Go 使用 UTF-8 编码，一个中文字符占 3 个字节，但只有 1 个 rune。

```go
package main

import "fmt"

func main() {
    s := "Hello世界"

    // 转换为 rune 切片
    runes := []rune(s)
    fmt.Println(runes)           // [72 101 108 108 111 19990 30028]
    fmt.Println(string(runes[5])) // 世
    fmt.Printf("rune 数量: %d\n", len(runes)) // 7

    // 单个字符用 rune 表示
    ch := '中' // 单引号表示 rune
    fmt.Printf("rune: %c, Unicode: %U\n", ch, ch)
}
```

## 字符串遍历

### 按字节遍历

```go
package main

import "fmt"

func main() {
    s := "Hello"

    for i := 0; i < len(s); i++ {
        fmt.Printf("字节 %d: %c (%d)\n", i, s[i], s[i])
    }
}
```

按字节遍历时，`s[i]` 返回的是 `byte` 类型。对于包含多字节字符（如中文）的字符串，这种方式会逐个字节处理，无法正确识别字符边界。

### 按 rune 遍历（for-range）

```go
package main

import "fmt"

func main() {
    s := "Hello世界"

    // for-range 自动按 UTF-8 解码，返回 rune
    for i, ch := range s {
        fmt.Printf("索引 %d: %c (U+%04X)\n", i, ch, ch)
    }
}
```

for-range 遍历字符串时，索引 `i` 是字节偏移量，`ch` 是 `rune` 类型。这种方式能正确处理 Unicode 字符。

## 常用 strings 包函数

Go 的 `strings` 包提供了丰富的字符串操作函数。

```go
package main

import (
    "fmt"
    "strings"
)

func main() {
    s := "Hello, World! Go is awesome."

    // 判断是否包含子串
    fmt.Println(strings.Contains(s, "Go"))      // true
    fmt.Println(strings.Contains(s, "Java"))     // false

    // 判断是否包含任意字符
    fmt.Println(strings.ContainsAny(s, "abc"))   // true（包含 'a'）

    // 分割字符串
    parts := strings.Split(s, " ")
    fmt.Println(parts) // [Hello, World! Go is awesome.]

    // 连接字符串
    joined := strings.Join(parts, "-")
    fmt.Println(joined) // Hello,-World!-Go-is-awesome.

    // 替换
    replaced := strings.Replace(s, "Go", "Golang", 1)
    fmt.Println(replaced) // Hello, World! Golang is awesome.

    // 替换所有
    replacedAll := strings.ReplaceAll("aaa", "a", "b")
    fmt.Println(replacedAll) // bbb

    // 去除首尾空白
    trimmed := strings.TrimSpace("  hello  ")
    fmt.Printf("[%s]\n", trimmed) // [hello]

    // 去除首尾指定字符
    trimmed2 := strings.Trim("##hello##", "#")
    fmt.Println(trimmed2) // hello

    // 判断前缀/后缀
    fmt.Println(strings.HasPrefix(s, "Hello")) // true
    fmt.Println(strings.HasSuffix(s, "awesome.")) // true

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

    // 统计子串出现次数
    fmt.Println(strings.Count("banana", "an")) // 2

    // 查找子串首次出现的位置
    fmt.Println(strings.Index(s, "World")) // 7
    fmt.Println(strings.Index(s, "Java"))  // -1
}
```

## 字符串格式化

`fmt.Sprintf` 用于格式化字符串，类似 C 语言的 `sprintf`。

```go
package main

import "fmt"

func main() {
    name := "Golang"
    version := 1.21
    year := 2023

    // 常用格式化动词
    fmt.Println(fmt.Sprintf("名称: %s", name))          // 名称: Golang
    fmt.Println(fmt.Sprintf("版本: %.2f", version))      // 版本: 1.21
    fmt.Println(fmt.Sprintf("年份: %d", year))           // 年份: 2023
    fmt.Println(fmt.Sprintf("十六进制: %x", 255))        // 十六进制: ff
    fmt.Println(fmt.Sprintf("布尔值: %t", true))         // 布尔值: true
    fmt.Println(fmt.Sprintf("宽度对齐: [%10s]", name))   // 宽度对齐: [    Golang]
    fmt.Println(fmt.Sprintf("左对齐: [%-10s]", name))    // 左对齐: [Golang    ]

    // 组合使用
    result := fmt.Sprintf("[%s] 版本 %.1f 发布于 %d 年", name, version, year)
    fmt.Println(result) // [Golang] 版本 1.2 发布于 2023 年
}
```

## 字符串拼接方式对比

Go 中字符串是不可变的，每次拼接都会创建新字符串。不同拼接方式的性能差异很大。

### 使用 + 运算符

```go
package main

import (
    "fmt"
    "time"
)

func main() {
    start := time.Now()
    s := ""
    for i := 0; i < 10000; i++ {
        s += "a"
    }
    fmt.Printf("+ 拼接耗时: %v\n", time.Since(start))
}
```

`+` 拼接在循环中性能最差，每次都会分配新内存。

### 使用 strings.Builder

```go
package main

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

func main() {
    start := time.Now()
    var builder strings.Builder

    // 可选：预分配容量
    builder.Grow(10000)

    for i := 0; i < 10000; i++ {
        builder.WriteString("a")
    }
    result := builder.String()
    fmt.Printf("Builder 拼接耗时: %v, 长度: %d\n", time.Since(start), len(result))
}
```

`strings.Builder` 是推荐的拼接方式，内部使用可增长的字节缓冲区，性能远优于 `+`。

`bytes.Buffer` 与 `strings.Builder` 性能接近，适用于需要同时处理字节和字符串的场景。当已有字符串切片时，`strings.Join` 是最简洁高效的拼接方式。

## strconv 包常用操作

`strconv` 包用于字符串与其他基本类型之间的转换。

```go
package main

import (
    "fmt"
    "strconv"
)

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

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

    // 字符串转浮点数
    f, _ := strconv.ParseFloat("3.14", 64)
    fmt.Println(f) // 3.14

    // 浮点数转字符串
    s3 := strconv.FormatFloat(3.14159, 'f', 2, 64)
    fmt.Println(s3) // "3.14"

    // 字符串转布尔
    b, _ := strconv.ParseBool("true")
    fmt.Println(b) // true

    // 整数转指定进制字符串
    s2 := strconv.FormatInt(255, 16)
    fmt.Println(s2) // "ff"
}
```

## 总结

本篇全面介绍了 Go 语言字符串的核心知识：字符串是不可变的 UTF-8 字节序列，支持双引号字面量和反引号原始字符串两种创建方式。`byte` 和 `rune` 分别用于按字节和按 Unicode 字符处理字符串。`strings` 包提供了丰富的字符串操作函数，`fmt.Sprintf` 用于格式化，`strconv` 包处理字符串与基本类型的转换。在拼接大量字符串时，推荐使用 `strings.Builder` 以获得最佳性能。

