Go语言--字符串的使用

 一 字符串类型

字符串在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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值