美文网首页
Go语言之字符串八

Go语言之字符串八

作者: DreamKing | 来源:发表于2019-02-27 14:13 被阅读0次

    字符串在 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
    代码说明如下:

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

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

    1. 第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语言教程

    相关文章

      网友评论

          本文标题:Go语言之字符串八

          本文链接:https://www.haomeiwen.com/subject/kdaxuqtx.html