Go 中的rune类型是int32的别名。
rune类型底层是int32,因此它存储的是一个有符号的 32 位整数值。
不过,与int32类型不同,rune类型中存储的整数值表示单个 Unicode 字符。
Unicode 是 ASCII 的超集,它为每个字符分配一个唯一的编号,以此来表示字符。 这个唯一的编号称为 Unicode 码点。 Unicode 的目标是把世界上所有的字符都表示为 Unicode 码点,包括各种字母、数字、符号,甚至 emoji。
在 Go 中,rune类型表示单个 Unicode 码点。
下表列出了一些示例 Unicode 字符及其 Unicode 码点和十进制值:
| Unicode 字符 | Unicode 码点 | 十进制值 |
|---|---|---|
| 0 | U+0030 |
48 |
| A | U+0041 |
65 |
| a | U+0061 |
97 |
| ¿ | U+00BF |
191 |
| π | U+03C0 |
960 |
| 🧠 | U+1F9E0 |
129504 |
UTF-8 是一种变长字符编码,用于把每个 Unicode 码点编码为 1、2、3 或 4 个字节。
由于一个 Unicode 码点最多可以编码为 4 个字节,rune类型需要能够容纳最多 4 个字节的数据。
这就是rune类型是int32的别名的原因,因为int32类型能够容纳最多 4 个字节的数据。
Go 源代码文件使用 UTF-8 编码。
声明rune类型的变量时,把字符放在单引号中:
myRune := '¿'
由于rune只是int32的别名,打印 rune 的类型会得到int32:
myRune := '¿'
fmt.Printf("myRune type: %T\n", myRune)
// Output: myRune type: int32
同样,打印 rune 的值会得到它的整数(十进制)值:
myRune := '¿'
fmt.Printf("myRune value: %v\n", myRune)
// Output: myRune value: 191
要打印 rune 所表示的 Unicode 字符,可以使用%c格式化动词:
myRune := '¿'
fmt.Printf("myRune Unicode character: %c\n", myRune)
// Output: myRune Unicode character: ¿
要打印 rune 所表示的 Unicode 码点,可以使用%U格式化动词:
myRune := '¿'
fmt.Printf("myRune Unicode code point: %U\n", myRune)
// Output: myRune Unicode code point: U+00BF
Go 中的字符串使用 UTF-8 编码,这意味着它们包含 Unicode 字符。 字符串中的字符根据所表示的 Unicode 字符,以 1、2、3 或 4 个字节存储和编码。
在 Go 中,切片用来表示序列,这些切片可以用 range 来迭代。
当我们迭代一个字符串时,Go 会把字符串转换成一系列 rune,每个 rune 占 4 个字节(记住,rune 类型是int32的别名!)
尽管字符串只是一个字节切片,但range关键字迭代的是字符串的 rune,而不是它的字节。
在这个例子中,index变量表示当前 rune 字节序列的起始下标,char变量表示当前的 rune:
myString := "❗hello"
for index, char := range myString {
fmt.Printf("Index: %d\tCharacter: %c\t\tCode Point: %U\n", index, char, char)
}
// Output:
// Index: 0 Character: ❗ Code Point: U+2757
// Index: 3 Character: h Code Point: U+0068
// Index: 4 Character: e Code Point: U+0065
// Index: 5 Character: l Code Point: U+006C
// Index: 6 Character: l Code Point: U+006C
// Index: 7 Character: o Code Point: U+006F
由于 rune 可以以 1、2、3 或 4 个字节存储,字符串的长度不一定等于字符串中字符的数量。
使用内置的len函数可以获取字符串以字节为单位的长度,使用utf8.RuneCountInString函数可以获取字符串中 rune 的数量:
import "unicode/utf8"
myString := "❗hello"
stringLength := len(myString)
numberOfRunes := utf8.RuneCountInString(myString)
fmt.Printf("myString - Length: %d - Runes: %d\n", stringLength, numberOfRunes)
// Output: myString - Length: 8 - Runes: 6
你的任务是创建一个日志库,用来协助管理组织的日志。 这个库将让用户能够识别某条日志是由哪个应用程序发出的,修复损坏的日志,以及判断某行日志是否在指定的字符数限制之内。
日志来自多个应用程序,每个应用程序都使用自己专有的日志格式。 在把日志存入日志聚合系统之前,必须先识别出这条日志是由哪个应用程序发出的。
实现Application函数,它接收一行日志,返回发出这行日志的应用程序。
要识别某行日志是由哪个应用程序发出的,请在日志行中查找下表指定的某个字符:
| 应用程序 | 字符 | Unicode 码位 |
|---|---|---|
recommendation |
❗ | U+2757 |
search |
🔍 | U+1F50D |
weather |
☀ | U+2600 |
如果某行日志不包含上表中的任何字符,就向调用方返回default。
如果某行日志包含上表中的多个字符,则返回日志行中从左到右最先找到的那个字符所对应的应用程序。
Application("❗ recommended search product 🔍")
// => recommendation
由于日志基础设施中存在一个罕见但顽固的 bug,日志中的某些字符可能会损坏。 在花时间找出这些损坏的字符及其原始值之后,你决定更新日志库,帮助修复损坏的日志。
实现Replace函数,它接收一行日志、一个损坏的字符和原始值,返回修改后的日志行,其中所有出现的损坏字符都已替换为原始值。
log := "please replace '👎' with '👍'"
Replace(log, '👎', '👍')
// => please replace '👍' with '👍'"
负责显示日志的系统对每行日志可以显示的字符数有限制。 因此,用户希望这个库包含一个辅助函数,用来判断某行日志是否在指定的字符数限制之内。
实现WithinLimit函数,它接收一行日志和字符数限制,返回该日志行是否在字符数限制之内。
WithinLimit("hello❗", 6)
// => true