Kodewick
开始学 →
第 9 课,共 24 课目录

字符串和 rune

第 9 课,共 24 课 · 第 2 部分 · 数据 · 约 15 分钟

先猜一猜:「你好」有几个字?两个。可是 Go 说不是:

prog.go
1package main2 3import "fmt"4 5func main() {6	fmt.Println(len("Go"))7	fmt.Println(len("你好"))8	fmt.Println(len("Go语言"))9}
输出
2
6
8

len 数的不是字,而是字节。Go 的字符串是一串字节,文字用 UTF-8 编码存:英文字母、数字一个字节,一个汉字三个字节。所以「你好」是 6,「Go语言」是 2 + 3 + 3 = 8。

下标取到的是字节

s[i] 取第 i 个字节,类型是 byte,显示出来是一个数:

prog.go
1package main2 3import "fmt"4 5func main() {6	s := "Go语言"7	fmt.Println(s[0], s[1])8	fmt.Println(string(s[0]))9	fmt.Println(s[2])10	fmt.Println(s[2:5])11}
输出
71 111
G
232
语

G 的编码是 71,o 是 111;string(s[0]) 把它变回文字。s[2] 是「语」三个字节里的第一个,单独拿出来没有意义。切一段 s[2:5] 正好是这三个字节,所以显示「语」。

字符串不能改

字符串是只读的:不能改其中的一个字节。

prog.go
1package main2 3import "fmt"4 5func main() {6	word := "hello"7	word[0] = 'H'8	fmt.Println(word)9}
输出
# command-line-arguments
./prog.go:7:2: cannot assign to word[0] (neither addressable nor a map index expression)

发生了什么: 字符串是只读的,不能改其中一个字节(word[0]):要改就拼一个新的字符串,或者先转成 []byte / []rune。

编译器说不能给 word[0] 赋值。想要不一样的字符串,就拼一个新的,比如 word = "H" + word[1:];变量 word 可以换成别的字符串,原来那串字节不会变。'H' 单引号是一个字符,下一节讲。

按字走:range

for i, r := range s 一次走一个字:i 是这个字从第几个字节开始,r 是这个字,类型是 rune(字的编号,也是一个数)。

prog.go
1package main2 3import "fmt"4 5func main() {6	for i, r := range "Go语言" {7		fmt.Println(i, r, string(r))8	}9}
输出
0 71 G
1 111 o
2 35821 语
5 35328 言

下标是 0、1、2、5:「语」占了 2、3、4 三个字节,所以「言」从 5 开始。r 本身显示成数(「语」是 35821),string(r) 把它变回文字。单引号 '语' 写的就是一个 rune。点单步,看 i 怎么跳过去。

[]rune:按字数

[]rune(s) 把字符串变成一个 rune 切片,一个字一个元素。数字数、按字取,都用它:

prog.go
1package main2 3import "fmt"4 5func main() {6	title := "今天天气很好"7	runes := []rune(title)8	fmt.Println(len(title), len(runes))9	fmt.Println(string(runes[0]))10	fmt.Println(string(runes[:4]))11}
输出
18 6
今
今天天气

string(runes[:4]) 把前四个字变回字符串。直接写 title[:4] 会切在一个字的中间。

先猜一猜

这段程序显示什么?

1package main2 3import "fmt"4 5func main() {6	s := "猫cat"7	fmt.Println(len(s), len([]rune(s)))8}

strings 包

strings 包里是处理字符串的常用函数。它们都不改原来的字符串,而是返回新的:

prog.go
1package main2 3import (4	"fmt"5	"strings"6)7 8func main() {9	s := "go is fun"10	fmt.Println(strings.Contains(s, "fun"))11	fmt.Println(strings.ToUpper(s))12	fmt.Println(strings.Repeat("-", 9))13	fmt.Println(strings.Count(s, "o"))14}
输出
true
GO IS FUN
---------
1

ToUpper 只对字母有用,汉字没有大小写,所以这里用英文。

切开和拼起来:Split 按指定的分隔符切成切片,Fields 按空白切(几个空格连在一起也只算一处),Join 用分隔符把切片拼成一个字符串:

