字符串 string
# 5.字符串 string
# 5.1 go对字符串类型的支持
# 5.1.1 Go 原生支持字符串的好处
首先,对比C 语言,没有提供对字符串类型的原生支持,也就是说,C 语言中并没有“字符串”这个数据类型。在 C 语言中,字符串是以字符串字面值或以’\0’结尾的字符类型数组来呈现的,比如下面代码:
#define GO_SLOGAN "less is more"
const char * s1 = "hello, gopher"
char s2[] = "I love go"
2
3
这样定义的非原生字符串在使用过程中会有很多问题,比如:
- 不是原生类型,编译器不会对它进行类型校验,导致类型安全性差;
- 字符串操作时要时刻考虑结尾的’\0’,防止缓冲区溢出;
- 以字符数组形式定义的“字符串”,它的值是可变的,在并发场景中需要考虑同步问题;
- 获取一个字符串的长度代价较大,通常是 O(n) 时间复杂度;
- C 语言没有内置对非 ASCII 字符(如中文字符)的支持。
C 代码换成等价的 Go 代码是这样的:
const (
GO_SLOGAN = "less is more" // GO_SLOGAN是string类型常量
s1 = "hello, gopher" // s1是string类型常量
)
var s2 = "I love go" // s2是string类型变量
2
3
4
5
6
带来的好处:
==第一点:string 类型的数据是不可变的,提高了字符串的并发安全性和存储利用率。==
var s string = "hello"
s[0] = 'k' // 错误:字符串的内容是不可改变的
s = "gopher" // ok
2
3
==第二点:没有结尾’\0’,而且获取长度的时间复杂度是常数时间,消除了获取字符串长度的开销。==
==第三点:原生支持“所见即所得”的原始字符串,大大降低构造多行字符串时的心智负担。==
// Go 语言原始字符串中的任意转义字符都不会起到转义的作用
var s string = ` ,_---~~~~~----._
_,,_,*^____ _____*g*\"*,--,
/ __/ /' ^. / \ ^@q f
[ @f | @)) | | @)) l 0 _/
\/ \~____ / __ \_____/ \
| _l__l_ I
} [______] I
] | | | |
] ~ ~ |
| |
| |`
fmt.Println(s)
2
3
4
5
6
7
8
9
10
11
12
13
==第四点:对非 ASCII 字符提供原生支持,消除了源码在不同环境下显示乱码的可能。==
# 5.1.2 Go 字符串的组成
字节视角
Go 语言中的字符串值也是一个可空的字节序列,字节序列中的字节个数称为该字符串的长度。一个个的字节只是孤立数据,不表意。
var s = "中国人"
fmt.Printf("the length of s = %d\n", len(s)) // 9
for i := 0; i < len(s); i++ {
fmt.Printf("0x%x ", s[i]) // 0xe4 0xb8 0xad 0xe5 0x9b 0xbd 0xe4 0xba 0xba
}
fmt.Printf("\n")
2
3
4
5
6
7
字符视角
字符串是由一个可空的字符序列构成。
var s = "中国人"
fmt.Println("the character count in s is", utf8.RuneCountInString(s)) // 3
for _, c := range s {
fmt.Printf("0x%x ", c) // 0x4e2d 0x56fd 0x4eba
}
fmt.Printf("\n")
2
3
4
5
6
7
# 5.2 字符串介绍
Go 语言里的字符串的内部实现使用 UTF-8 编码。字符串的值为双引号(“)中的内容,可以在 Go 语言的源码中直接添加非 ASCII 码字符。
s1 := "hello"
s2 := "你好"
2
string 类型其实是一个“描述符”,它本身并不真正存储字符串数据,而仅是由一个指向底层存储的指针和字符串的长度字段组成的。下图直观地展示了一个 string 类型变量在 Go 内存中的存储:
# 5.3 多行字符串
反引号(`)间换行将被作为字符串中的换行,但是所有的转义字符均无效,文本将会原样输出。
package main
import "fmt"
func main() {
s1 := `
第一行
第二行
第三行
`
fmt.Println(s1)
}
输出:
第一行
第二行
第三行
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 5.4 byte和rune
Go 语言的字符有以下两种:
- uint8类型,或者叫 byte 型:代表了ASCII码的一个字符。
- rune类型:代表一个 UTF-8字符。
字符串底层是一个byte数组,所以可以和[]byte类型相互转换;
字符串是不能修改的;
字符串是由byte字节组成,所以字符串的长度是byte字节的长度;
rune类型用来表示utf8字符,一个rune字符由一个或多个byte组成。
package main
import "fmt"
func main() {
s := "川普总统"
s_rune := []rune(s)
fmt.Println("拜登" + string(s_rune)[:2])
}
输出:
拜登总统
2
3
4
5
6
7
8
9
10
11
12
# 5.5 字符串常用操作
# len(str)
package main
import "fmt"
func main() {
var str = "this is str"
fmt.Println(len(str)) // 11
}
2
3
4
5
6
7
8
# +(拼接)
package main
import "fmt"
func main() {
var str1 = "你好"
var str2 = "golang"
fmt.Println(str1 + str2) // 你好golang
}
2
3
4
5
6
7
8
9
除了这个方法外,Go 还提供了 strings.Builder、strings.Join、fmt.Sprintf 等函数来进行字符串连接操作。
# strings.Split()
package main
import (
"fmt"
"strings"
)
func main() {
var s = "123-456-789"
var arr = strings.Split(s, "-")
fmt.Println(arr) // [123 456 789]
}
2
3
4
5
6
7
8
9
10
11
12
# strings.Join()
package main
import (
"fmt"
"strings"
)
func main() {
var str = "123-456-789"
var arr = strings.Split(str, "-") // [123 456 789]
var str2 = strings.Join(arr, "*") // 123*456*789
fmt.Println(arr)
fmt.Println(str2)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 单引号
组成每个字符串的元素叫做“字符”,可以通过遍历字符串元素获得字符,字符用单引号(’)表示。
package main
import "fmt"
func main() {
a := 'a'
name := "zhangsan"
//当我们直接输出 byte(字符)的时候输出的是这个字符对应的码值
fmt.Println(a) // 97 这里输出的是 a 字符串的 ASCII值
fmt.Println(name) // zhangsan
//如果我们要输出这个字符,需要格式化输出
fmt.Printf("值是%c", a) // 值是a
}
2
3
4
5
6
7
8
9
10
11
12
13
# 下标操作
在字符串的实现中,真正存储数据的是底层的数组。字符串的下标操作本质上等价于底层数组的下标操作。
var s = "中国人"
fmt.Printf("0x%x\n", s[0]) // 0xe4:字符“中” utf-8编码的第一个字节
2
通过下标操作,获取的是字符串中特定下标上的字节,而不是字符。
# 字符串比较
Go 字符串类型支持各种比较关系操作符,包括 = =、!= 、>=、<=、> 和 <。
func main() {
// ==
s1 := "世界和平"
s2 := "世界" + "和平"
fmt.Println(s1 == s2) // true
// !=
s1 = "Go"
s2 = "C"
fmt.Println(s1 != s2) // true
// < and <=
s1 = "12345"
s2 = "23456"
fmt.Println(s1 < s2) // true
fmt.Println(s1 <= s2) // true
// > and >=
s1 = "12345"
s2 = "123"
fmt.Println(s1 > s2) // true
fmt.Println(s1 >= s2) // true
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 5.6 字符串遍历
# 遍历字符串
package main
import "fmt"
func main() {
s := "hello 张三"
// 方法一:for遍历
for i := 0; i < len(s); i++ {
fmt.Printf("%v(%c)", s[i], s[i])
// 104(h) 101(e) 108(l) 108(l) 111(o) 32( ) 229(å) 188(¼) 160() 228(ä) 184(¸) 137()
}
fmt.Println() // 打印一个换行
//方法二:for range遍历
for _, r := range s { //rune
fmt.Printf("%v==>%c", r, r)
// 104=>h 101=>e 108=>l 108=>l 111=>o 32=> 24352=>张 19977=>三
fmt.Println()
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Go 有两种迭代形式:==常规 for 迭代与 for range 迭代。==
- 通过常规 for 迭代对字符串进行的操作是一种字节视角的迭代,每轮迭代得到的的结果都是组成字符串内容的一个字节,以及该字节所在的下标值,这也等价于对字符串底层数组的迭代。
- 通过 for range 迭代,我们每轮迭代得到的是字符串中 Unicode 字符的码点值,以及该字符在字符串中的偏移值。
# 修改字符串
要修改字符串,需要先将其转换成[]rune 或[]byte,完成后再转换为 string。无论哪种转换,都会重新分配内存,并复制字节数组。
将“美国第一”改成“中国第一”
package main
import "fmt"
func main() {
s := "美国第一"
s_rune := []rune(s)
fmt.Println("中国" + string(s_rune[2:])) //中国第一
}
2
3
4
5
6
7
8
9
# 5.7 转string
# strconv
package main
import (
"fmt"
"strconv"
)
func main() {
//1、int 转换成 string
var num1 int = 20
s1 := strconv.Itoa(num1)
fmt.Printf("类型: %T ,值=%v \n", s1, s1) // 类型: string ,值=20
// 2、float 转 string
var num2 float64 = 20.113123
/* 参数 1:要转换的值
参数 2:格式化类型
参数 3: 保留的小数点 -1(不对小数点格式化)
参数 4:格式化的类型
*/
s2 := strconv.FormatFloat(num2, 'f', 2, 64)
fmt.Printf("类型: %T ,值=%v \n", s2, s2) // 类型: string ,值=20.11
// 3、bool 转 string
s3 := strconv.FormatBool(true)
fmt.Printf("类型: %T ,值=%v \n", s3, s3) // 类型: string ,值=20.11
//4、int64 转 string
var num3 int64 = 20
s4 := strconv.FormatInt(num3, 10) /* 第二个参数10为 进制 */
fmt.Printf("类型: %T ,值=%v \n", s4, s4) // 类型 string ,值=20
}
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
# string与int转换
package main
import (
"fmt"
"strconv"
)
func main() {
//1、int string 互转
num := 100
strNum := strconv.Itoa(num)
fmt.Printf("num: %T %v \n", num, num)
fmt.Printf("strNum: %T %v \n", strNum, strNum)
intNum, _ := strconv.Atoi(strNum)
fmt.Printf("intNum: %T %v \n", intNum, intNum)
}
/*
num: int 100
strNum: string 100
intNum: int 100
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Go 还支持字符串与字节切片、字符串与 rune 切片的双向转换,并且这种转换无需调用任何函数,只需使用显式类型转换就可以。
var s string = "中国人"
// string -> []rune
rs := []rune(s)
fmt.Printf("%x\n", rs) // [4e2d 56fd 4eba]
// string -> []byte
bs := []byte(s)
fmt.Printf("%x\n", bs) // e4b8ade59bbde4baba
// []rune -> string
s1 := string(rs)
fmt.Println(s1) // 中国人
// []byte -> string
s2 := string(bs)
fmt.Println(s2) // 中国人
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18