Z.S.K.'s Records

golang学习(string)

字符串在所有语言中可以说是用的最频繁的一种数据结构, 在golang中当然也存在.虽然字符串往往被看做一个整体,但是实际上字符串是一片连续的内存空间,我们也可以将它理解成一个由字符组成的数组

golang中的字符串是个只读的, 它实际上是由字符组成的数组,会占用一片连续的内存空间, 这里的只读是说无法直接改变字符串, 在运行时我们其实还是可以将这段内存拷贝到堆或者栈上,将变量的类型转换成 []byte 之后就可以进行,修改后通过类型转换就可以变回 string,Go 语言只是不支持直接修改 string 类型变量的内存空间

数据结构

1
2
3
4
type StringHeader struct {
Data uintptr
Len int
}

字符串定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 使用"", 如果字符串内部出现双引号,需要使用 \ 符号转义
aString := "我\"是\"中国人"
cString := "我\n是中国人" // 使用显式的换行也是可以的
dString := "我
是中国人" // 这样是不行的

// 使用``, 可以由多行组成,但不支持转义,并且可以包含除了反引号外其他所有字符
bString := `我

中国人
!@
`
fmt.Println(aString, "\n", bString)
fmt.Println(bString[1]) // 136 这里输出的是unicode码

常用操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 访问
// 由于字符串的底层是个字节数组, 因此也是通过下标进行访问, 如果下标越界会提示index out of range
aString[1]
// 如果强制去修改字符串的元素会提示 cannot assign to xxx

// 求长度
// 直接使用len(bString)即可求得长度
// 字符串是 UTF-8 字符的一个序列(当字符为 ASCII 码时则占用 1 个字节,其它字符根据需要占用 2-4 个字节)

bString := "我是中国人" // 长度15, 一个汉字占3个字节
cString := "我是\n中国人" // 长度16, \n是可以用ascii表示, 因此只占用一个字节
fString := "youareme" // 长度8

// 拼接
dString := aString + bString
dString += aString // == dString = dString + aString

// 拷贝
eString := aString
// 但是在正常情况下,运行时会调用 copy 将输入的多个字符串拷贝到目标字符串所在的内存空间中,新的字符串是一片新的内存空间,与原来的字符串也没有任何关联,一旦需要拼接的字符串非常大,拷贝带来的性能损失就是无法忽略的

// 切片
// 由于字符串底组是字节数组, 因此也支持切片操作
// gString跟eString指向的底层数组为同一个
gString := eString[2:3]

类型转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
bString := "abc我是中国人"
for i, v := range bString {
fmt.Printf("(%d, %x) ", i, v)
}
fmt.Println()
// (0, 61) (1, 62) (2, 63) (3, 6211) (6, 662f) (9, 4e2d) (12, 56fd) (15, 4eba)
// 遍历bString,取出的是Unicode码,可以看到“我”这个汉字的Unicode是 6211,它是从第3个开始的

for i, v := range []byte(bString) {
fmt.Printf("(%d, %x) ", i, v)
}
fmt.Println()
// (0, 61) (1, 62) (2, 63) (3, e6) (4, 88) (5, 91) (6, e6) (7, 98) (8, af) (9, e4) (10, b8) (11, ad) (12, e5) (13, 9b) (14, bd) (15, e4) (16, ba) (17, ba)
// 把字符串转为[]bype数组,打印出utf-8的字节,可以看到“我”这个汉字占了e6 88 91三个16进制字节。看到一个汉字占3个字节,一个英文字母占1个字节,utf-8是可变宽度

for i, v := range []rune(bString) {
fmt.Printf("(%d, %c) ", i, v)
}
fmt.Println()
// (0, a) (1, b) (2, c) (3, 我) (4, 是) (5, 中) (6, 国) (7, 人)
// 将字符串转为[]rune 也就是int32的别名,占4个字节。打印出了abc好好学习。

