1、字符串的定义

字符串是不可变值类型,内部用指针指向 UTF-8 字节数组。

Go 语言中可以使用反引号或者双引号来定义字符串。反引号表示原生的字符串,即不进行转义。Go 语言的字符串不支持单引号

• 默认值是空字符串 ""。
• 用索引号访问某字节,如 s[i]。
• 不能用序号获取字节元素指针,&s[i] 非法。
• 不可变类型,无法修改字节数组。
• 字节数组尾部不包含 NULL。
  • 双引号

字符串使用双引号括起来,其中的相关的转义字符将被替换。例如:

str := "Hello World! \n Hello Gopher! \n"输出:
Hello World!
Hello Gopher!
  • 反引号

字符串使用反引号括起来,其中的相关的转义字符不会被替换。例如:

str :=  `Hello World! \n Hello Gopher! \n` 输出:
Hello World! \nHello Gopher! \n

总结:双引号中的转义字符被替换,而反引号中原生字符串中的 \n 会被原样输出。

2、字符串转义符

Go 语言的字符串常见转义符包含回车、换行、单双引号、制表符等,如下表所示。

转义 含义
\r 回车符(返回行首)
\n 换行符(直接跳到下一行的同列位置)
\t 制表符
单引号
" 双引号
\\ 反斜杠自身

举个例子:

fmt.Println("人生苦短\n我要转go")

3、多行字符串

Go语言中要定义一个多行字符串时,就必须使用反引号字符

s1 := `第一行第二行第三行`fmt.Println(s1)

反引号间换行将被作为字符串中的换行,但是所有的转义字符均无效,文本将会原样输出。

4、byte 和 rune 类型

单引号 可 直接 打印 字符 对应的 ASCII码值

组成每个字符串的元素叫做“字符”,可以通过遍历或者单个获取字符串元素获得字符。 字符用单引号(’)包裹起来,如:

var s1 := 'a'       // 97var s2 := 'A'       // 65

Go 语言的字符有以下两种:

  • uint8类型,或者叫 byte 型,代表了ASCII码的一个字符。
  • rune类型,代表一个 UTF-8字符。

字符串底层是一个byte数组,所以可以和[]byte类型相互转换。字符串是不能修改的 字符串是由byte字节组成,所以字符串的长度是byte字节的长度。 rune类型用来表示utf8字符,一个rune字符由一个或多个byte组成。

当需要处理中文、日文或者其他复合字符时,则需要用到rune类型。rune类型实际是一个int32。 Go 使用了特殊的 rune 类型来处理 Unicode,让基于 Unicode的文本处理更为方便,也可以使用 byte 型进行默认字符串处理,性能和扩展性都有照顾。

实际上,Go语言的range循环在处理字符串的时候,会自动隐式解码UTF8字符串。

