简介

一个Go语言字符串是一个任意字节的常量序列。Go语言的字符串类型在本质上就与其他语言的字符串类型不同。

Java的String、C++的std::string以及python3的str类型都只是定宽字符序列,而 Go语言的字符串是一个用UTF-8编码的变宽字符序列,它的每一个字符都用一个或多个字节表示 。

Go语言中的字符串字面量使用 双引号 或 反引号 来创建 :

  • 双引号用来创建可解析的字符串字面量 (支持转义,但不能用来引用多行);

  • 反引号用来创建原生的字符串字面量 ,这些字符串可能由多行组成(不支持任何转义序列),原生的字符串字面量多用于书写多行消息、HTML以及正则表达式。

  • 单引号则用于表示Golang的一个特殊类型:rune,类似其他语言的byte但又不完全一样,是指:码点字面量(Unicode code point),不做任何转义的原始内容。

字符串的实质

go中有两种方式对字符串进行遍历,一种是utf-8遍历,另一种是Unicode遍历。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package main

import "fmt"

func main() {
    str := "Hello,世界"
    //utf-8遍历
    for i := 0; i < len(str); i++ {
        ch := str[i]
        fmt.Println(ch)
    }
    fmt.Println("=============>Unicode遍历")
    //Unicode遍历
    for _, ch1 := range str {
        fmt.Println(ch1)
    }
}

上面代码执行后,会打印一串数字而不是字符。这是由于go语言中的字符串实际上是类型为byte的只读切片。或者说一个字符串就是一堆字节。这意味着,当我们将字符存储在字符串中时,实际存储的是这个字符的字节。一个字符串包含了任意个byte,它并不限定Unicode,UTF-8或者任何其他预定义的编码。那么go语言用什么来表示字符呢,下面的例子可以验证一下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
    "fmt"
    "reflect"
)

func main() {
    str := "Hello,世界"
    //utf-8遍历
    for i := 0; i < len(str); i++ {
        ch := str[i]
        ctype:=reflect.TypeOf(ch)
        fmt.Printf("%s ",ctype)
    }
    fmt.Println("=============>Unicode遍历")
    //Unicode遍历
    for _, ch1 := range str {
        ctype:=reflect.TypeOf(ch1)
        fmt.Printf("%s ",ctype)
    }
}
1
2
uint8 uint8 uint8 uint8 uint8 uint8 uint8 uint8 uint8 uint8 uint8 uint8 =============>Unicode遍历
int32 int32 int32 int32 int32 int32 int32 int32 %

代码运行后显示ch的类型为uint8,也就是byte类型,而ch1的类型为int32,也就是rune类型。go语言中的源码定义为utf-8文本,不允许其他的表示。但是也存在特殊处理,那就是字符串上使用for…range循环。range循环迭代时,就会解码一个utf-8编码的rune。现在既然已经知道上述不管哪种遍历方式,其实质都是字节。所以在打印时,只需要将这些结果转化为字符字面值或者转换其输出类型就可以了。下面是两种字符串遍历方式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package main

import (
    "fmt"
)

func main() {
    str := "Hello,世界"
    //方法一:格式化打印
    for _, ch1 := range str {
        fmt.Printf("%q",ch1) //单引号围绕的字符字面值,由go语法安全的转义
    }
    fmt.Println("==========>方法二")
    //方法二:转化输出格式
    for _, ch2 := range str {
        fmt.Println(string(ch2))
    }
}

字符串面值

一个原生的字符串面值形式是`…`,使用反引号代替双引号。在原生的字符串面值中,没有转义操作;全部的内容都是字面的意思,包含退格和换行,因此一个程序中的原生字符串面值可能跨越多行(译注:在原生字符串面值内部是无法直接写 ` 字符的,可以用八进制或十六进制转义或+ " ` " 连接字符串常量完成)。唯一的特殊处理是会删除回车以保证在所有平台上的值都是一样的,包括那些把回车也放入文本文件的系统(译注:Windows系统会把回车和换行一起放入文本文件中)。

原生字符串面值用于编写正则表达式会很方便,因为正则表达式往往会包含很多反斜杠。原生字符串面值同时被广泛应用于HTML模板、JSON面值、命令行提示信息以及那些需要扩展到多行的场景。

1
2
3
4
5
const GoUsage = `Go is a tool for managing Go source code.

Usage:
    go command [arguments]
...`

拼接

看下面那个选项是正确的:

1
2
3
4
5
6
A. str:='abc'+'123'
B. str:="abc"+"123"
C. str:='123'+"abc"
D. str:=`123`+`abc`
E. str:=`123`+'abc'
F. str:=`123`+"abc"

正确答案为BDF,为什么是这样呢?这就是Go的特别之处。其他语言,例如JavaScript,单引号和双引号可以同时使用,都可以用来表示字符串。Java中单引号表示char类型,双引号表示string类型。而在Go中,双引号是用来表示字符串string,其实质是一个byte类型的数组,单引号表示rune类型。还有一个反引号,用来创建原生的字符串字面量,它可以由多行组成,但不支持任何转义序列。因此,当把两个不同类型的变量进行拼接时,就会报错。

双引号字符串的转义

在一个双引号包含的字符串面值中,可以用以反斜杠\开头的转义序列插入任意的数据。下面的换行、回车和制表符等是常见的ASCII控制代码的转义方式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
\a      响铃
\b      退格
\f      换页
\n      换行
\r      回车
\t      制表符
\v      垂直制表符
\'      单引号 (只用在 '\'' 形式的rune符号面值中)
\"      双引号 (只用在 "..." 形式的字符串面值中)
\\      反斜杠

可以通过十六进制或八进制转义在字符串面值中包含任意的字节。一个十六进制的转义形式是\xhh,其中两个h表示十六进制数字(大写或小写都可以)。一个八进制转义形式是\ooo,包含三个八进制的o数字(0到7),但是不能超过\377(译注:对应一个字节的范围,十进制为255)。每一个单一的字节表达一个特定的值。稍后我们将看到如何将一个Unicode码点写到字符串面值中。

参考:https://blog.csdn.net/benben_2015/article/details/78904860