strings包的使用

strings包中包含非常多的实用函数, 这里挑几个比较常用的记录一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 判断给定字符串s中是否包含子串substr, 找到返回true, 找不到返回false
fmt.Println("包含子串返回:", strings.Contains("oldboy", "boy")) // true

// 在字符串s中查找sep所在的位置, 返回位置值, 找不到返回-1, 如果存在多个, 只返回第一个匹对的位置
fmt.Println("存在返回第一个匹配字符的位置:", strings.Index("heloboyboy", "boy")) // 4

// 统计给定子串sep的出现次数, sep为空时, 返回字符串的长度 + 1
fmt.Println("子字符串出现次数:", strings.Count("hello world", "o")) // 2
fmt.Println("子字符串为空时, 返回:", strings.Count("hello", "")) // 6

// 重复s字符串count次, 最后返回新生成的重复的字符串
fmt.Println(strings.Repeat("嘀嗒", 4), "时针它不停在转动")

// 在s字符串中, 把old字符串替换为new字符串,n表示替换的次数,如果n<0会替换所有old子串
fmt.Println(strings.Replace("hel hel hel", "l", "llo", 2)) // hello hello hel
fmt.Println(strings.Replace("hel hel hel", "l", "llo", -1)) // hello hello hello

// 删除在s字符串的头部和尾部中由cutset指定的字符, 并返回删除后的字符串
fmt.Println(strings.Trim(" hello ", " ")) // hello

// 给定字符串转换为英文标题的首字母大写的格式(不能正确处理unicode标点)
fmt.Println(strings.Title("It is never too late to learn."))
// 返回将所有字母都转为对应的小写版本的拷贝
fmt.Println(strings.ToLower("It Is Never Too Late To Learn."))
// 返回将所有字母都转为对应的大写版本的拷贝
fmt.Println(strings.ToUpper("It is never too late to learn."))
// It Is Never Too Late To Learn.
// it is never too late to learn.
// IT IS NEVER TOO LATE TO LEARN.

// 判断字符串是否包含前缀prefix,大小写敏感
fmt.Println("前缀是以hello开头的:", strings.HasPrefix("helloworld", "hello")) // true

// 判断s是否有后缀字符串suffix,大小写敏感
fmt.Println("后缀是以world开头的:", strings.HasSuffix("helloworld", "world")) // true

// 用去掉s中出现的sep的方式进行分割,会分割到结尾,并返回生成的所有片段组成的切片(每一个sep都会进行一次切割,即使两个sep相邻,也会进行两次切割)。如果sep为空字符,Split会将s切分成每一个unicode码值一个字符串。
fmt.Printf("%q\n", strings.Split("a mountain a temple", "a ")) // ["" "mountain " "temple"]

// 返回将字符串按照空白(unicode.IsSpace确定,可以是一到多个连续的空白字符)分割的多个字符串。如果字符串全部是空白或者是空字符串的话,会返回空切片。
fmt.Printf("Fields are: %q\n", strings.Fields(" Linux Python Golang Java "))
// ["Linux" "Python" "Golang" "Java"]

注意点

  • 字符串是不可变值类型,内部⽤指针指向 UTF-8 字节数组。
  • 默认值是空字符串 “”。
  • ⽤索引号访问某字节,如 s[i], 取出的是字节,不是字符
  • 由于字符串是只读的, 因此不能⽤序号获取字节元素指针, &s[i] ⾮法, 提示cannot take the address of xxx,原因在于如果支持取地址操作,则就可以使用指针对改地址指向的值进行修改, 这就违背了字符串只读的前提
  • 不可变类型,⽆法修改字节数组。使用[]rune进行修改是重新分配内存,并复制字节数组
  • 字符串可以用==和<进行比较

参考文章:

转载请注明原作者: 周淑科(https://izsk.me)

 wechat
Scan Me To Read on Phone
I know you won't do this,but what if you did?