// 遍历字符串
func traversalString() {s := "pprof.cn博客"for i := 0; i < len(s); i++ {                   //bytefmt.Printf("%v(%c) ", s[i], s[i])}fmt.Println()for _, r := range s {                            //rune,用于遍历带有中文字符的字符串fmt.Printf("%v(%c) ", r, r)}fmt.Println()
}

输出:

112(p) 112(p) 114(r) 111(o) 102(f) 46(.) 99(c) 110(n) 229(å) 141() 154() 229(å) 174(®) 162(¢)
112(p) 112(p) 114(r) 111(o) 102(f) 46(.) 99(c) 110(n) 21338(博) 23458(客)

因为UTF8编码下一个中文汉字由3~4个字节组成,所以我们不能简单的按照字节去遍历一个包含中文的字符串,否则就会出现上面输出中第一行的结果。

字符串底层是一个byte数组,所以可以和[]byte类型相互转换。字符串是不能修改的 字符串是由byte字节组成,所以字符串的长度是byte字节的长度。 rune类型用来表示utf8字符,一个rune字符由一个或多个byte组成。

5、字符串遍历

5.1 字节长度

字符串的内容(纯字节)可以通过标准索引法来获取,在中括号 [] 内写入索引,索引从 0 开始计数:

  • 字符串 str 的第 1 个字节:str[0] 第 1 个字节

  • str[i - 1] 最后 1 个字节:str[len(str) - 1]

  • 获取字符串所占的字节长度,如:len(str)

注意:内置的len()函数获取的是每个字符的UTF-8编码的长度和,而不是直接的字符数量。

package mainimport ("fmt""unicode/utf8"
)func main() {// 一个中文字符编码需要三个字节s := "其实就是rune"fmt.Println(len(s))                     // "16" ,字节数, 3 * 4 + 4 = 16 字节fmt.Println(utf8.RuneCountInString(s))  // "8"  ,字符数,共 8 个字符数
}

5.2 字符数量

for range循环处理字符时,不是按照字节的方式来处理的。v 其实际上是一个rune类型值。实际上,Go语言的range循环在处理字符串的时候,会自动隐式解码UTF8字符串。如字符串含有中文等字符,我们可以看到每个中文字符的索引值相差3。

package mainimport ("fmt"
)func main() {s := "Go语言四十二章经"for k, v := range s {fmt.Printf("k:%d,v:%c == %d\n", k, v, v)}
}

因为一个中文字符编码需要三个字节,转化单个字节会出现乱码。如字符串含有中文等字符,我们可以看到每个中文字符的索引值相差3。

程序输出:
k:0,v:G == 71
k:1,v:o == 111
k:2,v:语 == 35821
k:5,v:言 == 35328
k:8,v:四 == 22235
k:11,v:十 == 21313
k:14,v:二 == 20108
k:17,v:章 == 31456
k:20,v:经 == 32463

5.3 字符串切片

在 Go 语言中,可以通过字符串切片实现获取子串的功能,切片区间可以对比数学中的区间概念来理解,它是一个左闭右开的区间(索引值非负):

 str := "hello, golang 风清扬"str1 := str[:5]   // 获取索引5(不含)之前的子串str2 := str[7:]   // 获取索引7(含)之后的子串str3 := str[0:5]  // 获取从索引0(含)到索引5(不含)之间的子串str4 := str[:]    // 打印完整的字符串fmt.Println("str1:", str1)fmt.Println("str2:", str2)fmt.Println("str3:", str3)fmt.Println("str4:", str4)

输出:

str1: hello
str2: golang 风清扬
str3: hello
str4: hello, golang 风清扬

6、字符串修改

要修改字符串,需要先将其转换成[]rune或[]byte,完成后再转换为string。无论哪种转换,都会重新分配内存,并复制字节数组

    func changeString() {s1 := "hello"// 强制类型转换byteS1 := []byte(s1)byteS1[0] = 'H'fmt.Println(string(byteS1))s2 := "博客"runeS2 := []rune(s2)runeS2[0] = '狗'fmt.Println(string(runeS2))}

7、字符串类型转换

Go语言中只有强制类型转换,没有隐式类型转换。该语法只能在两个类型之间支持相互转换的时候使用。

强制类型转换的基本语法如下:

T(表达式)

其中,T表示要转换的类型。表达式包括变量、复杂算子和函数返回值等.

比如计算直角三角形的斜边长时使用math包的Sqrt()函数,该函数接收的是float64类型的参数,而变量a和b都是int类型的,这个时候就需要将a和b强制类型转换为float64类型。

    func sqrtDemo() {var a, b = 3, 4var c int// math.Sqrt()接收的参数是float64类型,需要强制转换c = int(math.Sqrt(float64(a*a + b*b)))fmt.Println(c)}

8、strings 包 常用操作

说明:这里说的字符,指得是 rune 类型,即一个 UTF-8 字符(Unicode 代码点)。

8.1 字符串比较

  • 对字符串的比较除了使用内置的==符号来比较,strings标准库还提供了两个方法分别是CompareEqualFold

  • 需要明确的是,字符的比较是通过查出字符对应的ASCII码值,然后进行总和的计算再进行大小的比较

1)Compare

   // Compare 函数,用于比较两个字符串的大小,如果两个字符串相等,返回为 0。// 如果 a 小于 b ,返回 -1 ,反之返回 1 。// 不推荐使用这个函数,直接使用 == != > < >= <= 等一系列运算符更加直观。func Compare(a, b string) int

示例:比较字符串:相等输出0、大于输出1、小于输出-1

 a := "gopher"b := "golang"fmt.Println(strings.Compare(a, b))        // 1    大于 fmt.Println(strings.Compare(a, a))        // 0    相等fmt.Println(strings.Compare(b, a))        // -1   小于

2)EqualFold

   //   EqualFold 函数,计算 s 与 t 忽略字母大小写后是否相等。func EqualFold(s, t string) bool
  • EqualFold相比Compare忽略了字母大小来比较
  • 返回的结果为布尔值,true代表相等,false代表不相等
 fmt.Println(strings.EqualFold("GO", "go"))      // truefmt.Println(strings.EqualFold("1", "一"))       // false

8.2 查询是否存在某个字符或子串

检查的字符串中是否包含有某个字串或字符的需求,比如“abcdefg”是否包含“efg”或者‘a’,strings包主要提供了Contains的方法供开发者使用,返回值是布尔值。

1)Contains方法系

三个函数签名如下:

// 子串 substr 在 s 中,返回 true
func Contains(s, substr string) bool// chars 中任何一个 Unicode 代码点在 s 中,返回 true
// 注意空字符的情况是返回false,与上面的Contains区分开来
func ContainsAny(s, chars string) bool// Unicode 代码点 r 在 s 中,返回 true
func ContainsRune(s string, r rune) bool

示例:

  • Contains
 // Containsfmt.Println(strings.Contains("adbcadeadh", "adb"))      // true,存在fmt.Println(strings.Contains("adbcadeadh", "abc"))      // false,不存在// 注意空子串和空格子串的区别:fmt.Println(strings.Contains("abc", ""))            // true,存在   fmt.Println(strings.Contains("abc", " "))           // false,不存在
  • ContainsAny:但凡字符串s 内包含 chars 任意一个字符(Unicode Code Point) 返回 true
 // ConmtainsAny/*第二个参数 chars 中任意一个字符(Unicode Code Point)如果在第一个参数 s 中存在,则返回 true,否则返回false注意空字符的情况是返回false,与上面的Contains区分开来*/fmt.Println(strings.ContainsAny("team", "b"))          // falsefmt.Println(strings.ContainsAny("team", "t"))          // truefmt.Println(strings.ContainsAny("team", "a & e "))     // truefmt.Println(strings.ContainsAny("team", "a | e "))     // truefmt.Println(strings.ContainsAny("team", "s g "))       // falsefmt.Println(strings.ContainsAny("team", " "))          // falsefmt.Println(strings.ContainsAny("team", ""))           // falsefmt.Println(strings.ContainsAny("", ""))               // falsefmt.Println(strings.ContainsAny("team", "ea"))         // truefmt.Println(strings.ContainsAny("team", "eg"))         // truefmt.Println(strings.ContainsAny("team", "ig"))         // falsefmt.Println(strings.ContainsAny("team", "ge"))         // true
  • ContainsRune 查询单个字符的情况
 // ContainsRune// 查询单个字符的情况,注意 rune 为 单个字符,单引号fmt.Println(strings.ContainsRune("team", 'a'))           // turefmt.Println(strings.ContainsRune("team", 'k'))           // falsefmt.Println(strings.ContainsRune("team", ' '))           // false

