Golang字符串操作:UTF-8编码与字节切片详解

Golang字符串操作:UTF-8编码与字节切片详解 大家好,我正在阅读这篇关于Go语言字符串的博客:https://go.dev/blog/strings

我尝试进行了一些实验,发现 samplesample[0] 的输出结果不同,如下所示。我知道UTF-8编码的Unicode字符“½”(二分之一)应该是 "\xc2\xbd",所以 sample 的输出对我来说更合理。有人能帮我理解为什么打印单个字节却能产生正确的UTF-8编码字符吗?

package main

import "fmt"

func main() {
    var sample = "\xbd"

    fmt.Println("Println:")
    fmt.Println(sample)
    fmt.Printf("%q, %q\n", sample, sample[0])
}

更多关于Golang字符串操作:UTF-8编码与字节切片详解的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

打印/转换不同的数据类型。

package main

import "fmt"

func main() {
	var sample = "\xbd"
	banana := sample[0]

	// DB hex = 189 decimal
	fmt.Printf("banana: %d \n", banana)

	// 189 decimal in string (ASCII table) = ½ https://www.ascii-code.com/CP1252/189
	fmt.Printf("%[1]s, %[1]q, %[2]q, %[2]U, %[1]T, %[2]T", sample, banana)
}

输出: banana: 189 �, “\xbd”, ‘½’, U+00BD, string, uint8

更多关于Golang字符串操作:UTF-8编码与字节切片详解的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


等等,我不太明白你的问题是什么?你是想知道为什么用 %q 吗? %q 是一个用 Go 语法安全转义的双引号字符串。

参见:fmt/format.go

// fmtQ formats a string as a double-quoted, escaped Go string constant.
// If f.sharp is set a raw (backquoted) string may be returned instead
// if the string does not contain any control characters other than tab.
func (f *fmt) fmtQ(s string) {
	s = f.truncateString(s)
	if f.sharp && strconv.CanBackquote(s) {
		f.padString("`" + s + "`")
		return
	}
	buf := f.intbuf[:0]
	if f.plus {
		f.pad(strconv.AppendQuoteToASCII(buf, s))
	} else {
		f.pad(strconv.AppendQuote(buf, s))
	}
}

在Go语言中,字符串本质上是只读的字节切片([]byte),每个字节对应的是UTF-8编码的单个字节。当你使用 sample = "\xbd" 时,字符串包含的是单个字节 0xbd,而不是完整的UTF-8编码序列。

关键点解释:

  1. UTF-8编码规则:Unicode字符“½”(U+00BD)的完整UTF-8编码是 "\xc2\xbd"(两个字节)。但你的代码中 "\xbd" 只是一个字节,它本身是无效的UTF-8编码(因为UTF-8中,以 0xbd 开头的字节不是合法的起始字节)。
  2. 输出差异
    • fmt.Println(sample):直接打印字符串时,Go会尝试将字节解释为UTF-8字符。由于 0xbd 是无效编码,Go会使用Unicode替换字符(�,U+FFFD)来替代,因此输出可能显示为乱码或占位符。
    • sample[0]:访问 sample[0] 会返回字节值 0xbd(类型为 byte),而 fmt.Printf("%q", sample[0]) 会将该字节转换为ASCII/UTF-8可打印形式。由于 0xbd 不是可打印ASCII字符,%q 会将其转义为十六进制形式 "\xbd"

示例代码验证:

package main

import "fmt"

func main() {
    // 正确编码的Unicode字符 "½"
    correct := "\xc2\xbd"
    fmt.Printf("完整UTF-8编码: %q\n", correct) // 输出: "½"
    
    // 单个字节(无效UTF-8)
    singleByte := "\xbd"
    fmt.Printf("单个字节字符串: %q\n", singleByte) // 输出: "\xbd"(转义形式)
    fmt.Printf("字节值: 0x%x\n", singleByte[0])   // 输出: 0xbd
    
    // 遍历字节切片展示编码差异
    fmt.Println("\n字节切片对比:")
    fmt.Printf("correct (UTF-8): % x\n", []byte(correct))   // 输出: c2 bd
    fmt.Printf("singleByte:      % x\n", []byte(singleByte)) // 输出: bd
}

输出说明:

  • 当字符串包含无效UTF-8字节时,直接打印可能显示乱码,但通过 %q 或字节切片可以查看原始字节值。
  • 你的代码中 sample[0] 返回的是字节 0xbd%q 将其格式化为转义字符串 "\xbd",这不是正确的UTF-8字符“½”,而只是字节的字面量表示。

总结: 要正确表示Unicode字符“½”,必须使用完整的UTF-8编码 "\xc2\xbd"。单个字节 "\xbd" 是无效编码,其输出结果取决于上下文(打印方式)。

回到顶部