go语言字符串换行_Go语言之字符串八

字符串在 Go 语言中以原生数据类型出现,使用字符串就像使用其他原生数据类型(int、bool、float32、float64 等)一样。

字符串的值为双引号中的内容,可以在 Go 语言的源码中直接添加非 ASCII 码字符,代码如下:

str := "hello world"

ch := "中文"

字符串转义符

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"

这段代码中将双引号和反斜杠“\”进行转义。

字符串实现基于 UTF-8 编码

Go 语言里的字符串的内部实现使用 UTF-8 编码。通过 rune 类型,可以方便地对每个 UTF-8 字符进行访问。当然,Go 语言也支持按传统的 ASCII 码方式进行逐字符访问。

定义多行字符串

在源码中,将字符串的值以双引号书写的方式是字符串的常见表达方式,被称为字符串字面量(string literal)。这种双引号字面量不能跨行。如果需要在源码中嵌入一个多行字符串时,就必须使用`字符,代码如下:

const str = ` 第一行

第二行

第三行

\r\n

`

fmt.Println(str)

代码运行结果:

第一行

第二行

第三行

\r\n

`叫反引号,就是键盘上 1 键左边的键,两个反引号间的字符串将被原样赋值到 str 变量中。

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

const codeTemplate = `// Generated by github.com/davyxu/cellnet/

protoc-gen-msg

// DO NOT EDIT!{{range .Protos}}

// Source: {{.Name}}{{end}}

package {{.PackageName}}

{{if gt .TotalMessages 0}}

import (

"github.com/davyxu/cellnet"

"reflect"

_ "github.com/davyxu/cellnet/codec/pb"

)

{{end}}

func init() {

{{range .Protos}}

// {{.Name}}{{range .Messages}}

cellnet.RegisterMessageMeta("pb","{{.FullName}}", reflect.TypeOf((*{{.Name}})(nil)).Elem(), {{.MsgID}}) {{end}}

{{end}}

}

`

这段代码只定义了一个常量 codeTemplate,类型为字符串,使用`定义。字符串的内容为一段代码生成中使用到的 Go 源码格式。

在`间的所有代码均不会被编译器识别,而只是作为字符串的一部分。

字符串的长度

Go 语言的内建函数 len(),可以用来获取切片、字符串、通道(channel)等的长度。下面的代码可以用 len() 来获取字符串的长度。

tip1 := "genji is a ninja"

fmt.Println(len(tip1))

tip2 := "忍者" //一个中文是3个字符

fmt.Println(len(tip2))

程序输出如下:

16

6

len() 函数的返回值的类型为 int,表示字符串的 ASCII 字符个数或字节长度。

输出中第一行的 16 表示 tip1 的字符个数为 16。

输出中第二行的 6 表示 tip2 的字符格式,也就是“忍者”的字符个数是 6,然而根据习惯,“忍者”的字符个数应该是 2。

这里的差异是由于 Go 语言的字符串都以 UTF-8 格式保存,每个中文占用 3 个字节,因此使用 len() 获得两个中文文字对应的 6 个字节。

如果希望按习惯上的字符个数来计算,就需要使用 Go 语言中 UTF-8 包提供的 RuneCountInString() 函数,统计 Uncode 字符数量。

下面的代码展示如何计算UTF-8的字符个数。

fmt.Println(utf8.RuneCountInString("忍者"))

fmt.Println(utf8.RuneCountInString("龙忍出鞘,fight!"))

程序输出如下:

2

11

一般游戏中在登录时都需要输入名字,而名字一般有长度限制。考虑到国人习惯使用中文做名字,就需要检测字符串 UTF-8 格式的长度。

总结

ASCII 字符串长度使用 len() 函数。

Unicode 字符串长度使用 utf8.RuneCountInString() 函数。

字符串的fmt.Sprintf(格式化输出)

格式化在逻辑中非常常用。使用格式化函数,要注意写法:

fmt.Sprintf(格式化样式, 参数列表…)

格式化样式:字符串形式,格式化动词以%开头。

参数列表:多个参数以逗号分隔,个数必须与格式化样式中的个数一一对应,否则运行时会报错。

