一 字符串类型
字符串在Go语言中是以原生数据类型出现的,使用字符串就像使用其他基本类型(int、bool、float32、float64等)一样。Go语言中,使用关键字string来声明字符串变量。
字符串的值为双引号中的内容,可以在Go语言的源码中直接添加非ASCII码字符。示例代码如下:
str := "Hello, Golang"
ch := "中文"
1.1 字符串转义符
Go语言的字符串转义符和其他编程语言的是一样,如:回车、换行、单双引号、制表符等。
转义符 含义
\r 回车符(返回行首)
\n 换行符(直接跳到下一行的同列位置)
\t 制表符
\' 单引号
\" 双引号
\\ 反斜杠
在Go语言源码中使用转义字符的示例如下:
package main
import (
"fmt"
)
func main() {
fmt.Println("str := \"c:\\Go\\bin\\go.exe\"")
}
运行结果为:str := "c:\Go\bin\go.exe"
这段代码中将双引号("")和反斜杠(\)进行转义。
1.2 字符串实现基于UTF-8编码
Go语言字符串的内部实现使用的是UTF-8编码。通过rune类型,可以很方便地对每个UTF-8字符进行访问。当然,Go语言也支持按传统的ASCII码方式进行逐个字符访问。
1.3 定义多行字符串
在源码中,将字符串的值以双引号("")书写的方式是字符串的常见表达方式,被称为字符串字面量(string literal)。这种双引号字面量不能跨行,如果需要在源码中嵌入一个多行字符串时,就必须使用反引号字符(`)。示例代码如下:
package main
import "fmt"
func main(){
const str = `第一行
第二行
第三行
\r\n
`
fmt.Println(str)
}
输出结果如下:go run strDemo.go
第一行
第二行
第三行
\r\n
<说明> 在两个反引号中的字符串将被原样赋值到str变量中。反引号间的换行被视为字符串中的换行,但是所有的转义字符均无效,文本将会原样输出。多行字符串一般用于内嵌源码和内嵌数据等。在反引号间的所有代码均不会被编译器识别,而只是作为字符串的一部分。
二 字符串的应用
2.1 计算字符串的长度
Go语言的内建函数 len(),可以用来计算切片、字符串、通道(channel)等的长度。示例代码如下:
tip1 := "genji is a nijia"
fmt.Println(len(tip1)) //11
tip2 := "忍者"
fmt.Println(len(tip2)) //6
len()函数返回值的类型为int,表示字符串的ASCII码字符串个数或是字节长度,因为一个ASCII码字符使用一个字节的空间存储。
而输出语句的第2行的中文字符串"忍者",返回的字节数为6,这是因为Go语言的字符串都以UTF-8格式保存,每个中文字符占用3个字节的内存空间,因此使用len()函数获得两个中文文字对应的字节数为6。
如果希望按习惯上的字符个数来计算,就需要使用Go语言中UTF-8包提供的RuneCountInString()方法,统计Unicode字符数量。
package main
import(
"fmt"
"unicode/utf8" //导入utf-8包
)
func main(){
tip1 := "genji is a nijia"
fmt.Println(len(tip1)) //11
tip2 := "忍者"
fmt.Println(len(tip2)) //6
fmt.Println(utf8.RuneCountInString("忍者")) //2
fmt.Println(utf8.RuneCountInString("龙刃出鞘, fight!")) //12
}
<总结>
- 计算ASCII字符串长度使用len()函数。
- 计算Unicode字符串长度使用utf8.RuneCountInString()函数。
2.2 遍历字符串 —— 获取每一个字符串元素
1、遍历ASCII字符串中的字符
遍历ASCII字符串使用for循环语句,直接取每个字符的下标索引即可。示例如下:
package main
import(
"fmt"
)
func main(){
str := "Hello, Golang"
for i:=0; i<len(str); i++ {
fmt.Printf("ASCII: %c %d\n", str[i], str[i])
}
}
运行程序:go run traversalString.go
ASCII: H 72
ASCII: e 101
ASCII: l 108
ASCII: l 108
ASCII: o 111
ASCII: , 44
ASCII: 32
ASCII: G 71
ASCII: o 111
ASCII: l 108
ASCII: a 97
ASCII: n 110
ASCII: g 103
2、遍历Unicode字符串
package main
import(
"fmt"
)
func main(){
theme := "阻击 start"
for _, s := range theme{
fmt.Printf("Unicode: %c %d\n", s, s)
}
}
程序运行:go run rangeString.go
Unicode: 阻 38459
Unicode: 击 20987
Unicode: 32
Unicode: s 115
Unicode: t 116
Unicode: a 97
Unicode: r 114
Unicode: t 116
<总结>
- ASCII字符串遍历直接使用下标。
- Unicode字符串遍历使用for...range。
3、Go语言字符
字符串中的每一个元素叫做“字符”。在遍历或者获取单个字符串元素时可以获得字符。
GO语言字符有下面两种:
- 一种是uint8类型,或者叫byte类型,代表了ASCII码的一个字符。
- 另一种是rune类型,代表了一个UTF-8字符。当需要处理中文、日文或者其他复合字符时,需要用到rune类型。rune类型实际上是一个int32类型。
使用fmt.Printf中的"%T"格式输出符,可以输出变量的实际类型,使用这个方法可以查看byte和rune的本来类型,代码如下:
var a byte = 'a'
fmt.Printf("%d %T\n", a, a)
var b rune = '你'
fmt.Printf("%d %T\n", b, b)
输出结果如下:
97 uint8
20320 int32
可以看到,byte类型的变量a,实际类型是uint8,其值为'a',对应的ASCII编码为97。rune类型的变量b的实际类型是int32,对应的Unicode编码是20320。Go使用特殊的rune类型来处理Unicode字符串,让基于Unicoded的文本处理更为方便,也可以使用byte型进行默认字符串处理,性能和扩展性都有体现。
扩展 - UTF-8和Unicode的区别?
ASCII 和 Unicode 二者都是字符集。字符集为每一个字符分配一个唯一的ID。例如,上面的例子中,字符'a'在ASCII和Unicode的编码ID都是97。中文字符 '你' 在Unicode字符集的编码为20320。
UTF-8是编码规则,将Unicode中字符的ID以某种方式进行编码。UTF-8是一种变长编码规则,从1到4字节不等。编码规则如下:
- 0xxx xxxx 表示文字符号0~127,以兼容ASCII字符集。
- 从128~0x10 ffff 表示其他字符。
根据这个规则,拉丁文语系的字符编码一般情况下,每个字符依然占用一个字节,而中文每个字符占用3个字节。广义的Unicode指一个标准,定义字符集及编码规则,即Unicode字符集和UTF-8、UTF-16编码等。
2.3 获取字符串的子串(substring)
使用strings.Index()函数在字符串中搜索另一个子串。代码如下:
tracer := "死神来了,死神 bye bye"
comma := strings.Index(tracer[comma:], ",")
pos := strings.Index(tracer[comma:], "死神")
fmt.Println(comma, pos, tracer[comma+pos:])
运行结果:12 3 死神 bye bye
代码说明如下:
- 第2行尝试在tracer字符串中搜索中文逗号,返回的位置存在comma变量中,类型是int,表示从tracer字符串开始的ASCII码位置。
strings.Index()函数并没有像其他编程语言一样,提供一个从某偏移量开始搜搜的功能,但是我们可以对字符串进行切片操作来实现这个逻辑。
- 第3行中,tracer[comma:] 表示从tracer的comma位置开始到tracer字符串结尾构造一个子字符串,返回给strings.Index()进行再索引。得到的pos是相对于tracer[comma:]的结果,即此时tracer[comma:] = ",死神 bye bye"。
- comma中文逗号的位置是12(一个中文字符3个字节,offset=3 * 4 = 12)。而pos是相对位置,值为3(中文逗号占3个字节)。我们为了获得第2个死神的位置,也就是中文逗号后面的字符串,就必须让comma加上pos的相对偏移量,计算出15的偏移量,然后再通过切片操作tracer[comma+pos:]计算出最终的子串,获得最终的结果:"死神 bye bye"。
<总结>
- strings.Index():用于正向搜索子串。
- strings.LastIndex():反向搜索子串。
- 搜索的起始位置可以通过切片操作实现偏移量。
2.4 修改字符串
Go语言的字符串和其他高级编程语言(Java、C#)一样,默认是不可变的(immutable),无法直接修改字符串中的每一个字符元素,只能通过重新构造新的字符串并通过赋值原来的字符串来实现。示例代码如下:
angel := "Heros never die"
angelBytes := []byte(angel)
for i:=5; i<=10; i++ {
angelBytes[i] = ' '
}
fmt.Println(angel)
fmt.Println(string(angelBytes))
运行结果:
Heros never die
Heros die
代码说明:上面的代码中,我们并没有修改angel字符串中的元素,实际修改的是[]byte,[]byte在Go语言中是可变的,本身就是一个切片。在完成了对[]byte操作后,使用string()函数将[]byte转换为字符串,重新构造了一个新的字符串。
字符串不可变的好处:天生线程安全,大家使用的都是只读对象,无须加锁访问;方便内存共享,而不必使用写时复制(Copy On Write)技术;字符串hash值也只需要制作一份。
<总结>
- Go语言字符串是不可变的。
- 修改字符串时,可以将字符串转换为 []byte 进行修改。
- []byte 和 string 可以通过强制类型互相转换。
2.5 连接字符串
Go语言和其他编程语言(Java、C#等)一样,使用加号“+”对字符串进行连接操作。
Go语言除了使用加号连接字符串,Go语言也有类似于Java语言的StringBuilder机制来进行高效的字符串连接。示例代码如下:
hammer := "吃我一锤"
sickle := "死吧"
//声明字节缓存
var stringBuilder bytes.BUffer
//把字符串写入字符缓存
stringBuilder.WriteString(hammer)
stringBuilder.WriteString(sickle)
//将字符缓存以字符串形式输出
fmt.Println(stringBuilder.String())
代码说明:bytes.Buffer 是可以缓存并可以往里面写入各种字节数组的。字符串也是一种字节数组,使用WriteString()方法进行写入。将需要连接的字符串,通过调用 WriteString()方法,写入stringBuilder中,然后再通过stringBuilder.String()方法将缓冲转换为字符串。
2.6 格式化
Go语言字符串格式化写法:
fmt.Sprintf(格式化样式, 参数列表)
- 格式化样式:格式化动词以%开头。
- 参数列表:多个参数用逗号隔开,个数必须与格式化样式中的个数一一对应,否则运行时会报错。
表1 字符串格式化时常用动词及功能
在Go语言中,格式化的命名延续了C语言风格。示例代码如下:
var progress = 2
var target = 8
str := fmt.Sprintf("已采集%d个草药, 还需要%d个完成任务", progress, target);
fmt.Println(str)
pi := 3.14159
//按数值本身的格式输出
variant := fmt.Sprintf("%v %v %v", "月球基地", true, pi)
fmt.Println(variant)
//匿名结构体声明,并初始化
profile := &struct{
Name string
HP int
}{
Name: "rat",
HP: 150,
}
fmt.Printf("使用'%%+v' %+v\n", profile)
fmt.Printf("使用'%%#v' %#v\n", profile)
fmt.Printf("使用'%%T' %T\n", profile)
运行结果如下:
已采集2个草药, 还需要8个完成任务
月球基地 true 3.14159
使用'%+v' &{Name:rat HP:150}
使用'%#v' &struct { Name string; HP int }{Name:"rat", HP:150}
使用'%T' *struct { Name string; HP int }
2.7 数字字符串转换为数值
strconv包内的Atoi()函数或 ParseInt()函数用于解释表示整数的字符串,而 ParsUint()用于无符号整数。
x, err := strconv.Atoi("123") //x是整数
y, err := strconv.ParseInt("123", 10, 64) //10进制,最长为64位
ParseInt()函数的第三个参数指定结果必须匹配何种大小的整型;例如,16表示int16,而0作为特殊值表示int。任何情况下,结果y的类型总是int64。