2)Contains方法系的内部实现

  • 查看这三个函数的源码,发现它们只是调用了相应的 Index 函数(子串出现的位置)

  • 然后和 0 作比较返回 true 或 fales。

func Contains(s, substr string) bool {return Index(s, substr) >= 0
}

8.3 子串出现次数 ( 字符串匹配 )

在数据结构与算法中,可能会讲解以下字符串匹配算法:

  • 朴素匹配算法
  • KMP 算法
  • Rabin-Karp 算法
  • Boyer-Moore 算法

在 Go 中,查找子串出现次数即字符串模式匹配,实现的是 Rabin-Karp 算法。Count 函数的签名如下:

func Count(s, sep string) int

这里要特别说明一下的是当 sep 为空(“”)时,Count 的返回值是:utf8.RuneCountInString(s) + 1

 fmt.Println(strings.Count("cheese", "e"))         // 3fmt.Println(len("百度中国"))                              // 12 个 字节fmt.Println(strings.Count("百度中国", ""))          // 5, utf8.RuneCountInString(s) + 1fmt.Println(strings.Count("爱学习的你", "学"))       // 1
  • 另外,Count 是计算子串在字符串中出现的无重叠的次数,比如:
 fmt.Println(strings.Count("fiveveve", "vev"))     // 1

8.4 字符或子串在字符串中出现的位置

strings.Index可以在字符串中搜索某个子串,并得到对应子串起始索引下标,若不存在对应子串则返回-1。

// 在 s 中查找 sep 的第一次出现,返回第一次出现的索引
func Index(s, sep string) int

除了对子串进行搜索之外,也可以对某个字节,字符,字符集合进行搜索。

1)获取正向索引,从左往右匹配到第一个

// 字节搜索,在 s 中查找字节 c 的第一次出现,返回第一次出现的索引
func IndexByte(s string, c byte) int// 字符搜索,Unicode 代码点 r 在 s 中第一次出现的位置
func IndexRune(s string, r rune) int// 字符集合搜索,匹配chars中的任何一个字符
// 返回chars 中任何一个 Unicode 代码点在 s 中首次出现的位置
func IndexAny(s, chars string) int// 查找字符 c 在 s 中第一次出现的位置,其中 c 满足 f(c) 返回 true
func IndexFunc(s string, f func(rune) bool) int

示例:

 // 匹配字符第一次出现fmt.Println(strings.Index("golang go go go", "g"))     // 0fmt.Println(strings.Index("golang go go go", "o"))     // 1// 匹配字节,单引号fmt.Println(strings.IndexByte("hello", 'o'))              // 4// 匹配字符,单引号fmt.Println(strings.IndexRune("风华正茂", '风'))           // 0// 匹配字符集合fmt.Println(strings.IndexAny("golang", "l&g"))          // 0fmt.Println(strings.IndexAny("golang", "&o"))           // 1

2)获取反向索引,从右往左 匹配到最后一个

  • strings包也提供了一系列函数获取对应元素的最后一个匹配项的索引下标
  • 对应于上面的每个Index函数,都有一个LastIndex函数

函数声明如下:

// 查找最后一次出现的位置
func LastIndex(s, sep string) intfunc LastIndexByte(s string, c byte) intfunc LastIndexAny(s, chars string) intfunc LastIndexFunc(s string, f func(rune) bool) int

示例:

 // 匹配字符最后一次出现fmt.Println(strings.LastIndex("golang go go go", "go"))     // 13fmt.Println(strings.LastIndexByte("golang go go go", 'o'))     // 14// 匹配字节,单引号fmt.Println(strings.LastIndexByte("hello", 'o'))              // 4// 匹配字符集合fmt.Println(strings.LastIndexAny("golang", "l&g"))          // 5fmt.Println(strings.LastIndexAny("golang", "&o"))           // 1
  • 需要再次注意的是,IndexAnyLastIndexAny 返回的是Unicode码点对应的索引这
  • 比如“你”和“好”都占用3个字节,“你好”匹配“好”,返回的就是3。
 s := "你好帅!你好帅!"fmt.Printf("\"%s\"的字节长度:%d\n", s, len(s))        // 24fmt.Println(strings.LastIndexAny(s, "你"))           // 12

3)IndexFunc 和 LastIndexFunc

IndexFuncLastIndexFunc,用途是查找字符字符串中第一次出现的位置,其中字符满足 func函数 并返回 true

 // 查找字符串中汉字第一次出现的位置,没有汉字返回-1han := func(c rune) bool {return unicode.Is(unicode.Han, c)     // 汉字}// 无匹配fmt.Println(strings.IndexFunc("hello golang", han))        // -1// 匹配 "你"fmt.Println(strings.LastIndexFunc("你好,Golang", han))     // 3 // 匹配 "好"fmt.Println(strings.IndexFunc("你好,Golang", han))         // 0

8.5 字符串分割为[]string

字符串分割很常见,一般分割后会返回对应的字符串切片,strings包提供了六个三组分割函数:Fields 和 FieldsFunc、Split 和 SplitAfter、SplitN 和 SplitAfterN。

