這是個_龐大_的主題! 要為它寫出一本厚書是有可能的,而且真的有好幾個人這麼做了(在 Amazon 搜尋「unicode book」就能看到一些例子)。
在幾十年前,電腦處理字元要簡單得多,當時的程式設計師都假設英文是唯一重要的語言。
所以:26 個字母(含大小寫)、10 個數字、幾個標點符號,再加上一個會讓鈴響的代碼(0x07),而這一切全都塞得進 7 個位元:ASCII 字元集。
理所當然地,有人開始問那 à、ä 和 Ł 怎麼辦,接著又有人問起 ऄ、ஹ 和 ญ,而年輕人想要 emoji 😱。 該怎麼辦呢?
長話短說,許多聰明又有耐心的人得在委員會裡待上好幾年,研究 Unicode 字元集以及像 UTF-8 這類編碼的種種細節,而大量的軟體需要進行_極度_複雜的改寫。 不僅如此,還引入了許多新的 bug。
為了防止_所有東西_都壞掉,Unicode/UTF-8 的設計確保前 127 個編碼與 ASCII 完全相同_(連那個鈴也是)_。
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輸入。
uppercase() 和 lowercase() 改變大小寫。isuppercase() 和 islowercase() 測試大小寫。isletter(),涵蓋許多字母系統isdigit(),嚴格測試 0:9isnumeric(),比isdigit更廣,因此對 ¾ 和各種非歐洲文字系統會是true
isxdigit(),十六進位數字isascii(),Unicode 之前的字元ispunct(),標點符號isspace(),任何空白字元isprint(),可列印字元(相反的是iscntrl())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()。
要把 Char 向量轉成字串,則有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()建構子作用在 Vector 上。
若要把單一個Char_轉型_成單一字元的字串,要用的函式是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 會把它們當成一個母字元加上一個修飾符來處理。