在 Go 语言中,格式化的命名延续C语言风格:

var progress = 2

var target = 8

// 两参数格式化

title := fmt.Sprintf("已采集%d个药草, 还需要%d个完成任务", progress, target)

fmt.Println(title)

pi := 3.14159

// 按数值本身的格式输出

variant := fmt.Sprintf("%v %v %v", "月球基地", pi, true)

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个完成任务

"月球基地" 3.14159 true

使用'%+v' &{Name:rat HP:150}

使用'%#v' &struct { Name string; HP int }{Name:"rat", HP:150}

使用'%T' *struct { Name string; HP int }C语言中, 使用%d代表整型参数

下表中标出了常用的一些格式化样式中的动词及功能。

表:字符串格式化时常用动词及功能

动 词 功 能

%d int变量

%x, %o, %b 分别为16进制,8进制,2进制形式的int

%f, %g, %e 浮点数: 3.141593 3.141592653589793 3.141593e+00

%t 布尔变量:true 或 false

%c rune (Unicode码点),Go语言里特有的Unicode字符类型

%s string

%q 带双引号的字符串 "abc" 或 带单引号的 rune 'c'

%v 会将任意变量以易读的形式打印出来

%T 打印变量的类型

%% 字符型百分比标志(%符号本身,没有其他操作)

遍历字符串——获取每一个字符串元素

遍历字符串有下面两种写法。

遍历每一个ASCII字符

遍历 ASCII 字符使用 for 的数值循环进行遍历,直接取每个字符串的下标获取 ASCII 字符,如下面的例子所示。

theme := "狙击 start"

for i := 0; i < len(theme); i++ {

fmt.Printf("ascii: %c %d\n", theme[i], theme[i])

}

程序输出如下:

ascii: ? 231

ascii: 139

ascii: 153

ascii: ? 229

ascii: 135

ascii: ? 187

ascii: 32

ascii: s 115

ascii: t 116

ascii: a 97

ascii: r 114

ascii: t 116

这种模式下取到的汉字“惨不忍睹”。由于没有使用 Unicode,汉字被显示为乱码。

按Unicode字符遍历字符串

同样的内容:

theme := "狙击 start"

for _, s := range theme {

fmt.Printf("Unicode: %c %d\n", s, s)

}

程序输出如下:

Unicode: 狙 29401

Unicode: 击 20987

Unicode: 32

Unicode: s 115

Unicode: t 116

Unicode: a 97

Unicode: r 114

Unicode: t 116

可以看到,这次汉字可以正常输出了。

总结

ASCII 字符串遍历直接使用下标。

Unicode 字符串遍历用 for range。

字符串截取(获取字符串的某一段字符)

获取字符串的某一段字符是开发中常见的操作,我们一般将字符串中的某一段字符称做子串(substring)。

下面例子中使用 strings.Index() 函数在字符串中搜索另外一个子串,代码如下:

tracer := "死神来了, 死神bye bye"

comma := strings.Index(tracer, ", ")

pos := strings.Index(tracer[comma:], "死神")

fmt.Println(comma, pos, tracer[comma+pos:])

程序输出如下:

12 3 死神bye bye

代码说明如下:

第 2 行尝试在 tracer 的字符串中搜索中文的逗号,返回的位置存在 comma 变量中,类型是 int,表示从 tracer 字符串开始的 ASCII 码位置。

strings.Index() 函数并没有像其他语言一样,提供一个从某偏移开始搜索的功能。不过我们可以对字符串进行切片操作来实现这个逻辑。

第4行中,tracer[comma:] 从 tracer 的 comma 位置开始到 tracer 字符串的结尾构造一个子字符串,返回给 string.Index() 进行再索引。得到的 pos 是相对于 tracer[comma:] 的结果。

comma 逗号的位置是 12,而 pos 是相对位置,值为 3。我们为了获得第二个“死神”的位置,也就是逗号后面的字符串,就必须让 comma 加上 pos 的相对偏移,计算出 15 的偏移,然后再通过切片 tracer[comma+pos:] 计算出最终的子串,获得最终的结果:“死神bye bye”。

