字符串和 rune
先猜一猜:「你好」有几个字?两个。可是 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,显示出来是一个数:
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] 正好是这三个字节,所以显示「语」。
字符串不能改
字符串是只读的:不能改其中的一个字节。
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(字的编号,也是一个数)。
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 切片,一个字一个元素。数字数、按字取,都用它:
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}
「猫」是 3 个字节,cat 是 3 个,len(s) 是 6。变成 []rune 以后一个字一个元素:猫、c、a、t,一共 4 个。
strings 包
strings 包里是处理字符串的常用函数。它们都不改原来的字符串,而是返回新的:
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 用分隔符把切片拼成一个字符串:
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。
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() 拿到整串。
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 快得多。
练一练
写好代码,点检查。提示随便看,实在想不出来再看答案。
字节数和字数
✓写好 sizes:返回字符串的字节数和字数。应该显示下面两行。
6 2 8 4
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}
整理标签
✓写好 tidy:把标签变成小写,去掉多余的空格,单词之间用 - 连起来。应该显示 go-web-api 和 hello-world。
go-web-api hello-world
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}
修好它:数接不到字符串上
✓这段程序编译不过。改好它,让它显示 共3件 和 共12件。
共3件 共12件
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}
这一课学会了
- 字符串是只读的一串字节(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包:ContainsToUpperRepeatCountSplitFieldsJoin,都返回新的字符串。strconv.Itoa把 int 变成字符串,strconv.Atoi反过来(还返回一个错误,第 14 课讲)。- 在循环里拼字符串用
strings.Builder:WriteString,最后String()。