prog.go
1package main2 3import (4	"fmt"5	"strings"6)7 8func main() {9	list := "牛奶,面包,鸡蛋"10	items := strings.Split(list, ",")11	fmt.Println(items, len(items))12	fmt.Println(strings.Join(items, "、"))13 14	words := strings.Fields("  我 喜欢   Go  ")15	fmt.Println(words, len(words))16}
输出
[牛奶 面包 鸡蛋] 3
牛奶、面包、鸡蛋
[我 喜欢 Go] 3

第 8 课的数单词,就可以先用 strings.Fields 把一句话切成单词。

数字和字符串互换

"共" + 3 编译不过:字符串和数不能直接加。strconv 包负责互换:strconv.Itoa 把 int 变成字符串,strconv.Atoi 把字符串变成 int。

prog.go
1package main2 3import (4	"fmt"5	"strconv"6)7 8func main() {9	count := 310	label := "共" + strconv.Itoa(count) + "件"11	fmt.Println(label)12 13	n, _ := strconv.Atoi("42")14	fmt.Println(n + 1)15}
输出
共3件
43

Atoi 返回两个值:数,和一个「出错没有」(字符串不是数字时会出错)。这里先用 _ 丢掉第二个,第 14 课细讲。

strings.Builder:一点点拼

在循环里一段段拼字符串,用 strings.Builder:sb.WriteString 往后加(sb. 后面是它自带的函数,叫方法,第 12 课讲),最后 String() 拿到整串。

prog.go
1package main2 3import (4	"fmt"5	"strconv"6	"strings"7)8 9func main() {10	var sb strings.Builder11	for i := range 5 {12		if i > 0 {13			sb.WriteString(", ")14		}15		sb.WriteString(strconv.Itoa((i + 1) * 10))16	}17	fmt.Println(sb.String())18}
输出
10, 20, 30, 40, 50

var sb strings.Builder 的零值就能用。用 += 拼也行,但每次都会建一个新字符串,拼得多时 Builder 快得多。

练一练

写好代码,点检查。提示随便看,实在想不出来再看答案。

练习 1

字节数和字数

写好 sizes:返回字符串的字节数和字数。应该显示下面两行。

要显示
6 2
8 4
exercise1.go
1package main2 3import "fmt"4 5func sizes(s string) (int, int) {6	return 0, 07}8 9func main() {10	b, c := sizes("你好")11	fmt.Println(b, c)12	b, c = sizes("Go语言")13	fmt.Println(b, c)14}
输出
点「运行」或按 Ctrl+Enter
练习 2

整理标签

写好 tidy:把标签变成小写,去掉多余的空格,单词之间用 - 连起来。应该显示 go-web-api 和 hello-world。

要显示
go-web-api
hello-world
exercise2.go
1package main2 3import (4	"fmt"5	"strings"6)7 8func tidy(tag string) string {9	return strings.ToLower(tag)10}11 12func main() {13	fmt.Println(tidy("  Go  web   API "))14	fmt.Println(tidy("Hello World"))15}
输出
点「运行」或按 Ctrl+Enter
练习 3

修好它:数接不到字符串上

这段程序编译不过。改好它,让它显示 共3件 和 共12件。

要显示
共3件
共12件
exercise3.go
1package main2 3import "fmt"4 5func label(n int) string {6	return "共" + n + "件"7}8 9func main() {10	fmt.Println(label(3))11	fmt.Println(label(12))12}
输出
点「运行」或按 Ctrl+Enter

这一课学会了

  • 字符串是只读的一串字节(UTF-8);len(s) 是字节数,一个汉字 3 个字节。
  • s[i] 是一个 byte(一个数);字符串不能改,要不一样的就拼一个新的。
  • for i, r := range s 按字走,r 是 rune;string(r) 变回文字。
  • []rune(s) 一个字一个元素:len([]rune(s)) 是字数,string(runes[:n]) 取前 n 个字。
  • strings 包:Contains ToUpper Repeat Count Split Fields Join,都返回新的字符串。
  • strconv.Itoa 把 int 变成字符串,strconv.Atoi 反过来(还返回一个错误,第 14 课讲)。
  • 在循环里拼字符串用 strings.Builder:WriteString,最后 String()。
这一课学完了吗?点一下标记。进度存在这个浏览器里,随时可以撤销。