总结

字符串索引比较常用的有如下几种方法:

strings.Index:正向搜索子字符串。

strings.LastIndex:反向搜索子字符串。

搜索的起始位置可以通过切片偏移制作。

修改字符串

Go 语言的字符串无法直接修改每一个字符元素,只能通过重新构造新的字符串并赋值给原来的字符串变量实现。请参考下面的代码:

angel := "Heros never die"

angleBytes := []byte(angel)

for i := 5; i <= 10; i++ {

angleBytes[i] = ' '

}

fmt.Println(string(angleBytes))

程序输出如下:

Heros die

代码说明如下:

在第 2 行中,将字符串转为字符串数组。

第 3~6 行利用循环,将 never 单词替换为空格。

最后打印结果。

感觉我们通过代码达成了修改字符串的过程,但真实的情况是:Go 语言中的字符串和其他高级语言(Java、C#)一样,默认是不可变的(immutable)。

字符串不可变有很多好处,如天生线程安全,大家使用的都是只读对象,无须加锁;再者,方便内存共享,而不必使用写时复制(Copy On Write)等技术;字符串 hash 值也只需要制作一份。

所以说,代码中实际修改的是 []byte,[]byte 在 Go 语言中是可变的,本身就是一个切片。

在完成了对 []byte 操作后,在第 9 行,使用 string() 将 []byte 转为字符串时,重新创造了一个新的字符串。

总结

Go 语言的字符串是不可变的。

修改字符串时,可以将字符串转换为 []byte 进行修改。

[]byte 和 string 可以通过强制类型转换互转。

字符串拼接(连接)

连接字符串这么简单,还需要学吗?确实,Go 语言和大多数其他语言一样,使用+对字符串进行连接操作,非常直观。

但问题来了,好的事物并非完美,简单的东西未必高效。除了加号连接字符串,Go 语言中也有类似于 StringBuilder 的机制来进行高效的字符串连接,例如:

hammer := "吃我一锤"

sickle := "死吧"

// 声明字节缓冲

var stringBuilder bytes.Buffer

// 把字符串写入缓冲

stringBuilder.WriteString(hammer)

stringBuilder.WriteString(sickle)

// 将缓冲以字符串形式输出

fmt.Println(stringBuilder.String())

bytes.Buffer 是可以缓冲并可以往里面写入各种字节数组的。字符串也是一种字节数组,使用 WriteString() 方法进行写入。

将需要连接的字符串,通过调用 WriteString() 方法,写入 stringBuilder 中,然后再通过 stringBuilder.String() 方法将缓冲转换为字符串。

Base64编码——电子邮件的基础编码格式

Base64 编码是常见的对 8 比特字节码的编码方式之一。Base64 可以使用 64 个可打印字符来表示二进制数据,电子邮件就是使用这种编码。

Go 语言的标准库自带了 Base64 编码算法,通过几行代码就可以对数据进行编码,示例代码如下。

package main

import (

"encoding/base64"

"fmt"

)

func main() {

// 需要处理的字符串

message := "Away from keyboard. https://golang.org/"

// 编码消息

encodedMessage := base64.StdEncoding.EncodeToString([]byte (message))

// 输出编码完成的消息

fmt.Println(encodedMessage)

// 解码消息

data, err := base64.StdEncoding.DecodeString(encodedMessage)

// 出错处理

if err != nil {

fmt.Println(err)

} else {

// 打印解码完成的数据

fmt.Println(string(data))

}

}

代码说明如下:

第 11 行为需要编码的消息,消息可以是字符串,也可以是二进制数据。

第 14 行,base64 包有多种编码方法,这里使用 base64.StdEnoding 的标准编码方法进行编码。传入的字符串需要转换为字节数组才能供这个函数使用。

第 17 行,编码完成后一定会输出字符串类型,打印输出。

第 20 行,解码时可能会发生错误,使用 err 变量接收错误。

第 24 行,出错时,打印错误。

第 27 行,正确时,将返回的字节数组([]byte)转换为字符串。

本文学习来源于C语言中文网>Go语言教程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值