字符

字符 属于 Julia

4 个练习

关于 字符

这是一个很大的话题! 完全可以为它写一本厚书,事实上也有不少人这么做了(在 Amazon 上搜索“unicode book”就能看到一些例子)。

极简历史

在更早的几十年里,计算机处理字符要简单得多,因为当时的程序员认为英语是唯一重要的语言。 于是,26 个字母(大写和小写)、10 个数字、几个标点符号,再加上一个用来响铃的编码(0x07),全部装进了 7 位里:这就是 ASCII 字符集。

很自然地,有人开始问:那 à、ä 和 Ł 呢?接着又有其他人问起 ऄ、ஹ 和 ญ,而年轻人则想要 emoji 😱。 该怎么办呢?

长话短说,许多聪明又有耐心的人在委员会里工作了好几年,敲定 Unicode 字符集以及 UTF-8 这类编码的细节,而大量软件也需要进行极其复杂的重写。 与此同时,还引入了许多新 bug。

为了防止所有东西都出问题,Unicode/UTF-8 的设计保证前 127 个编码与 ASCII 完全相同*(连响铃也不例外)*。

Julia 中的字符

2005 年前后之后设计的语言有一个巨大的优势:一个相当稳定的 Unicode 标准已经存在。

Julia(2012 年首次发布)可以假定一切都将是 Unicode:字符、字符串、变量名和函数名、数学运算符……

引用手册中的话:“Julia 让处理纯 ASCII 文本变得简单而高效,处理 Unicode 也尽可能简单高效。”注意“尽可能”这几个字,它是这句话里很重要的一部分。

字符字面量用单引号书写,与用双引号书写的字符串不同。

这对来自 C/C++ 世界的人来说显而易见,但对 Python 和 JavaScript 程序员来说可能会让人困惑。

julia> a = 'a'  # Roman alphabet
'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)

julia> typeof(a)
Char

julia> jha = 'झ'  # Devanagari alphabet
'झ': Unicode U+091D (category Lo: Letter, other)

julia> typeof(jha)
Char


julia> '❤'  # heart emoji
'❤': Unicode U+2764 (category So: Symbol, other)

从这些例子可以看到,类型是 Char,而且 Julia 还提供了关于字符类别的更多信息。

仔细观察会发现,这些字符可以用 4 个十六进制数字表示。 完整的字符集最多需要 6 个十六进制数字。

这些数字称为“码点”,目前范围从 U+0000 到 U+10FFFF。 它们会在 REPL 中显示出来,但在代码里要用 codepoints() 获取。

在 Char 和 Int 之间转换很简单:

julia> Int('a')
97

julia> Char(97)
'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)

编译器允许对 Char 进行某些形式的整数运算:

julia> 'b' - 'a'  # interval, in alphabetic order
1

julia> 'b' + 'a'
ERROR: MethodError: no method matching +(::Char, ::Char)

julia> 'a' + 5
'f': ASCII/Unicode U+0066 (category Ll: Letter, lowercase)

julia> 'f' + ('A' - 'a')  # same as `uppercase('f')`
'F': ASCII/Unicode U+0046 (category Lu: Letter, uppercase)

字符相关函数

一部分字符串处理函数也能作用于 Char 输入。

islowercase('A')  # false
uppercase('γ')  # 'Γ': Unicode U+0393 (category Lu: Letter, uppercase)
ispunct('@')  # true
isdigit('A')  # false
isxdigit('A')  # true

要检查某个字符是否出现在字符串中,可以用 in。 注意,这与子字符串不同:

# char search
julia> 'a' ∈ "xabcahliazlnkiw"
true

# substring search
julia> "a" ∈ "xabcahliazlnkiw"
use occursin(needle, haystack) for string containment

此外,正则表达式(属于另一个概念)能实现强大的搜索和操作。

Char 向量与字符串的相互转换

要把字符串转换为字符向量,可以用 collect()。

要把字符向量转换为字符串,可以用 String() 构造函数。

julia> s = "abcde"
"abcde"

julia> cv = collect(s)
5-element Vector{Char}:
 'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)
 'b': ASCII/Unicode U+0062 (category Ll: Letter, lowercase)
 'c': ASCII/Unicode U+0063 (category Ll: Letter, lowercase)
 'd': ASCII/Unicode U+0064 (category Ll: Letter, lowercase)
 'e': ASCII/Unicode U+0065 (category Ll: Letter, lowercase)

julia> String(cv)
"abcde"

这对任何字符都适用,不只是 ASCII。

julia> collect("❤,😱")
3-element Vector{Char}:
 '❤': Unicode U+2764 (category So: Symbol, other)
 ',': ASCII/Unicode U+002C (category Po: Punctuation, other)
 '😱': Unicode U+1F631 (category So: Symbol, other)

注意,String() 构造函数作用于向量。 要把单个 Char 强制转换为长度为 1 的字符串,应使用 string() 函数,注意是小写的 s。

julia> string('a')
"a"

存储

到目前为止,本文里的内容似乎都相对简单,那么真的没什么可担心的吗?

可惜,这种想法太乐观了!

一个麻烦来自每个码点“最多”需要 6 个十六进制数字。 这意味着,当用 UTF-8 编码时,不同的字符在内存中需要不同大小的空间。

一个字节只能存储最大到 255 的(无符号)数字,也就是两个十六进制数字,因此 UTF-8 用可变数量的字节(1 到 4 个)来存储一个 Char。 这些字节称为“代码单元”,ncodeunits() 函数会返回给定字符所需的代码单元数量。

julia> codepoint(jha)  # jha 'झ' is defined in an earlier example
0x0000091d

julia> ncodeunits(jha)
3

julia> ncodeunits('a')  # ASCII character
1

julia> ncodeunits('😱')  # emoji
4

另外,并非所有能显示在屏幕上的东西都有自己独一无二的码点。 有些视觉上不同的字符被视为派生自其他字符,因此 Unicode 把它们当作一个父字符加上一个修饰符来处理。

通过 GitHub 编辑 该链接会在新窗口或标签页中打开

学习 字符

练习已锁定

再解锁 3 个练习即可练习 字符