在 Golang 中, 字符串是一种基本类型, 这一点和 C 语言不同. C 语言没有原生的字符串类型, 而是使用字符数组来表示字符串, 并以字符指针来传递字符串. Golang 中的字符串是一个不可改变的 UTF-8 字符序列, 一个 ASCII 码占用 1 个字节, 其它字符根据需要占用 2-4 个字节, 这一点与其它主流的开发语言 ( C++,Java,Python) 是不同的. 这样设计的好处有两个:
减少内存的使用, 节约硬盘空间
统一编码格式 (UTF-8) 有助于减少读取文件时的编码和解码工作
字符串的声明与初始化
声明和初始化字符串非常容易:
s := "hello world"
上面的代码声明了字符串变量 s, 其内容为 "hello world". 在 Golang 中字符串的值是不可变的, 当创建一个字符串后, 无法再次修改这个字符串的内容. 所以如果你通过下面的代码修改 s 中的内容就会发生编译错误:
s := "hello nick"
字符串字面量
Golang 支持两种类型的字符串字面量:
解释型字符串
非解释型字符串
所谓的解释型字符串就是用双引号括起来的字符串(""), 其中的转义字符会被替换掉, 这些转义字符包括:
- \a // 响铃
- \b // 退格
- \f // 换页
- \n // 换行
- \r // 回车
- \t // 制表符
- \u // Unicode 字符
- \v // 垂直制表符
- \" // 双引号
- \\ // 反斜杠
非解释型字符串是指用反引号 ( ` 一般在 Esc 键下面, 数字键 1 的左边) 括起来的字符串. 在非解释型字符串中的转义字符不会被解释, 并且还支持换行.
看下面的 demo:
- package main
- import "fmt"
- func main() {
- s1 := "Hello\nWorld!"
- s2 := `Hello\n
- nick!`
- fmt.Println(s1)
- fmt.Println(s2)
- }
运行上面的代码, 输出如下:
反单引号可以跨行, 并且引号内的所有内容都会直接输出, 包括转义字符和空格缩进等. 而双引号则不能换行, 并且会解析转义字符.
字符串的长度
内置函数 len() 可以返回一个字符串中的字节数(注意, 不是 rune 字符数目), 索引操作 s[i] 可以返回字符串 s 中第 i 个字节的值.
- s := "abc 你"
- fmt.Printf("字符串的字节长度是:%d\n", len(s))
- for i := 0; i < len(s); i++ {
- fmt.Println(s[i])
- }
字符串的字节长度是: 6
- 97 // a
- 98 // b
- 99 // c
- 228 // 你
- 189 // 你
- 160 // 你
最后的三个字节组成了汉字 "你".
如果要获取字符串中字符的个数, 可以先把字符串转换成 []rune 类型, 然后用 len() 函数获取字符个数:
- s := "abc 你"
- r := []rune(s)
- fmt.Print(len(r))
这次输出的结果为: 4.
从字符串中截取内容
可以通过下面的语法截取字符串中的内容:
- s := "abcdef"
- s1 := s[1:4]
此时 s1 的内容为 "bcd", 该语法通过下标索引的方式截取字符串中的内容, 特点是 "左含右不含". 也就是说新的子串包含源串索引为 1 的字符, 而不包含源串索引为 4 的字符.
如果要从源串的开始处截取可以省略第一个索引:
s2 := s[:4]
s2 的内容为 "abcd".
如果要从源串的某个位置开始一直截取到末尾, 可以省略第二个索引:
s3 := s[2:]
s3 的内容为 "cdef".
访问越界问题
在通过索引访问字符串或者是截取子串时需要考虑索引越界的问题, 如果试图访问超出字符串索引范围的字节将会在运行时导致 panic 异常:
s4 := s[2:10]
连接字符串
使用 + 号可轻松的把字符串连接起来:
- s := "hello"
- s1 := " "
- s2 := "world"
- s3 := s + s1 + s2
此时 s3 的内容为 "hello world".
遍历字符串
由于可以通过下标索引字符串中的字节, 所以可以用这种方式遍历字符串:
- s := "abc 你好"
- for i := 0; i < len(s); i++ {
- fmt.Printf("%c", s[i])
- }
输出的结果如下:
abcä½ å¥½
可见在字符串中含有非单字节的字符时这种方法是不正确的. range 函数能解决这个问题:
- for _, v := range s {
- fmt.Printf("%c", v)
- }
这次输出的结果为:
abc 你好
修改字符串
在 Golang 中, 不能修改字符串的内容, 也就是说不能通过 s[i] 这种方式修改字符串中的字符. 要修改字符串的内容, 可以先将字符串的内容复制到一个可写的变量中, 一般是 []byte 或 []rune 类型的变量, 然后再进行修改.
如果要对字符串中的字节进行修改, 就转换为 []byte 类型, 如果要对字符串中的字符修改, 就转换为 []rune 类型, 在转换类型的过程中会自动复制数据.
修改字符串中的字节(用 []byte)
对于那些单字节字符来说, 可以通过这种方式进行修改:
- s := "Hello 世界"
- b := []byte(s) // 转换为 []byte, 数据被自动复制
- b[5] = ',' // 把空格改为半角逗号
- fmt.Printf("%s\n", s)
- fmt.Printf("%s\n", b)
输出结果为:
Hello 世界
Hello, 世界
修改字符串中的字符(用 []rune)
- s := "Hello 世界"
- b := []rune(s) // 转换为 []rune, 数据被自动复制
- b[6] = '中'
- b[7] = '国'
- fmt.Println(s)
- fmt.Println(string(b))
输出结果为:
Hello 世界
Hello 中国
注意: 和 C/C++ 不一样, Golang 语言中的字符串是根据长度限定的, 而非特殊的字符 \0.string 类型的 0 值是长度为 0 的字符串, 即空字符串 "".
strings 包
strings 是非常重要的一种基本类型, 所需要执行的操作繁多且比较复杂, 因此一般的编程语言都会额外封装一些方法用于处理字符串. Golang 语言的标准库中也存在这样一个名称为 strings 的库. 下面介绍一些 strings 库的常见用法.
检查是否包含子串
判断一个字符串中是否包含某个子串是经常遇到的一种字符串操作, 在 strings 包中, 可以使用 Contains() 函数进行判断:
- s := "A good tree bears good fruit"
- fmt.Printf("%t\n", strings.Contains(s, "tree"))
输出的结果为: true.
如果要检查字符串是不是以某个子串开始的, 可以使用 HasPrefix() 函数:
- s := "A good tree bears good fruit"
- fmt.Printf("%t\n", strings.HasPrefix(s, "A good"))
输出的结果为: true.
如果要检查字符串是不是以某个子串结束的, 可以使用 HasSuffix() 函数:
- s := "A good tree bears good fruit"
- fmt.Printf("%t\n", strings.HasSuffix(s, "good fruit"))
输出的结果为: true.
与 Contains() 函数相比, ContainsAny() 函数能够匹配更广泛的内容, 并且可以匹配 Unicode 字符:
- fmt.Println(strings.Contains("failure", "a & o")) // false
- fmt.Println(strings.Contains("foo", "")) // true
- fmt.Println(strings.Contains("","")) // true
- fmt.Println(strings.ContainsAny("failure", "a & o")) // true
- fmt.Println(strings.ContainsAny("foo", "")) // false
- fmt.Println(strings.ContainsAny("","")) // false
- fmt.Println(strings.ContainsAny("好树结好果", "好树")) // true
获取子串的索引
在 Golang 中, 字符串中的字符都有一个索引值, 很多时候我们要操作字符串, 就必须先获取字符在字符串中的索引值. 在 strings 包中 Index 函数可以返回指定字符或字符串的第一个字符的索引值, 如果不存在则返回 -1:
- fmt.Println(strings.Index("Hi I'm Nick, Hi","Nick")) // 7
- fmt.Println(strings.Index("Hi I'm Nick, Hi","Hi")) // 0
- fmt.Println(strings.Index("Hi I'm Nick, Hi","abc")) // -1
- fmt.Println(strings.LastIndex("Hi I'm Nick, Hi","Hi")) // 13
LastIndex 函数返回匹配到的最后一个子串的索引值.
如果处理包含多个字节组成的字符的字符串, 需要使用 IndexRune 函数来对字符进行定位:
fmt.Println(strings.IndexRune("好树结好果", '树')) // 3
注意这里返回的是 3, 这是 "树" 的第一个字节在字符串中的位置.
替换字符串
替换字符串最常用的方式其实是通过正则匹配去替换的, 其灵活度更高. 而 Golang 则为比较基础的替换操作提供了 Replace 函数:
fmt.Println(strings.Replace("你好世界", "世界", "地球", 1))
输出的结果为: 你好地球
strings.Replate(str, old, new, n) 函数一共有 4 个参数, 第一个为源字符串, 第二个表示源字符串中需要被替换掉的字符串, 第三个是替换的内容, 最后一个 n 则表示替换匹配到的前 n 个记录.
大小写转换
操作字符串就免不了大小写转换, ToLower() 函数把字符串转换为小写, ToUpper() 函数把字符串转换为大写:
- s := "A good tree bears good fruit"
- s1 := "HOW ARE YOU?"
- fmt.Printf("%s\n", strings.ToUpper(s))
- fmt.Printf("%s\n", strings.ToLower(s1))
输出的结果如下:
- A GOOD TREE BEARS GOOD FRUIT
- how are you?
修剪
在处理用户的输入时, 去掉字符串前后多余的空白字符非常重要, strings 包中提供了 Trem(),TrimLeft() 和 TrimRight() 来实现这个功能:
- fmt.Printf("%q\n", strings.Trim("Golang", " "))
- fmt.Printf("%q\n", strings.TrimLeft("Golang", " "))
- fmt.Printf("%q\n", strings.TrimRight("Golang", " "))
输出的结果如下:
- "Golang"
- "Golang"
- "Golang"
分隔与拼接
Split() 函数按照指定的分隔符分隔字符串并返回一个切片:
- fmt.Printf("%q\n", strings.Split("a,b,c", ","))
- fmt.Printf("%q\n", strings.Split("a boy a girl a cat", "a"))
- fmt.Printf("%q\n", strings.Split("xyz", ""))
输出的结果如下:
- ["a" "b" "c"]
- ["""boy" "girl" "cat"]
- ["x" "y" "z"]
Join() 函数则会将元素类型为 string 的切片使用分隔符拼接组成一个字符串:
fmt.Printf("%q\n", strings.Join([]string{"boy", "girl", "cat"}, ";"))
输出的结果如下:
"boy;girl;cat"
strconv 包
这个包主要用于字符串与其他类型的转换. 这里我们简单的看下如何通过 Itoa() 函数把字符串转换为整型:
- num, _ := strconv.Atoi("123")
- num += 5
- fmt.Printf("%d\n", num)
上面这段代码输出的结果为: 128
这说明字符串 "123" 被成功的转换成了十进制整数 123, 随后还进行了加法运算.
总结
对于任何一门编程语言来说, 字符串的定义和相关操作都是非常基础的内容. 从本文我们可以看到, Golang 的字符串类型原生支持 Unicode, 操作起来也非常的方便, 特别是提供了便利的 strings 包. 这让我们能够轻松的了解并使用 Golang 的 string 类型.
参考:
《Go 语言编程入门与实战技巧》
来源: https://www.cnblogs.com/sparkdev/p/10666085.html