1)Fields 和 FieldsFunc

函数的签名如下:

func Fields(s string) []string
func FieldsFunc(s string, f func(rune) bool) []string
Fields
  • Fields一个或多个连续的空格分隔字符串 s,返回子字符串的切片。
  • 如果字符串 s 只包含空格,则返回空列表 ([]string 的长度为 0)。
  • 其中,空格的定义是 unicode.IsSpace

常见间隔符包括:’\t’, ‘\n’, ‘\v’, ‘\f’, ‘\r’, ‘ ‘, U+0085 (NEL), U+00A0 (NBSP)

由于是用空格分隔,因此结果中不会含有空格或空子字符串,例如:

 fmt.Printf("Fields are: %q\n", strings.Fields("  foo bar  baz   "))      //Fields are: ["foo" "bar" "baz"]fmt.Printf("Fields are: %q\n", strings.Fields("  "))                     //Fields are: []
FieldsFunc
  • FieldsFunc 用这样的 Unicode 代码点 c 进行分隔:满足 f© 返回 则true,该函数返回[]string。
  • 如果字符串 s 中所有的代码点 (unicode code points) 都满足 f© 或者 s 是空,则 FieldsFunc 返回空 slice。
  • 也就是说,我们可以通过实现一个回调函数来指定分隔字符串 s 的字符。
  • 比如上面的例子,我们通过 FieldsFunc 来实现:
 // FieldsFunc are: ["foo" "bar" "baz"]fmt.Printf("FieldsFunc are: %q\n", strings.FieldsFunc("  foo bar  baz   ", unicode.IsSpace))// FieldsFunc are: ["a" "b" "c"]fmt.Printf("FieldsFunc are: %q\n", strings.FieldsFunc("a+b+c", func(r rune) bool {return r=='+'}))
  • 通过查看源码发现,实际上,Fields 函数就是调用 FieldsFunc 实现的:

    func Fields(s string) []string {return FieldsFunc(s, unicode.IsSpace)
    }
    

2)Split 和 SplitAfter、 SplitN 和 SplitAfterN

将这四个函数放在一起讲,是因为它们都是通过一个同一个内部函数来实现的。它们的函数签名及其实现:

func Split(s, sep string) []string { return genSplit(s, sep, 0, -1) }
func SplitAfter(s, sep string) []string { return genSplit(s, sep, len(sep), -1) }
func SplitN(s, sep string, n int) []string { return genSplit(s, sep, 0, n) }
func SplitAfterN(s, sep string, n int) []string { return genSplit(s, sep, len(sep), n) }

它们都调用了 genSplit 函数。

  • 这四个函数都是通过 sep 进行分割,返回[]string。

  • 如果 sep 为空,相当于分成一个个的 UTF-8 字符,如 Split("abc",""),得到的是[a b c]。

  • Split(s, sep) 和 SplitN(s, sep, -1) 等价;

  • SplitAfter(s, sep) 和 SplitAfterN(s, sep, -1) 等价。

Split
 fmt.Printf("%q\n", strings.Split("a,b,c,d", ","))    // ["a" "b" "c" "d"]fmt.Printf("%q\n", strings.Split("你是一个大帅比,你就是", "你"))    // ["" "是一个大帅比," "就是"]fmt.Printf("%q\n", strings.Split(" a bc", ""))     // [" " "a" " " "b" "c"]fmt.Printf("%q\n", strings.Split("", "xx"))        // [""]
SplitAfter

那么,Split 和 SplitAfter 有啥区别呢?通过这两句代码的结果就知道它们的区别了:

fmt.Printf("%q\n", strings.Split("foo,bar,baz", ","))
fmt.Printf("%q\n", strings.SplitAfter("foo,bar,baz", ","))

输出:

["foo" "bar" "baz"]
["foo," "bar," "baz"]

也就是说,Split 会将 s 中的 sep 去掉,而 SplitAfter 会保留 sep。

SplitN和SplitAfterN
  • 带 N 的方法可以通过最后一个参数 n 控制返回的结果中的 slice 中的元素个数

  • 当n < 0 时,返回所有的子字符串;

  • 当 n == 0 时,返回的结果是 nil;

  • 当 n > 0 时,表示返回的 slice 中最多只有 n 个元素,其中,最后一个元素不会分割,比如:

 // SplintN, Split 会将 s 中的 sep 去掉// n > 0,表示返回的 slice 中最多只有 n 个元素,其中,最后一个元素不会分割fmt.Printf("%q\n", strings.SplitN("cat,pig,bird", ",", 1))    // ["cat,pig,bird"]fmt.Printf("%q\n", strings.SplitN("cat,pig,bird", ",", 2))    // ["cat" "pig","bird"]fmt.Printf("%q\n", strings.SplitN("cat,pig,bird", ",", 3))    // ["cat" "pig" "bird"]fmt.Printf("%q\n", strings.SplitN("cat,pig,bird", ",", 4))    // ["cat" "pig" "bird"]// n = 0 , 返回的结果是 nilfmt.Printf("%q\n", strings.SplitN("cat, pig, alex", ",", 0))   // []// n < 0, 返回所有的子字符串fmt.Printf("%q\n", strings.SplitN("cat, pig, alex", ",", -1))  // ["cat" " pig" " alex"]
 // SplintAfterN,  SplitAfter 会保留 sep// n > 0,表示返回的 slice 中最多只有 n 个元素,其中,最后一个元素不会分割fmt.Printf("%q\n", strings.SplitAfterN("cat,pig,bird", ",", 1))    // ["cat,pig,bird"]fmt.Printf("%q\n", strings.SplitAfterN("cat,pig,bird", ",", 2))    // ["cat,","pig,bird"]fmt.Printf("%q\n", strings.SplitAfterN("cat,pig,bird", ",", 3))    // ["cat,","pig,","bird"]fmt.Printf("%q\n", strings.SplitAfterN("cat,pig,bird", ",", 4))    // ["cat,","pig,","bird"]// n = 0 , 返回的结果是 nilfmt.Printf("%q\n", strings.SplitAfterN("cat, pig, alex", ",", 0))   // []// n < 0, 返回所有的子字符串fmt.Printf("%q\n", strings.SplitAfterN("cat, pig, alex", ",", -1))  // ["cat" " pig" " alex"]

