Go语言 字符串


导言

  • 原文链接: Part 14: Strings
  • If translation is not allowed, please leave me in the comment area and I will delete it as soon as possible.

字符串

Go语言 的字符串值得一提,因为与其它语言相比,Go语言 字符串的实现方式有所不同。


字符串是什么?

Go语言 中,字符串是 字节 的集合。通过将内容放置于 " " 之间,我们可以创建一个字符串。看看下面的例子。

package main

import (  
    "fmt"
)

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

上面的程序将会输出: Hello World

Go 中的字符串符合 Unicode,并通过 UTF-8 进行编码。


访问字符串的单个字节

因为字符串是 字节 的集合,所以我们可以访问字符串的每个字节。

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)
}

在上面程序的第 8 行,len(s) 返回字符串有多少个字节,并且,利用 for循环,我们打印出了这些字节的十六进制形式。%x 是十六进制的格式限定符。最终,程序将会输出:

48 65 6c 6c 6f 20 57 6f 72 6c 64

这就是 Hello WorldUTF-8编码。

UnicodeUTF-8 的基本理解,将有利于我们认识字符串。我推荐你阅读 这篇文章,它能你知道什么是Unicode、什么是 UTF-8

上面的这篇文章是全英文的。

修改一下上面的代码,我们就能够打印字符串的 字符 了。

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.Printf("\n")
    printChars(name)
}

在上面程序的第 16 行,%c 格式限定符,能被使用去输出字符串的字符。程序输出如下:

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

虽然看起来,上面的程序能够访问字符串的每个字符,但这其实存在一个严重的 BUG

让我们修改一下代码,看看到底存在什么 BUG

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.Printf("\n")
    printChars(name)
    fmt.Printf("\n")
    name = "Señor"
    printBytes(name)
    fmt.Printf("\n")
    printChars(name)
}

在上面程序的第 28 行,我们试图打印 Señor,然而输出却是 S e à ± o r

为什么打印 Señor 会出错,而打印 Hello World 却不会呢?这是因为 ñUnicode码点 是 U+00F1,它的 UTF-8编码 占用 2 个字节:c3b1,而当我们输出字符时,我们认为每个字符都只占用 1 个字节,显然,这是错误的 — 在 UTF-8编码中,有的码点需要占用多个字节。

那我们如何解决这个问题呢?rune 出现了!


rune

rune 是一个 Go语言 的内建类型,它是 int32 的别名。在 Go 中,rune 表示 1Unicode码点。无论码点占用了多少个字节,rune 都能表示它。

通过 rune,我们修改一下上面的代码,实现输出字符串字符的目的。修改后的代码如下:

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.Printf("\n")
    printChars(name)
    fmt.Printf("\n\n")
    name = "Señor"
    printBytes(name)
    fmt.Printf("\n")
    printChars(name)
}

在上面程序的第 14 行,s 被转换为 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循环 遍历字符串

通过rune,上面的代码可以完美地遍历字符串的字符。但 Go 提供了一种更简洁的方式 — for range循环,通过这个循环,我们也可以实现相同的目的。

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)
}

在上面程序的第 8 行,我们使用 for range 遍历了 字符串s。该循环返回 rune 对应字节的起始索引,以及 rune 的值。

程序输出如下:

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 个字节。


将 字节切片 构建为 字符串

package main

import (  
    "fmt"
)

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

上面程序的 byteSlice,它的内容是 Café 的 十六进制UTF-8编码。这个程序会输出:Café

假如我们采用对应的 十进制UTF-8编码,程序是否还能正常运行呢?来检验一下:

package main

import (  
    "fmt"
)

func main() {  
    byteSlice := []byte{67, 97, 102, 195, 169}//decimal equivalent of {'\x43', '\x61', '\x66', '\xC3', '\xA9'}
    str := string(byteSlice)
    fmt.Println(str)
}

这个程序同样会打印:Café


rune切片 构建为 字符串

package main

import (  
    "fmt"
)

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

在上面的程序中,runeSlice 包含了 字符串Señor 的 十六进制Unicode码点。该程序输出也为:Señor


字符串的长度

utf8包 的 RuneCountInString函数 能够获取字符串的长度。这个函数将字符串作为参数,并返回该字符串的 rune 个数。

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)
}

程序输出为:

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

len函数 也能获取字符串的长度,但它获取的是该字符串的 byte 个数。

fmt.Println(len("你好"), utf8.RuneCountInString("你好"))

此时输出是:

6 2

字符串是不可修改的

Go语言 中,字符串一经创建就不能修改。

package main

import (  
    "fmt"
)

func mutate(s string)string {  
    s[0] = 'a'//any valid unicode character within single quote is a rune 
    return s
}
func main() {  
    h := "hello"
    fmt.Println(mutate(h))
}

在上面程序的第 8 行,我们试图去修改 字符串s 的第 1 个字符,这是不允许的,因为字符串无法修改。于是,程序会抛出错误:main.go:8: cannot assign to s[0]

为了解决字符串的不可修改性,我们可以将字符串转为 rune类型 切片,之后修改这个切片,最后将该切片转换为一个新的字符串。

package main

import (  
    "fmt"
)

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

在上面程序的第 7 行,mutate函数 接收一个rune切片,随后将该切片的第 1 个元素修改为 a,最后将该切片转换为 string 进行返回。在程序的第 13 行,h 转换为 rune切片 后,传入了 mutate函数。程序最终会输出:aello

这就是字符串了。

祝你早生贵子~


原作者留言

我已经将上面的东西整合到了一个程序,你可以在 github 下载它。

优质内容来之不易,您可以通过该 链接 为我捐赠。


最后

感谢原作者的优质内容。

欢迎指出文中的任何错误。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值