> For the complete documentation index, see [llms.txt](https://sunwenfei.gitbook.io/sunwenfei/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://sunwenfei.gitbook.io/sunwenfei/golang/golang-ji-chu-jiao-cheng/qi-ta-shu-ju-lei-xing/zi-fu-chuan.md).

# 字符串

Golang中的字符串跟其他编程语言区别较大，因此这里专门抽出一节来介绍。

#### 什么是字符串

Golang中的字符串数据类型是`string`。Golang中字符串本质上是一个由**字节**组成的**切片**。可以通过双引号来创建字符串。下面我们来创建一个字符串并打印。

```go
package main

import "fmt"

func main() {
    name := "Hello World"
    fmt.Println(name)
}
```

[Go Playground在线运行](https://play.golang.org/p/tDuV4kUpGv1)

该程序输出为：`Hello World`。

Golang中字符串是通过**UTF-8**编码的(UTF-8是Unicode的实现方式之一)。

#### 访问单个字节

前面说过，GOlang中字符串本质上是一个由字节组成的切片，因此我们当然可以访问组成字符串的单个字节。

```go
package main

import "fmt"

func printBytes(s string) {
    for i := 0; i < len(s); i++ {
        fmt.Printf("%x ", s[i])
    }
}

func main() {
    name := "Hello World"
    printBytes(name)
}
```

[Go Playground在线运行](https://play.golang.org/p/i3IKCA4bpvd) 这里`len(s)`返回字符串的字节个数，然后我们通过for循环按照十六进制的格式打印出了每个字节。该程序输出为：`48 65 6c 6c 6f 20 57 6f 72 6c 64`。输出的这些字节其实就是`Hello World`这个字符串的UTF-8编码。通过[这篇文章](https://naveenr.net/unicode-character-set-and-utf-8-utf-16-utf-32-encoding/)可以了解下什么是Unicode以及UTF-8编码。了解下Unicode和UTF-8的知识点非常有助于理解Golang中的字符串。

现在我们将前面这段代码修改如下：

```go
package main

import "fmt"

func printBytes(s string) {
    for i := 0; i < len(s); i++ {
        fmt.Printf("%x ", s[i])
    }
}

func printChars(s string) {
    for i := 0; i < len(s); i++ {
        fmt.Printf("%c ", s[i])
    } 
}

func main() {
    name := "Hello World"
    printBytes(name)
    fmt.Println()
    printChars(name)
}
```

[Go Playground在线运行](https://play.golang.org/p/rEoi3AdaJfO) 这里的`%c`用来按字符打印字符串。该程序输出如下：

```
48 65 6c 6c 6f 20 57 6f 72 6c 64 
H e l l o   W o r l d
```

这里使用`%c`来打印字符串中的每个字符看起来是合法的，但其实是有问题的。我们换个字符串来打印试试看：

```go
package main

import "fmt"

func printBytes(s string) {
    for i := 0; i < len(s); i++ {
        fmt.Printf("%x ", s[i])
    }
}

func printChars(s string) {
    for i := 0; i < len(s); i++ {
        fmt.Printf("%c ", s[i])
    } 
}

func main() {
    name := "Señor"
    printBytes(name)
    fmt.Println()
    printChars(name)
}
```

[Go Playground在线运行](https://play.golang.org/p/GmYH_y5cL6a) 该程序输出如下：

```go
53 65 c3 b1 6f 72 
S e Ã ± o r
```

这里我们本来期望打印出`Señor`，却打印出了`S e Ã ± o r`。你肯定会疑惑，为什么打印`Hello World`能正常打印，换成`Señor`却不行。其实我们应该能看出来，这里是字符`ñ`打印成了`Ã ±`。为什么会这样呢？是因为字符`ñ`对应的Unicode码点为`U+00F1`，采用UTF-8编码会占用两个字节：`c3`和`b1`，而前面程序中直接按照`%c`格式打印字符串是按照每个字符一个字节来打印的，因此才打印出了错误的结果。那怎么避免这种bug呢？Golang提供了`rune`(符文)数据类型来解决这个问题。

#### rune(符文)

Golang中`rune`也是一种基本数据类型，本质上是`int32`的别名，这一点在前面基本数据类型部分也提到过。Golang中`rune`用来表示一个Unicode码点，无论一个Unicode码点由几个字节组成(int32即为4个字节，4个字节来表示一个Unicode码点已经足够了)，该码点均可由`rune`来表示。我们修改下前面的程序，使用`rune`来打印输出一个字符串。

```go
package main

import "fmt"

func printBytes(s string) {
    for i := 0; i < len(s); i++ {
        fmt.Printf("%x ", s[i])
    }
}

func printChars(s string) {
    runes := []rune(s)
    for i := 0; i < len(runes); i++ {
        fmt.Printf("%c ", runes[i])
    }
}

func main() {
    name := "Hello World"
    printBytes(name)
    fmt.Println()
    printChars(name)
    fmt.Println()
    name = "Señor"
    printBytes(name)
    fmt.Println()
    printChars(name)
}
```

[Go Playground在线运行](https://play.golang.org/p/N9lueB8M6W6) 这里`runes := []rune(s)`将字符串强制转换成了`rune`切片。然后我们基于`rune`来打印出了正常的字符。该程序执行结果如下，正是我们想要的。

```
48 65 6c 6c 6f 20 57 6f 72 6c 64 
H e l l o   W o r l d 
53 65 c3 b1 6f 72 
S e ñ o r
```

#### 使用`for range`循环访问字符串

前面几段程序中循环访问字符串每个字符的方法都挺好，但是Golang中还有一种更加简洁的方法：`for range`。

```go
package main

import "fmt"

func printCharsAndBytes(s string) {
    for index, rune := range s {
    fmt.Printf("%c starts at byte %d\n", rune, index)
    }
}

func main() {
    name := "Señor"
    printCharsAndBytes(name)
}
```

[Go Playground在线运行](https://play.golang.org/p/A5wblQroK3h) 这里我们使用`for range`来迭代访问字符串`s`。打印出了迭代访问到的每个字符以及其字节索引位置。该程序输出如下：

```
S starts at byte 0
e starts at byte 1
ñ starts at byte 2
o starts at byte 4
r starts at byte 5
```

从输出结果也能清晰的看出来，字符`ñ`占用了2个字节。

#### 基于byte(字节)切片构建字符串

```go
package main

import "fmt"

func main() {
    byteSlice := []byte{0x43, 0x61, 0x66, 0xC3, 0xA9}
    str := string(byteSlice)
    fmt.Println(str)
}
```

[Go Playground在线运行](https://play.golang.org/p/-c9ZKiq6VP2) 这里创建了一个切片`byteSlice`，该切片由十六进制形式的字节组成(一个字节包含8比特，这里的`0x43`就是一个字节)。这些字节本身是字符串`Café`UTF-8的UTF-8编码。因此该程序输出为`Café`。

当然，这里其实没必要非的用十六进制形式，比如下面换成十进制形式也可以：

```go
package main

import "fmt"

func main() {
    byteSlice := []byte{67, 97, 102, 195, 169}
    str := string(byteSlice)
    fmt.Println(str)
}
```

[Go Playground在线运行](https://play.golang.org/p/vlhffoolci2) 输出结果仍然是`Café`。

#### 基于rune(符文)切片构建字符串

```go
package main

import "fmt"

func main() {
    runeSlice := []rune{0x0053, 0x0065, 0x00f1, 0x006f, 0x0072}
    str := string(runeSlice)
    fmt.Println(str)
}
```

[Go Playground在线运行](https://play.golang.org/p/3nGfhNh8Gnh) 该程序中`runeSlice`由字符串`Señor`的Unicode编码码点组成，格式为十六进制形式。输出为：`Señor`。

#### 字符串长度

注意，之类说的字符串长度为字符串中字符(rune)的长度。 `utf8`包中的函数`func RuneCountInString(s string) (n int)`用来获取字符串长度。

```go
package main

import (
    "fmt"
    "unicode/utf8"
)

func length(s string) {
    fmt.Printf("length of %s is %d\n", s, utf8.RuneCountInString(s))
}

func main() {
    word1 := "Señor"
    length(word1)
    word2 := "Pets"
    length(word2)
}
```

[Go Playground在线运行](https://play.golang.org/p/nT5PKwXhebJ) 该程序执行结果如下：

```
length of Señor is 5
length of Pets is 4
```

#### immutable

**immutable**即**不可变类型**，一旦创建完就不可修改。Golang中字符串就是不可变类型。

```go
package main

import "fmt"

func mutate(s string) string {
    s[0] = 'a'
    return s
}

func main() {
    str := "hello"
    fmt.Println(mutate(str))
}
```

[Go Playground在线运行](https://play.golang.org/p/EICoR_Vb3A1) 这里我们尝试将字符串的第一个字符改为`a`，由于字符串类型是immutable的，因此执行报错：

```
cannot assign to s[0]
```

如果我们确实想改变字符串中的某个字符，可以先将字符串转换为符文(rune)切片，通过切片去修改，完事之后再转换回字符串即可。

```go
package main

import "fmt"

func mutate(s []rune) string {  
    s[0] = 'a' 
    return string(s)
}
func main() {  
    str := "hello"
    fmt.Println(mutate([]rune(str)))
}
```

[Go Playground在线运行](https://play.golang.org/p/WbEwN2pG7it) 该程序先将字符串`str`转换成了符文切片，通过切片将第一个字符改为`a`，然后再将切片转换回字符串。输出为：`aello`。
