复制成功
请遵守本站 许可
REPORT
Chapter_Post // Field_Report

Post_Ref: RL-CH05-STR

2026.05.03

Ch.05:字符串进阶篇

Chongxi
Chongxi
Listening: IDLE_SESSION
#教程#Golang
ANALYSIS

TL;DR:本文介绍了在Golang中字符串进阶内容

上一篇提到字符串,有几个细节刻意留到这篇来讲:len() 为什么返回字节数、中文字符怎么处理、byte 和 rune 到底是什么关系。这些问题如果没搞清楚,日后处理中文字符串一定会踩坑

字符串的本质#

Go 的字符串本质上是一段只读的字节序列,底层是这样的:

PRTCL // GO
name := "Hello"

内存里存的不是字符,是字节。“Hello” 对应的字节是 72 101 108 108 111,每个字母占一个字节

这在只处理英文时没有任何问题,但中文字符不是一个字节能表示的

UTF-8 编码#

Go 源代码默认使用 UTF-8 编码,字符串也是 UTF-8 编码的字节序列

UTF-8 是一种变长编码:

  • ASCII 字符(英文字母、数字、标点)占 1 个字节
  • 大多数中文字符占 3 个字节
  • 某些生僻字和 emoji 占 4 个字节

所以:

PRTCL // GO
a := "Hello"
b := "你好"
fmt.Println(len(a)) // 5,5 个字节
fmt.Println(len(b)) // 6,2 个中文字符 × 3 字节

len() 返回的是字节数,不是你直觉上的字符数。这是处理中文最容易踩的第一个坑

byte:字节#

byteuint8 的别名,表示一个字节,取值范围 0-255。

字符串可以按字节来访问,用下标:

PRTCL // GO
s := "Hello"
fmt.Println(s[0]) // 输出 72,是 'H' 的 ASCII 码,不是字符 'H'

注意下标访问返回的是数字,不是字符。要打印字符需要转换:

PRTCL // GO
fmt.Printf("%c\n", s[0]) // 输出 H

可以把字符串转成 []byte(字节切片)来操作每个字节:

PRTCL // GO
s := "Hello"
bs := []byte(s)
bs[0] = 'h' // 修改第一个字节
fmt.Println(string(bs)) // 输出 hello

字符串本身不可修改,但转成 []byte 之后可以修改,改完再转回字符串

[]byte 和字符串互转:

PRTCL // GO
s := "Hello"
bs := []byte(s) // string → []byte
s2 := string(bs) // []byte → string

rune:字符#

runeint32 的别名,表示一个 Unicode 字符。每个 rune 对应一个字符,不管这个字符底层占几个字节

PRTCL // GO
s := "你好"
rs := []rune(s)
fmt.Println(len(s)) // 6,字节数
fmt.Println(len(rs)) // 2,字符数

把字符串转成 []rune,再用 len(),得到的才是你直觉上的字符数

按字符遍历字符串,用 []rune

PRTCL // GO
s := "你好 Go"
rs := []rune(s)
fmt.Println(len(rs)) // 4,四个字符:你、好、G、o
fmt.Println(rs[0]) // 20320,' 你 ' 的 Unicode 码
fmt.Printf("%c\n", rs[0]) // 你

[]rune 和字符串互转:

PRTCL // GO
s := "你好"
rs := []rune(s) // string → []rune
s2 := string(rs) // []rune → string

range 遍历字符串#

遍历字符串有两种方式,结果完全不同。

按字节遍历(下标):

PRTCL // GO
s := "你好"
for i := 0; i < len(s); i++ {
fmt.Printf("索引 %d: %d\n", i, s[i])
}

输出六行,每行是一个字节的数值,中文字符被拆散了

按字符遍历(range):

PRTCL // GO
s := "你好 Go"
for i, r := range s {
fmt.Printf("索引 %d: %c\n", i, r)
}

输出:

PRTCL // PLAINTEXT
索引 0: 你
索引 3: 好
索引 6: G
索引 7: o

range 遍历字符串时,自动按 UTF-8 解码,每次返回一个 rune。注意索引不是连续的: 的索引是 0, 的索引是 3,因为每个中文字符占 3 个字节

处理中文字符串,遍历用 range,计算字符数转成 []rune

字符串操作#

Go 的字符串操作主要靠标准库的 strings 包,以下是最常用的。

使用前先引入:

PRTCL // GO
import "strings"

判断包含:

PRTCL // GO
s := "Hello, Go!"
fmt.Println(strings.Contains(s, "Go")) // true
fmt.Println(strings.Contains(s, "Python")) // false

判断前缀和后缀:

PRTCL // GO
s := "Hello, Go!"
fmt.Println(strings.HasPrefix(s, "Hello")) // true
fmt.Println(strings.HasSuffix(s, "Go!")) // true

查找位置:

PRTCL // GO
s := "Hello, Go!"
fmt.Println(strings.Index(s, "Go")) // 7,返回第一次出现的字节位置
fmt.Println(strings.Index(s, "Java")) // -1,找不到返回 -1

替换:

PRTCL // GO
s := "Hello, Go! Go is great!"
// 替换所有
fmt.Println(strings.ReplaceAll(s, "Go", "Rust"))
// Hello, Rust! Rust is great!
// 替换前 n 个,n=-1 表示全部
fmt.Println(strings.Replace(s, "Go", "Rust", 1))
// Hello, Rust! Go is great!

大小写:

PRTCL // GO
s := "Hello, Go!"
fmt.Println(strings.ToUpper(s)) // HELLO, GO!
fmt.Println(strings.ToLower(s)) // hello, go!

