字元

字元 在 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 向量與字串的相互轉換

要把字串轉成 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 會把它們當成一個母字元加上一個修飾符來處理。

透過 GitHub 編輯 連結會在新視窗或分頁中開啟

學習 字元

練習已鎖定

再解鎖 3 個練習,就能練習 字元