字符串中的符文

runes within strings

提问人:Farhan Alvi 提问时间:3/26/2023 最后编辑:colm.anseoFarhan Alvi 更新时间:3/26/2023 访问量:94

问:

我正在阅读 Go By Example,字符串和符文部分非常令人困惑。

运行此命令:

    sample := "\xbd\xb2\x3d\xbc\x20\xe2\x8c\x98"
    fmt.Println(sample)
    fmt.Printf("%%q: %q\n", sample)
    fmt.Printf("%%+q: %+q\n", sample)

产生以下结果:

��=� ⌘
%q: "\xbd\xb2=\xbc ⌘"
%+q: "\xbd\xb2=\xbc \u2318"

..这很好。第 1、2 和第 4 个符文似乎是不可打印的,我猜这意味着 ,并且根本不受 Unicode 或其他东西的支持(如果我错了,请纠正我),因此它们显示为 .两者都正确地转义了这 3 个不可打印的符文。\xbd\xb2\xbc%q%+q

但是现在,当我像这样遍历字符串时:

    for _, runeValue := range sample {
        fmt.Printf("% x, %q, %+q\n", runeValue, runeValue, runeValue)
    }

突然间,这 3 个不可打印的符文没有被转义,而是保持为 ,并试图揭示它们的底层代码点,这显然是不正确的:%q%+q

 fffd, '�', '\ufffd'
 fffd, '�', '\ufffd'
 3d,   '=' ,  '='
 fffd, '�', '\ufffd'
 20,   ' ' ,  ' '
 2318, '⌘', '\u2318'

更奇怪的是,如果我将字符串作为字节切片进行迭代:

    for _, runeValue := range []byte(sample) {
        fmt.Printf("% x, %q, %+q\n", runeValue, runeValue, runeValue)
    }

突然之间,这些符文不再是不可打印的,它们的基础代码点是正确的:

 bd, '½', '\u00bd'
 b2, '²', '\u00b2'
 3d, '=', '='
 bc, '¼', '\u00bc'
 20, ' ', ' '
 e2, 'â', '\u00e2'
 8c, '\u008c', '\u008c'
 98, '\u0098', '\u0098'

有人可以解释一下这里发生了什么吗?

字符串 Go Unicode UTF-8

评论

1赞 JimB 3/26/2023
官方文档可能是一个更好的资源,go.dev/blog/strings
0赞 Farhan Alvi 3/26/2023
@JimB 是的,这实际上是开头的字符串的来源

答:

0赞 colm.anseo 3/26/2023 #1

fmt.Printf将在幕后做很多魔术,通过型式检查等提供尽可能多的有用信息。如果要验证字符串(或字节切片)是否有效,请使用标准库包。UTF-8encoding/utf8

例如:

import "unicode/utf8"

var sample = "\xbd\xb2\x3d\xbc\x20\xe2\x8c\x98"

fmt.Printf("%q valid? %v\n", sample, utf8.ValidString(sample)) // reports "false"

扫描字符串的各个符文,我们可以确定是什么使这个字符串无效(从编码的角度来看)。注意:十六进制值表示遇到了错误的符文。此错误值定义为包常量 utf8。符文错误UTF-80xfffd

for _, r := range sample {

    validRune := r != utf8.RuneError // is 0xfffd? i.e. bad rune?

    if validRune {
        fmt.Printf("'%c' validRune: true   hex: %4x\n", r, r)
    } else {
        fmt.Printf("'%c' validRune: false\n", r)
    }
}

https://go.dev/play/p/9NO9xMvcxCp

生产:

'�' validRune: false
'�' validRune: false
'=' validRune: true   hex:   3d
'�' validRune: false
' ' validRune: true   hex:   20
'⌘' validRune: true   hex: 2318

评论

0赞 Farhan Alvi 3/26/2023
这真是令人大开眼界。但是,如果这些字节是无效的 utf-8,那么我如何在第二个循环中获得 1/2、² 和 1/4?经过进一步检查,utf-8 中的 1/2 似乎是“\xc2\xbd”,² 是“\xc2\xb2”,1/4 是“\xc2\xbc”,所以它们本质上都只需要后面的“\xc2”就可以是有效的 utf-8。然而,第二个循环在没有它们的情况下工作吗?
0赞 JimB 3/26/2023
@FarhanAlvi,当您转换为 u 时,您不再解码为 utf8,您只是将类型转换为一系列原始字节。[]byte
0赞 colm.anseo 3/26/2023
假设 A 符合 UTF-8 标准(因为它的后备数组只是一个字节数组 - 这当然可以是不是 UTF-8 的垃圾字节)。第二个循环的范围是字节切片 - 这非常不同。在这里,每个原始字节都被读取 - 而不是字符串大小写的符文。一个字节只有 1 个字节。一个符文最多有 4 个字节(在封面下,符文只是一个 )。从 UTF-8 等编码(或任何其他编码)中扫描随机字节将产生不可预知的结果。例如,ASCII 字节低于十六进制 - 而那些流氓字节高于十六进制。stringint320x80