去除空白:

PRTCL // GO
s := " Hello, Go! "
fmt.Println(strings.TrimSpace(s)) // "Hello, Go!"
fmt.Println(strings.Trim(s, " ")) // 同上,去除两端指定字符
fmt.Println(strings.TrimLeft(s, " ")) // 只去左边
fmt.Println(strings.TrimRight(s, " ")) // 只去右边

分割:

PRTCL // GO
s := "a,b,c,d"
parts := strings.Split(s, ",")
fmt.Println(parts) // [a b c d]
fmt.Println(parts[0]) // a
fmt.Println(len(parts)) // 4

拼接:

PRTCL // GO
parts := []string{"a", "b", "c"}
fmt.Println(strings.Join(parts, "-")) // a-b-c

统计出现次数:

PRTCL // GO
s := "go go go"
fmt.Println(strings.Count(s, "go")) // 3

重复:

PRTCL // GO
fmt.Println(strings.Repeat("go", 3)) // gogogo

高效拼接:strings.Builder#

+ 拼接字符串,每次都会创建一个新字符串,大量拼接时性能很差:

PRTCL // GO
// 这太闹腾了,不推荐大量使用
result := ""
for i := 0; i < 10000; i++ {
result += "a"
}

应该用 strings.Builder

PRTCL // GO
var builder strings.Builder
for i := 0; i < 10000; i++ {
builder.WriteString("a")
}
result := builder.String()

strings.Builder 内部维护一个缓冲区,避免反复创建新字符串,性能好很多

字符串格式化#

fmt 包提供了格式化字符串的功能,用 fmt.Sprintf 生成字符串:

PRTCL // GO
name := "Chongxi"
age := 23
s := fmt.Sprintf("我叫 %s,今年 %d 岁", name, age)
fmt.Println(s) // 我叫 Chongxi,今年 23 岁

常用格式化动词:

动词含义
%s字符串
%d整数(十进制)
%f浮点数
%.2f浮点数,保留两位小数
%b整数(二进制)
%x整数(十六进制)
%c字符(rune)
%T变量的类型
%v任意类型的默认格式
%+v结构体时打印字段名
PRTCL // GO
pi := 3.14159
fmt.Printf("%.2f\n", pi) // 3.14
fmt.Printf("%T\n", pi) // float64
fmt.Printf("%d\n", 255) // 255
fmt.Printf("%x\n", 255) // ff
fmt.Printf("%b\n", 255) // 11111111

fmt.Sprintf 返回格式化后的字符串,fmt.Printf 直接打印,fmt.Fprintf 输出到指定地方(比如文件),三者格式化规则相同。

strconv:数字和字符串互转#

字符串和数字之间不能直接用类型转换,要用 strconv 包:

PRTCL // GO
import "strconv"

整数转字符串:

PRTCL // GO
n := 42
s := strconv.Itoa(n) // Itoa = Integer to ASCII
fmt.Println(s) // "42"
fmt.Printf("%T\n", s) // string

字符串转整数:

PRTCL // GO
s := "42"
n, err := strconv.Atoi(s) // Atoi = ASCII to Integer
if err != nil {
fmt.Println("转换失败:", err)
} else {
fmt.Println(n) // 42
}

strconv.Atoi 返回两个值:转换结果和错误。字符串不一定能转成整数,比如 "hello" 就转不了,所以必须处理错误。错误处理是 Go 的核心概念,后面会专门讲

字符串转浮点数:

PRTCL // GO
s := "3.14"
f, err := strconv.ParseFloat(s, 64) // 64 表示 float64
if err != nil {
fmt.Println("转换失败:", err)
} else {
fmt.Println(f) // 3.14
}

浮点数转字符串:

PRTCL // GO
f := 3.14159
s := strconv.FormatFloat(f, 'f', 2, 64) // 'f' 格式,2 位小数,float64
fmt.Println(s) // "3.14"

我踩过的常见坑#

用 len() 计算中文字符数

PRTCL // GO
s := "你好"
fmt.Println(len(s)) // 6,字节数,不是字符数
fmt.Println(len([]rune(s))) // 2,字符数,正确

下标访问中文字符

PRTCL // GO
s := "你好"
fmt.Println(s[0]) // 228,是第一个字节,不是 ' 你 '
fmt.Println([]rune(s)[0]) // 20320,' 你 ' 的 Unicode 码

截取中文字符串用下标

PRTCL // GO
s := "你好世界"
fmt.Println(s[:3]) // 乱码,截取了 3 个字节,破坏了中文编码
fmt.Println(string([]rune(s)[:2])) // "你好",正确

以为字符串可以修改

PRTCL // GO
s := "Hello"
s[0] = 'h' // 报错,字符串不可修改

要修改,先转成 []byte[]rune,改完再转回来


下一篇讲常量与 iota,Go 里枚举是怎么定义的。

R P
Rhine Lab Pioneer Division
Auth_Verified: 2026.05.03
// END OF POST
Donation_Channel // Support_Us

如果觉得本文不错,不妨请我喝杯咖啡。

爱发电
golearn
Series_Associated // 专题收录

从0带你学Golang

该文章已被收录至本站深度研究专题。点击进入项目主页,查看完整研究序列。

Classified
Chapter_06 // Legal_Protocol
Protocol_Ref: CC-BY-NC-SA-4.0

Ch.05:字符串进阶篇

Author: CHONGXI // Released: 2026.05.03

本受试报告采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 许可协议进行分发。

} } out>