8.6 字符串是否有某个前缀或后缀

函数源码如下:

// s 中是否以 prefix 开始
func HasPrefix(s, prefix string) bool {return len(s) >= len(prefix) && s[0:len(prefix)] == prefix
}// s 中是否以 suffix 结尾
func HasSuffix(s, suffix string) bool {return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix
}
  • 注:如果 prefix 或 suffix 为"" , 返回值总是true
 fmt.Println(strings.HasPrefix("Gopher", "Go"))         //  truefmt.Println(strings.HasPrefix("Gopher", "C"))          //  falsefmt.Println(strings.HasPrefix("Gopher", ""))           //  truefmt.Println(strings.HasSuffix("Amigo", "go"))          //  truefmt.Println(strings.HasSuffix("Amigo", "Ami"))         //  falsefmt.Println(strings.HasSuffix("Amigo", ""))            //  true

8.7 字符串 JOIN 操作

Join函数用法简单,将字符串数组(或 slice)连接起来可以通过 Join 实现,函数签名如下:

func Join(a []string, sep string) string

示例如下:

 fmt.Println(strings.Join([]string{"name=xxx", "age=xx"}, "&"))   // name=xxx&age=xx

假如没有这个库函数,我们自己实现一个,我们会这么实现:

func Join(str []string, sep string) string {// 特殊情况应该做处理if len(str) == 0 {return ""}if len(str) == 1 {return str[0]}buffer := bytes.NewBufferString(str[0])for _, s := range str[1:] {buffer.WriteString(sep)buffer.WriteString(s)}return buffer.String()
}

这里,我们使用了 bytes 包的 Buffer 类型,避免大量的字符串连接操作(因为 Go 中字符串是不可变的)。我们再看一下标准库的实现:

func Join(a []string, sep string) string {if len(a) == 0 {return ""}if len(a) == 1 {return a[0]}n := len(sep) * (len(a) - 1)for i := 0; i < len(a); i++ {n += len(a[i])}b := make([]byte, n)bp := copy(b, a[0])for _, s := range a[1:] {bp += copy(b[bp:], sep)bp += copy(b[bp:], s)}return string(b)
}

标准库的实现没有用 bytes 包**,当然也不会简单的通过 + 号连接字符串**。Go 中是不允许循环依赖的,标准库中很多时候会出现代码拷贝,而不是引入某个包。这里 Join 的实现方式挺好,我个人猜测,不直接使用 bytes 包,也是不想依赖 bytes 包(其实 bytes 中的实现也是 copy 方式)。

8.8 字符串重复几次

函数签名如下:

func Repeat(s string, count int) string

将 s 重复 count 次,如果 count 为负数或返回值长度 len(s)*count 超出 string 上限会导致 panic,这个函数使用很简单:

 fmt.Println(strings.Repeat("*", 5) + " Go " + strings.Repeat("*", 5))   // ***** Go *****

8.9 字符替换

函数签名如下:

func Map(mapping func(rune) rune, s string) string
  • map 函数,将 s 的每一个字符按照 mapping 的规则做映射替换

  • 如果 mapping 返回值 <0 ,则舍弃该字符

  • 该方法只能对每一个字符做处理,但处理方式很灵活,可以方便的过滤,筛选汉字等

示例:

