Go 语言类型 字符型
基本概念
字符(rune)用于表示 Unicode 字符,本质上是 int32
类型别名。字符通过使用 int32
类型的数字来保存其 Unicode 编码,Unicode 码点取值范围从 0
到 0x10FFFF
,总共有 1114112 个码点。
UTF
UTF(Unicode Transformation Format, Unicode 字符集转换格式)是一种编码方式,用于将 Unicode 字符集的字符表示为字节序列,是 Unicode 标准的一部分。目前主流 UTF 编码格式有三种:
- UTF-8:采用 1 到 4 个字节的可变长度字符编码方式,完全兼容 ASCII 编码。例如,西文字符通常使用 1 个字节,希腊字母需要 2 个字节,而东亚字符(CJK)则需要 3 个字节。Go 语言中默认使用 UTF-8 编码。
- UTF-16:使用 2 或 4 个字节无符号整数存储 Unicode 字符,有大端(Big Endian)和小端(Little Endian)之分。处理中文时相比于 UTF-8 更加高效。
- UTF-32:采用 4 个字节固定长度编码,编码和解码简单。适用于处理速度优先于存储效率的场合。
Unicode 编码表示格式(BMP)为 U+hhhh
,其中 h
代表一个十六进制数字。在 Go 语言中,可以直接使用这种格式定义 Unicode 字符:
package main
import "fmt"
func main() {
var ch rune = '\u03B2' // Unicode 编码表示
fmt.Printf("%d\n", ch) // 输出整数:946
fmt.Printf("%c\n", ch) // 输出实际字符:β
fmt.Printf("%X\n", ch) // 输出 Unicode 编码值:3B2
fmt.Printf("%U\n", ch) // 输出 Unicode 标准形式:U+03B2
utf8Bytes := []byte(string(rune(ch))) // UTF-8 编码多字节字符必须转为字节切片
fmt.Printf("%#X", utf8Bytes) // 输出十六进制格式:0XCEB2
}
转义字符
转义字符用来表示在字符串中不能直接表示的字符。转移字符以一个反斜杠 \
开始,后跟一个或多个字符来构成一个转义序列:
转义序列 | 说明 |
---|---|
\\ |
反斜杠字符「\」 |
\' |
单引号字符「'」 |
\" |
双引号字符「"」 |
\? |
疑问号字符「?」 |
\b |
退格符 |
\f |
换页符 |
\n |
换行符 |
\r |
回车符 |
\t |
水平制表符 |
\v |
垂直制表符 |
声明和初始化
字符值需要用单引号 '
括起来:
package main
import "fmt"
func main() {
// 类型零值为 0
var a rune
// 使用 Unicode 编码值初始化,支持补充平面
var b = '\u0063' + '\U00010330'
// 短变量声明,使用字面量初始化
c := 'c'
// 表达式赋值,使用十六进制数字初始化
d := rune(0x64)
fmt.Println(a, b, c, d) // 打印出整数
fmt.Printf("%c %c %c %c \n", a, b, c, d) // 打印字符
}
字符型切片
字符型切片用于正确处理可能包含多字节字符的 UTF-8 编码字符串:
package main
import "fmt"
func main() {
// 直接创建一个字符型切片并初始化其值
runeSlice := []rune{'G', 'o', '语', '言'}
// 将字符串转换为字符切片,来处理每个 Unicode 字符
runeSliceFromString := []rune("你好世界")
fmt.Println(runeSlice) // 输出字符切片的数字表示:[71 111 35821 35328]
fmt.Printf("%q\n", runeSliceFromString) // 输出字符形式:['你' '好' '世' '界']
}
字符处理
在 Go 语言中,unicode/utf8
和 unicode
包提供了不同但互补的功能集,用于处理 Unicode 字符和 UTF-8 编码。
字符分类
unicode
库定义了多个字符范围表,代表不同字符类别,如字母(unicode.Letter
)、数字(unicode.Digit
)、标点符号(unicode.Punct
)等。每个类别都对应一个特定函数,用于检查字符是否属于该类别。此外还提供检查空白字符、字母大小写、可打印等字符属性的函数:
package main
import (
"fmt"
"unicode"
)
func main() {
// 下面结果都为 true
fmt.Println(unicode.IsLetter('a')) // 检测字母
fmt.Println(unicode.IsDigit('1')) // 检测数字
fmt.Println(unicode.IsPunct(',')) // 检测标点符号
fmt.Println(unicode.IsSpace('\t')) // 检测空白字符,包括空格、制表符、回车符等
fmt.Println(unicode.IsLower('b')) // 检测是否小写
fmt.Println(unicode.IsPrint('c')) // 检测是否可打印
}
字符属性检查函数都返回布尔值,表示字符是否属于相应分类。
大小写转换
unicode
包中大小写转换功能基于 Unicode 标准,因此支持多种语言字符,包括希腊语、俄语等小语种语言:
package main
import (
"fmt"
"unicode"
)
func main() {
fmt.Println(string(unicode.ToUpper('a'))) // 英语,输出大写:A
fmt.Println(string(unicode.ToUpper('ł'))) // 波兰语,输出大写:Ł
fmt.Println(string(unicode.ToUpper('μ'))) // 希腊语,输出大写:Μ
fmt.Println(string(unicode.ToUpper('б'))) // 俄语,输出大写:Б
fmt.Println(string(unicode.ToUpper('啊'))) // 东亚文字没有大小写之分,输出不变:啊
fmt.Println(string(unicode.ToUpper('𝗲'))) // 符号也没有大小写之分,输出不变:𝗲
}
语言检测
上面提到 unicode
库内有多个字符范围表(RangeTable
),有些范围表代表某种语言字母表,可以直接配合 unicode.Is
函数使用,检测字符是否属于该语言:
package main
import (
"fmt"
"unicode"
)
func main() {
// 示例字符
chars := []rune{'α', 'β', 'A', 'Ω', 'π', 'z'}
// 遍历字符并检查是否是希腊字符
for _, r := range chars {
// 希腊字符表包含在 unicode.Greek 中
if unicode.Is(unicode.Greek, r) {
fmt.Printf("%c is a Greek character.\n", r)
} else {
fmt.Printf("%c is not a Greek character.\n", r)
}
}
}
但并不是每种语言都有专用范围表,例如中文横跨多个 Unicode 字符区块,并且和日语有共用字符,这种情况可以自定义一个 RangeTable
来表达:
package main
import (
"fmt"
"unicode"
)
// 日语字符范围
var japanese = &unicode.RangeTable{
R16: []unicode.Range16{
{0x3040, 0x309F, 1}, // 平假名
{0x30A0, 0x30FF, 1}, // 片假名
{0xFF66, 0xFF9D, 1}, // 半角片假名
},
R32: []unicode.Range32{
{0x1B000, 0x1B0FF, 1}, // Kana Supplement
{0x1B100, 0x1B12F, 1}, // Kana Extended-A
},
}
// 中文字符范围
var chinese = &unicode.RangeTable{
R16: []unicode.Range16{
{0x4E00, 0x9FFF, 1}, // 常用汉字
},
R32: []unicode.Range32{
{0x20000, 0x2A6DF, 1}, // 扩展 B
{0x2A700, 0x2B73F, 1}, // 扩展 C
{0x2B740, 0x2B81F, 1}, // 扩展 D
{0x2B820, 0x2CEAF, 1}, // 扩展 E
{0x2CEB0, 0x2EBEF, 1}, // 扩展 F
},
}
func main() {
// 遍历字符并检查是否是在字符集中
for _, r := range []rune{'a', 'β', '你', 'ん'} {
if unicode.In(r, japanese, chinese) {
fmt.Printf("%c is a Japanese or Chinese character.\n", r)
}
}
}
如果要求没那么严格,也可以使用预定义字符集。例如 unicode.Han
中包括简体字、繁体字、日文汉字和韩文汉字,unicode.Hiragana
包含日文平假名,应付一般语言判断场合够用。
UTF-8 编码
unicode/utf8
包专注于处理 UTF-8 编码文本,常用功能包括验证、编码和解码:
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
// 验证字符串编码是否有效,对无效编码解码会得到 \uFFFD
fmt.Println(utf8.ValidString("Hello, 世界")) // 输出:true
fmt.Println(utf8.ValidString(string([]byte{0xff, 0xfe, 0xfd}))) // 输出:false
// 解码单个 Unicode 字符,返回单个字符和字符占用字节数
r, size := utf8.DecodeRuneInString("こんにちは")
fmt.Printf("%c %v\n", r, size) // 输出:'こ' 和 3
// 编码单个 Unicode 字符,返回编码后占用字节数
buf := make([]byte, 4) // 创建目标字节切片
n := utf8.EncodeRune(buf, 'あ') // 将 UTF-8 编码写入切片
fmt.Printf("% x %v\n", buf[:n], n) // 输出:e3 81 82 和 3
}