14、strings

144 阅读4分钟

本文为译文 strings

go里string值得被提一下,因为它的实现方式和其他语言不一样。

在go中一个字符串是字节组(a slice of bytes)

在go中sting是unicode兼容的而且全部是utf8编码

访问string中的每一个独立字符

package main

import (  
    "fmt"
)

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

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

func main() {  
    name := "Hello World"
    fmt.Printf("String: %s\n", name)
    printChars(name)
    fmt.Printf("\n")
    printBytes(name)
    fmt.Printf("\n\n")
    name = "Señor"
    fmt.Printf("String: %s\n", name)
    printChars(name)
    fmt.Printf("\n")
    printBytes(name)
}

String: Hello World  
Characters: H e l l o   W o r l d  
Bytes: 48 65 6c 6c 6f 20 57 6f 72 6c 64 

String: Señor  
Characters: S e à ± o r  
Bytes: 53 65 c3 b1 6f 72  

这里有个很严重的bug,我们试图去打印Señor的每个字符,但是结果却是,S e à ± o r。为啥错了?

字符**ñ****unicode**码点是**U+00F1**,其**UTF8**编码的占两个字节,分别是**c3**``**b1**。按我们现在的逻辑认为每个字符都是一个字节。所以错了。utf8编码中,一个code可以占多个字节。

所以我们该如何解决这个问题呢?这里就有了rune了。

rune

rune是go的内置类型,也是int32的别名。在go中Rune就代表了一个码点。不管这个码点占据多少个字符,他都可以被Rune代表。

让我们修改上面的程序,来用Rune实现。

package main

import (  
    "fmt"
)

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

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

func main() {  
    name := "Hello World"
    fmt.Printf("String: %s\n", name)
    printChars(name)
    fmt.Printf("\n")
    printBytes(name)
    fmt.Printf("\n\n")
    name = "Señor"
    fmt.Printf("String: %s\n", name)
    printChars(name)
    fmt.Printf("\n")
    printBytes(name)
}

String: Hello World  
Characters: H e l l o   W o r l d  
Bytes: 48 65 6c 6c 6f 20 57 6f 72 6c 64 

String: Señor  
Characters: S e ñ o r  
Bytes: 53 65 c3 b1 6f 72

使用range循环获取单个的rune

上面的程序是一种完美的形式去迭代stirng的每一个rune。但是go提供了一种更简单的方式去做这个事情。

package main

import (  
    "fmt"
)

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

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

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

从上面的输出可以看出,ñ占了两个字节。因为上面的字符o开始的是byte4而不是byte3。

从一段字节中创建一个字符串。

package main

import (  
    "fmt"
)

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

上面的的第8行程序,是字符串Café的utf8编码。程序输出Café

如果我们输十六进制的十进制整数。上面的程序会怎么工作呢?

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é

string length

utf8包的RuneCountInString(s string) (n int)函数,可以被用来查找string的长度。这个函数的参数是string返回rune的数量。

正如我们之前讨论的,len(s)被用作查找string bytes的数量,不会返回string的长度。我们之前讨论过很多uicode字符的码点占据了超过一个字节。使用len去查找这些string的长度,将会返回错误的string长度。

package main

import (  
    "fmt"
    "unicode/utf8"
)

func main() {  
    word1 := "Señor"
    fmt.Printf("String: %s\n", word1)
    fmt.Printf("Length: %d\n", utf8.RuneCountInString(word1))
    fmt.Printf("Number of bytes: %d\n", len(word1))

    fmt.Printf("\n")
    word2 := "Pets"
    fmt.Printf("String: %s\n", word2)
    fmt.Printf("Length: %d\n", utf8.RuneCountInString(word2))
    fmt.Printf("Number of bytes: %d\n", len(word2))
}

String: Señor  
Length: 5  
Number of bytes: 6

String: Pets  
Length: 4  
Number of bytes: 4  

string 比较

The == operator is used to compare two strings for equality. If both the strings are equal, then the result is true else it's false.

package main

import (  
    "fmt"
)

func compareStrings(str1 string, str2 string) {  
    if str1 == str2 {
        fmt.Printf("%s and %s are equal\n", str1, str2)
        return
    }
    fmt.Printf("%s and %s are not equal\n", str1, str2)
}

func main() {  
    string1 := "Go"
    string2 := "Go"
    compareStrings(string1, string2)

    string3 := "hello"
    string4 := "world"
    compareStrings(string3, string4)
}

string 拼接

字符串拼接有多种形式。

  • +拼接

    string1 := "Go" string2 := "is awesome" result := string1 + " " + string2 fmt.Println(result)

  • 使用fmt包的Sprintf函数。

Sprintf函数根据输入格式的标识符返回string的结果。

string1 := "Go"
string2 := "is awesome"
result := fmt.Sprintf("%s %s", string1, string2)
fmt.Println(result)

string的不可变性(Strings are immutable)

Strings are immutable in Go. Once a string is created it's not possible to change it.

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

./prog.go:8:7: cannot assign to s[0]

解决string不可变的方案就是把string转成slice of 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)))
}