mapping := func(r rune) rune {switch {case r >= 'A' && r <= 'Z': // 大写字母转小写return r + 32case r >= 'a' && r <= 'z': // 小写字母不处理return rcase unicode.Is(unicode.Han, r): // 汉字换行return '\n'}return -1 // 过滤所有非字母、汉字的字符
}
fmt.Println(strings.Map(mapping, "Hello你#¥%……\n('World\n,好Hello^(&(*界gopher..."))

输出:

hello
world
hello
gopher

8.10 字符串子串替换

  • 进行字符串替换时,考虑到性能问题,能不用正则尽量别用,应该用这里的函数。

  • 字符串替换的函数签名如下:

// 用 new 替换 s 中的 old,一共替换 n 个。
// 如果 n < 0,则不限制替换次数,即全部替换
func Replace(s, old, new string, n int) string// 该函数内部直接调用了函数 Replace(s, old, new , -1)
func ReplaceAll(s, old, new string) string

示例如下:

fmt.Println(strings.Replace("oink oink oink", "k", "ky", 2))               // oinky oinky oink
fmt.Println(strings.Replace("oink oink oink", "oink", "moo", -1))          // moo moo moo
fmt.Println(strings.ReplaceAll("oink oink oink", "oink", "mmm"))           // mmm mmm mmm

如果我们希望一次替换多个,比如我们希望替换 This is <b>HTML</b> 中的 <><>,可以调用上面的函数两次。但标准库提供了另外的方法进行这种替换。

8.11 字符串大小写转换

  • 大小写转换包含了 4 个相关函数
  • ToLower,ToUpper 用于大小写转换
  • ToLowerSpecial,ToUpperSpecial 可以转换特殊字符的大小写

函数签名如下:

func ToLower(s string) string
func ToLowerSpecial(c unicode.SpecialCase, s string) string
func ToUpper(s string) string
func ToUpperSpecial(c unicode.SpecialCase, s string) string

示例如下:

 fmt.Println(strings.ToLower("HELLO WORLD"))       // hello worldfmt.Println(strings.ToLower("Ā Á Ǎ À"))           // ā á ǎ àfmt.Println(strings.ToLowerSpecial(unicode.TurkishCase, "壹"))              // 壹fmt.Println(strings.ToLowerSpecial(unicode.TurkishCase, "HELLO WORLD"))     // hello worldfmt.Println(strings.ToLower("Önnek İş"))                               // önnek işfmt.Println(strings.ToLowerSpecial(unicode.TurkishCase, "Önnek İş"))   // önnek işfmt.Println(strings.ToUpper("hello world"))   // HELLO WORLDfmt.Println(strings.ToUpper("ā á ǎ à"))       // Ā Á Ǎ Àfmt.Println(strings.ToUpperSpecial(unicode.TurkishCase, "一"))   // 一fmt.Println(strings.ToUpperSpecial(unicode.TurkishCase, "hello world"))  // HELLO WORLDfmt.Println(strings.ToUpper("örnek iş"))   //  ÖRNEK IŞfmt.Println(strings.ToUpperSpecial(unicode.TurkishCase, "örnek iş"))  // ÖRNEK İŞ

8.12 字符串标题处理

  • 标题处理包含 3 个相关函数
  • 其中 Title 会将 s 每个单词的首字母大写,不处理该单词的后续字符。
  • ToTitle 将 s 的每个字母大写
  • ToTitleSpecial 将 s 的每个字母大写,并且会将一些特殊字母转换为其对应的特殊大写字母。

函数签名如下:

func Title(s string) string
func ToTitle(s string) string
func ToTitleSpecial(c unicode.SpecialCase, s string) string

举例如下:

 fmt.Println(strings.Title("hElLo wOrLd"))          // HElLo WOrLdfmt.Println(strings.ToTitle("hElLo wOrLd"))        // HELLO WORLDfmt.Println(strings.ToTitleSpecial(unicode.TurkishCase, "hElLo wOrLd"))  // HELLO WORLDfmt.Println(strings.Title("āáǎà ōóǒò êēéěè"))    // Āáǎà Ōóǒò Êēéěèfmt.Println(strings.ToTitle("āáǎà ōóǒò êēéěè"))  // ĀÁǍÀ ŌÓǑÒ ÊĒÉĚÈfmt.Println(strings.ToTitleSpecial(unicode.TurkishCase, "āáǎà ōóǒò êēéěè"))  // ĀÁǍÀ ŌÓǑÒ ÊĒÉĚÈfmt.Println(strings.Title("dünyanın ilk borsa yapısı Aizonai kabul edilir"))// Dünyanın Ilk Borsa Yapısı Aizonai Kabul Edilirfmt.Println(strings.ToTitle("dünyanın ilk borsa yapısı Aizonai kabul edilir"))// DÜNYANIN ILK BORSA YAPISI AIZONAI KABUL EDILIRfmt.Println(strings.ToTitleSpecial(unicode.TurkishCase, "dünyanın ilk borsa yapısı Aizonai kabul edilir"))// DÜNYANIN İLK BORSA YAPISI AİZONAİ KABUL EDİLİR

8.13 字符串修剪

函数签名:

// 将 s 左侧和右侧中匹配 cutset 中的任一字符的字符去掉
func Trim(s string, cutset string) string
// 将 s 左侧的匹配 cutset 中的任一字符的字符去掉
func TrimLeft(s string, cutset string) string
// 将 s 右侧的匹配 cutset 中的任一字符的字符去掉
func TrimRight(s string, cutset string) string
// 如果 s 的前缀为 prefix 则返回去掉前缀后的 string , 否则 s 没有变化。
func TrimPrefix(s, prefix string) string
// 如果 s 的后缀为 suffix 则返回去掉后缀后的 string , 否则 s 没有变化。
func TrimSuffix(s, suffix string) string
// 将 s 左侧和右侧的间隔符去掉。常见间隔符包括:'\t', '\n', '\v', '\f', '\r', ' ', U+0085 (NEL)
func TrimSpace(s string) string
// 将 s 左侧和右侧的匹配 f 的字符去掉
func TrimFunc(s string, f func(rune) bool) string
// 将 s 左侧的匹配 f 的字符去掉
func TrimLeftFunc(s string, f func(rune) bool) string
// 将 s 右侧的匹配 f 的字符去掉
func TrimRightFunc(s string, f func(rune) bool) string

包含了 9 个相关函数用于修剪字符串。

举例如下:

x := "!!!@@@你好,!@#$ Gophers###$$$"
fmt.Println(strings.Trim(x, "@#$!%^&*()_+=-"))
fmt.Println(strings.TrimLeft(x, "@#$!%^&*()_+=-"))
fmt.Println(strings.TrimRight(x, "@#$!%^&*()_+=-"))
fmt.Println(strings.TrimSpace(" \t\n Hello, Gophers \n\t\r\n"))
fmt.Println(strings.TrimPrefix(x, "!"))
fmt.Println(strings.TrimSuffix(x, "$"))f := func(r rune) bool {return !unicode.Is(unicode.Han, r) // 非汉字返回 true
}
fmt.Println(strings.TrimFunc(x, f))
fmt.Println(strings.TrimLeftFunc(x, f))
fmt.Println(strings.TrimRightFunc(x, f))

输出如下:

你好,!@#$ Gophers
你好,!@#$ Gophers###$$$
!!!@@@你好,!@#$ Gophers
Hello, Gophers
!!@@@你好,!@#$ Gophers###$$$
!!!@@@你好,!@#$ Gophers###$$
你好
你好,!@#$ Gophers###$$$
!!!@@@你好

8.14 Replacer 类型

  • 这是一个结构,没有导出任何字段
  • 实例化通过 func NewReplacer(oldnew ...string) *Replacer 函数进行,其中不定参数 oldnew 是 old-new 对,即进行多个替换。
  • 如果 oldnew 长度与奇数,会导致 panic.

示例:

r := strings.NewReplacer("<", "&lt;", ">", "&gt;")
fmt.Println(r.Replace("This is <b>HTML</b>!"))

输出结果:

This is &lt;b&gt;HTML&lt;/b&gt;!

另外,Replacer 还提供了另外一个方法,它在替换之后将结果写入 io.Writer 中。

func (r *Replacer) WriteString(w io.Writer, s string) (n int, err error)

8.15 Builder 类型

Builder 结构如下:

type Builder struct {addr *Builder // of receiver, to detect copies by valuebuf  []byte
}
  • 该类型实现了 io 包下的 Writer, ByteWriter, StringWriter 等接口,可以向该对象内写入数据
  • Builder 没有实现 Reader 等接口,所以该类型不可读,但提供了 String 方法可以获取对象内的数据。

函数签名:

// 该方法向 b 写入一个字节
func (b *Builder) WriteByte(c byte) error// WriteRune 方法向 b 写入一个字符
func (b *Builder) WriteRune(r rune) (int, error)// WriteRune 方法向 b 写入字节数组 p
func (b *Builder) Write(p []byte) (int, error)// WriteRune 方法向 b 写入字符串 s
func (b *Builder) WriteString(s string) (int, error)// Len 方法返回 b 的数据长度。
func (b *Builder) Len() int// Cap 方法返回 b 的 cap。
func (b *Builder) Cap() int// Grow 方法将 b 的 cap 至少增加 n (可能会更多)。如果 n 为负数,会导致 panic。
func (b *Builder) Grow(n int)// Reset 方法将 b 清空 b 的所有内容。
func (b *Builder) Reset()// String 方法将 b 的数据以 string 类型返回。
func (b *Builder) String() string
  • Builder 有 4 个与写入相关的方法,这 4 个方法的 error 都总是为 nil.
  • Buildercap 会自动增长,一般不需要手动调用 Grow 方法。
  • String 方法可以方便的获取 Builder 的内容。

示例:

b := strings.Builder{}
_ = b.WriteByte('7')
n, _ := b.WriteRune('夕')
fmt.Println(n)
n, _ = b.Write([]byte("Hello, World"))
fmt.Println(n)
n, _ = b.WriteString("你好,世界")
fmt.Println(n)
fmt.Println(b.Len())
fmt.Println(b.Cap())
b.Grow(100)
fmt.Println(b.Len())
fmt.Println(b.Cap())
fmt.Println(b.String())
b.Reset()
fmt.Println(b.String())

输出结果:

3
12
15
31
32
31
164
7夕Hello, World你好,世界

8.16 Reader 类型

看到名字就能猜到,这是实现了 io 包中的接口。

它实现了:

  • io.Reader(Read 方法)
  • io.ReaderAt(ReadAt 方法)
  • io.Seeker(Seek 方法)
  • io.WriterTo(WriteTo 方法)
  • io.ByteReader(ReadByte 方法)
  • io.ByteScanner(ReadByte 和 UnreadByte 方法)
  • io.RuneReader(ReadRune 方法)
  • io.RuneScanner(ReadRune 和 UnreadRune 方法)

Reader 结构如下:

type Reader struct {s        string    // Reader 读取的数据来源i        int // current reading index(当前读的索引位置)prevRune int // index of previous rune; or < 0(前一个读取的 rune 索引位置)
}

可见 Reader 结构没有导出任何字段,而是提供一个实例化方法:

func NewReader(s string) *Reader

该方法接收一个字符串,返回的 Reader 实例就是从该参数字符串读数据。在后面学习了 bytes 包之后,可以知道 bytes.NewBufferString 有类似的功能,不过,如果只是为了读取,NewReader 会更高效。

其他方法不介绍了,都是之前接口的实现,有兴趣的可以看看源码实现,大部分都是根据 i、prevRune 两个属性来控制。

9、字符串相关补充

标准库中有四个包对字符串处理尤为重要:bytes、strings、strconv和unicode包。

  • strings包提供了许多如字符串的查询、替换、比较、截断、拆分和合并等功能。

  • bytes包也提供了很多类似功能的函数,但是针对和字符串有着相同结构的[]byte类型。因为字符串是只读的,因此逐步构建字符串会导致很多分配和复制。在这种情况下,使用bytes.Buffer类型将会更有效,稍后我们将展示。

  • strconv包提供了布尔型、整型数、浮点数和对应字符串的相互转换,还提供了双引号转义相关的转换。

  • unicode包提供了IsDigit、IsLetter、IsUpper和IsLower等类似功能,它们用于给字符分类

strings 包提供了很多操作字符串的简单函数,通常一般的字符串操作需求都可以在这个包中找到。

下面简单举几个例子:

  • 判断是否以某字符串打头/结尾 strings.HasPrefix(s, prefix string) bool strings.HasSuffix(s, suffix string) bool

  • 字符串分割 strings.Split(s, sep string) []string

  • 返回子串索引 strings.Index(s, substr string) int strings.LastIndex 最后一个匹配索引

  • 字符串连接 strings.Join(a []string, sep string) string 另外可以直接使用“+”来连接两个字符串

  • 字符串替换 strings.Replace(s, old, new string, n int) string

  • 字符串转化为大小写 strings.ToUpper(s string) string strings.ToLower(s string) string

  • 统计某个字符在字符串出现的次数 strings.Count(s, substr string) int

  • 判断字符串的包含关系 strings.Contains(s, substr string) bool

Go 字符串及strings包常见操作相关推荐

  1. Python中字符串的介绍以及常见操作

    1.字符串的介绍 python中的字符串格式: 如下定义的变量a,存储的是数字类型的值 a = 100 如下定义的变量b,存储的是字符串类型的值 b = "hello python" ...

  2. ida 字符串查找_IDA的常见操作

    IDA-python的使用:常用的api get_bytes(address,count)从address处读取count个字节的内容 patch_bytes(address,buf),将adress ...

  3. Go语言---strings包(字符串操作)

    strings标准库包主要涉及字符串的基本操作. 常见字符串的操作有: 字符串求长度 求子串 是否存在某个字符或者子串 子串出现的次数(字符串匹配) 字符串分割(切分)成[]string 字符串是否存 ...

  4. 【转】Go语言---strings包(字符串操作)

    2019独角兽企业重金招聘Python工程师标准>>> strings标准库包主要涉及字符串的基本操作. 常见字符串的操作有: 字符串求长度 求子串 是否存在某个字符或者子串 子串出 ...

  5. python设置字符间距_Python中字符串的常见操作技巧总结

    本文实例总结了Python中字符串的常见操作技巧.分享给大家供大家参考,具体如下: 反转一个字符串 >>> S = 'abcdefghijklmnop' >>> S ...

  6. python基础实例-Python基础之字符串常见操作经典实例详解

    本文实例讲述了Python基础之字符串常见操作.分享给大家供大家参考,具体如下: 字符串基本操作 切片 # str[beg:end] # (下标从 0 开始)从下标为beg开始算起,切取到下标为 en ...

  7. python 字符串操作_python中字符串的常见操作(一)

    如有字符串: str1 = '192.168.1.1' str2 = 'asdfghjk' str3 = 'Asd fg hj ki' str4 = ' ' str5 = '' 以下是常见操作: # ...

  8. C#字符串常见操作总结详解

    C#字符串常见操作总结详解 本篇文章是对C#中字符串的常见操作进行了详细的总结介绍,需要的朋友参考下 (1)取字符串长度       <string>.Length; (2)字符串转为比特 ...

  9. python字符串常见操作

    字符串常见操作 如有字符串mystr = 'hello world itcast and itcastcpp',以下是常见的操作 <1>find 检测 str 是否包含在 mystr中,如 ...

最新文章

  1. STC12C5A60S2 内部AD+1602显示
  2. Entity framewrok (linq to entity)查询优化的一点摸索
  3. 动态数据绑定之监听对象变化
  4. 四年级下册英语计算机房和教师办公室的图片,PEP英语四年级下册-Unit-1思维导图及知识点梳理.pptx...
  5. Java操作XML文件 dom4j 篇【转】
  6. JVM垃圾回收机制总结(5) :JDK垃圾收集器的配置命令
  7. 没有测量,就没有管理
  8. DEV ImageComboxEdit 使用
  9. NOIP2017洛谷P3953:逛公园(分层图最短路、dp、拓扑)
  10. 很有用很有效的操作之批量操作一组图片
  11. 【华为敏捷/DevOps实践】8. 持续交付,持续部署,傻傻分不清楚
  12. C++ 关联容器set | map | multiset | multimap
  13. 黑苹果hidp显示不清楚_魔兽世界9.0:盟约指挥台不显示胜率?搞清楚机制,轻松收菜!...
  14. json,pickle,shelve序列化和反序列化
  15. java回车不终止_java 在console行输入一串String后回车,仍无法停止。 下面的程序是计算相似度的,当用户输入关键字后...
  16. 有关坐标系常见问题的问与答(转自ESRI中国社区)
  17. python库之SnowNLP(自然语言处理)
  18. 「周末观赛指南」国足生死战 NBA将演“大结局”?
  19. 链接脚本在编程中的高级运用之一:可变长数组
  20. 浏览器闪退问题解决汇总(需配合360)

热门文章

  1. Excel/CSV 表格中被转换的E + 如何恢复
  2. 企业信用报告有哪些用途?
  3. 那些年我们的现代‘毕昇’
  4. 【知乎高赞】在一线城市有很多套房,是什么体验?
  5. [Asp.net]web.config customErrors 如何设置?
  6. 圆的内接三角形这样画最方便
  7. java 实现 string类_java 中String类的常用方法总结,带你玩转String类。
  8. java List截取一段数据
  9. Logback configuration error detected:
  10. 富士康登陆A股 工业互联网的盛宴