字串

字串是有限的字元序列。當然,當有人問什麼是字元時,真正的麻煩就來了。英語使用者熟悉的字元是字母 ABC 等,以及數字和常見的標點符號。這些字元與 0 到 127 之間的整數值對應,並由 ASCII 標準化。當然,在非英語語言中還有許多其他字元,包括帶有重音符號和其他修改的 ASCII 字元變體、西里爾文和希臘文等相關文字,以及與 ASCII 和英語完全無關的文字,包括阿拉伯文、中文、希伯來文、印地文、日文和韓文。 Unicode 標準解決了什麼是字元的複雜性,並且普遍被接受為解決此問題的明確標準。根據您的需求,您可以完全忽略這些複雜性,並假裝只有 ASCII 字元存在,或者您可以撰寫可以處理在處理非 ASCII 文字時可能會遇到的任何字元或編碼的程式碼。Julia 使得處理純 ASCII 文字變得簡單且有效率,而處理 Unicode 也盡可能地簡單且有效率。特別是,您可以撰寫 C 風格的字串程式碼來處理 ASCII 字串,它們將按照預期運作,無論是在效能或語意方面。如果此類程式碼遇到非 ASCII 文字,它將優雅地失敗並顯示明確的錯誤訊息,而不是靜默地引入錯誤的結果。發生這種情況時,修改程式碼以處理非 ASCII 資料非常簡單。

關於 Julia 的字串,有一些值得注意的高階功能

  • Julia 中用於字串(和字串文字)的內建具體類型是 String。這透過 UTF-8 編碼支援完整的 Unicode 字元範圍。(提供 transcode 函式來轉換為其他 Unicode 編碼或從其他 Unicode 編碼轉換。)
  • 所有字串類型都是抽象類型 AbstractString 的子類型,外部套件定義額外的 AbstractString 子類型(例如,用於其他編碼)。如果您定義一個預期字串參數的函數,您應該將類型宣告為 AbstractString,以接受任何字串類型。
  • 與 C 和 Java 類似,但與大多數動態語言不同,Julia 有個表示單一字元的首要類型,稱為 AbstractCharAbstractChar 的內建 Char 子類型是一個 32 位元元件類型,可以表示任何 Unicode 字元(且基於 UTF-8 編碼)。
  • 如同在 Java 中,字串是不變的:AbstractString 物件的值無法變更。若要建構不同的字串值,請從其他字串的部分建構新的字串。
  • 在概念上,字串是一個從索引到字元的部分函數:對於某些索引值,不會傳回字元值,而是會擲回例外。這允許透過編碼表示的位元組索引有效率地索引字串,而不是透過字元索引,後者無法針對 Unicode 字串的變寬度編碼以有效率且簡單的方式實作。

字元

Char 值表示單一字元:它只是一個 32 位元元件類型,具有特殊的文字表示和適當的算術行為,且可以轉換為表示 Unicode 碼位 的數字值。(Julia 套件可以定義 AbstractChar 的其他子類型,例如,最佳化其他 文字編碼 的運算。)以下是 Char 值的輸入和顯示方式(請注意,字元文字使用單引號分隔,而非雙引號)

julia> c = 'x'
'x': ASCII/Unicode U+0078 (category Ll: Letter, lowercase)

julia> typeof(c)
Char

您可以輕鬆地將 Char 轉換為其整數值,即碼位

julia> c = Int('x')
120

julia> typeof(c)
Int64

在 32 位元架構中,typeof(c) 會是 Int32。您可以輕鬆地將整數值轉換回 Char

julia> Char(120)
'x': ASCII/Unicode U+0078 (category Ll: Letter, lowercase)

並非所有整數值都是有效的 Unicode 碼點,但為了效能,Char 轉換不會檢查每個字元值是否有效。如果您想要檢查每個轉換值是否為有效的碼點,請使用 isvalid 函數

julia> Char(0x110000)
'\U110000': Unicode U+110000 (category In: Invalid, too high)

julia> isvalid(Char, 0x110000)
false

在撰寫本文時,有效的 Unicode 碼點為 U+0000U+D7FF 以及 U+E000U+10FFFF。這些碼點尚未全部指派有意義的含義,應用程式也不一定能詮釋它們,但所有這些值都被視為有效的 Unicode 字元。

您可以使用 \u 後接最多四個十六進位數字或 \U 後接最多八個十六進位數字(最長的有效值只需要六個)在單引號中輸入任何 Unicode 字元

julia> '\u0'
'\0': ASCII/Unicode U+0000 (category Cc: Other, control)

julia> '\u78'
'x': ASCII/Unicode U+0078 (category Ll: Letter, lowercase)

julia> '\u2200'
'∀': Unicode U+2200 (category Sm: Symbol, math)

julia> '\U10ffff'
'\U10ffff': Unicode U+10FFFF (category Cn: Other, not assigned)

Julia 使用您的系統區域設定和語言設定來決定哪些字元可以原樣列印,哪些必須使用通用轉譯 \u\U 輸入格式輸出。除了這些 Unicode 轉譯格式之外,所有 C 的傳統轉譯輸入格式 也都能使用

julia> Int('\0')
0

julia> Int('\t')
9

julia> Int('\n')
10

julia> Int('\e')
27

julia> Int('\x7f')
127

julia> Int('\177')
127

您可以對 Char 值進行比較和有限的算術運算

julia> 'A' < 'a'
true

julia> 'A' <= 'a' <= 'Z'
false

julia> 'A' <= 'X' <= 'Z'
true

julia> 'x' - 'a'
23

julia> 'A' + 1
'B': ASCII/Unicode U+0042 (category Lu: Letter, uppercase)

字串基礎

字串文字以雙引號或三雙引號為界(不是單引號)

julia> str = "Hello, world.\n"
"Hello, world.\n"

julia> """Contains "quote" characters"""
"Contains \"quote\" characters"

字串中的長行可以在換行符號前加上反斜線 (\) 來換行

julia> "This is a long \
       line"
"This is a long line"

如果您想要從字串中擷取一個字元,您可以在字串中對其編制索引

julia> str[begin]
'H': ASCII/Unicode U+0048 (category Lu: Letter, uppercase)

julia> str[1]
'H': ASCII/Unicode U+0048 (category Lu: Letter, uppercase)

julia> str[6]
',': ASCII/Unicode U+002C (category Po: Punctuation, other)

julia> str[end]
'\n': ASCII/Unicode U+000A (category Cc: Other, control)

許多 Julia 物件,包括字串,可以使用整數編製索引。第一個元素的索引(字串的第一個字元)由 firstindex(str) 傳回,而最後一個元素(字元)的索引由 lastindex(str) 傳回。關鍵字 beginend 可用於索引操作中,分別作為給定維度上第一個和最後一個索引的簡寫。字串索引,就像 Julia 中的大多數索引一樣,都是以 1 為基礎:firstindex 對於任何 AbstractString 始終傳回 1。但是,正如我們在下面將看到的,lastindex(str) 通常與字串的 length(str) 不同,因為有些 Unicode 字元可以佔用多個「碼元」。

您可以執行算術和其他操作,就像一般值一樣

julia> str[end-1]
'.': ASCII/Unicode U+002E (category Po: Punctuation, other)

julia> str[end÷2]
' ': ASCII/Unicode U+0020 (category Zs: Separator, space)

使用小於 begin1)或大於 end 的索引會引發錯誤

julia> str[begin-1]
ERROR: BoundsError: attempt to access 14-codeunit String at index [0]
[...]

julia> str[end+1]
ERROR: BoundsError: attempt to access 14-codeunit String at index [15]
[...]

您也可以使用範圍索引來擷取子字串

julia> str[4:9]
"lo, wo"

請注意,表達式 str[k]str[k:k] 沒有提供相同的結果

julia> str[6]
',': ASCII/Unicode U+002C (category Po: Punctuation, other)

julia> str[6:6]
","

前者是 Char 類型的單一字元值,而後者是剛好只包含一個字元的字串值。在 Julia 中,它們是非常不同的東西。

範圍索引會複製原始字串中所選部分的副本。或者,可以使用類型 SubString 來建立字串的檢視。更簡單地說,在程式碼區塊上使用 @views 巨集會將所有字串切片轉換為子字串。例如

julia> str = "long string"
"long string"

julia> substr = SubString(str, 1, 4)
"long"

julia> typeof(substr)
SubString{String}

julia> @views typeof(str[1:4]) # @views converts slices to SubStrings
SubString{String}

幾個標準函式,例如 chopchompstrip 會傳回 SubString

Unicode 和 UTF-8

Julia 完全支援 Unicode 字元和字串。如 上文所述,在字元字面值中,Unicode 碼點可以使用 Unicode \u\U 逸出序列,以及所有標準 C 逸出序列來表示。這些也可以用來撰寫字串字面值

julia> s = "\u2200 x \u2203 y"
"∀ x ∃ y"

這些 Unicode 字元是否顯示為逸出或顯示為特殊字元取決於終端的區域設定及其對 Unicode 的支援。字串字面值使用 UTF-8 編碼編碼。UTF-8 是一種可變寬度編碼,表示並非所有字元都編碼在相同數量的位元組(「碼單元」)中。在 UTF-8 中,ASCII 字元,即碼點小於 0x80 (128) 的字元,使用單一位元組編碼,就像在 ASCII 中一樣,而碼點 0x80 及以上的字元使用多個位元組編碼,每個字元最多四個位元組。

Julia 中的字串索引是指碼單元(= UTF-8 的位元組),這是用來編碼任意字元(碼點)的固定寬度建構區塊。這表示並非每個 String 中的索引都一定是字元的有效索引。如果你在字串中索引到這樣的無效位元組索引,就會擲回錯誤

julia> s[1]
'∀': Unicode U+2200 (category Sm: Symbol, math)

julia> s[2]
ERROR: StringIndexError: invalid index [2], valid nearby indices [1]=>'∀', [4]=>' '
Stacktrace:
[...]

julia> s[3]
ERROR: StringIndexError: invalid index [3], valid nearby indices [1]=>'∀', [4]=>' '
Stacktrace:
[...]

julia> s[4]
' ': ASCII/Unicode U+0020 (category Zs: Separator, space)

在這種情況下,字元 是三個位元組的字元,因此索引 2 和 3 無效,下一個字元的索引是 4;這個下一個有效索引可以使用 nextind(s,1) 計算,而下一個索引可以使用 nextind(s,4) 計算,以此類推。

由於 end 永遠是集合中最後一個有效索引,如果倒數第二個字元是多位元組,則 end-1 會參照無效的位元組索引。

julia> s[end-1]
' ': ASCII/Unicode U+0020 (category Zs: Separator, space)

julia> s[end-2]
ERROR: StringIndexError: invalid index [9], valid nearby indices [7]=>'∃', [10]=>' '
Stacktrace:
[...]

julia> s[prevind(s, end, 2)]
'∃': Unicode U+2203 (category Sm: Symbol, math)

第一個案例會運作,因為最後一個字元 y 和空白都是單位元組字元,而 end-2 則編入 多位元組表示法中間。此案例的正確方式是使用 prevind(s, lastindex(s), 2),或者,如果你使用該值編入 s,你可以寫 s[prevind(s, end, 2)],而 end 會擴充為 lastindex(s)

使用範圍索引萃取子字串也預期有效位元組索引,否則會擲回錯誤

julia> s[1:1]
"∀"

julia> s[1:2]
ERROR: StringIndexError: invalid index [2], valid nearby indices [1]=>'∀', [4]=>' '
Stacktrace:
[...]

julia> s[1:4]
"∀ "

由於長度可變編碼,字串中的字元數目(由 length(s) 提供)不總是與最後一個索引相同。如果你從 1 到 lastindex(s) 遍歷索引,並編入 s,則在未擲回錯誤時傳回的字元順序就是組成字串 s 的字元順序。因此 length(s) <= lastindex(s),因為字串中的每個字元都必須有自己的索引。以下是不有效率且冗長的遍歷 s 字元的程式碼

julia> for i = firstindex(s):lastindex(s)
           try
               println(s[i])
           catch
               # ignore the index error
           end
       end
∀

x

∃

y

空白行實際上包含空白。很幸運地,由於你可以直接將字串用作可遍歷物件,因此不需要例外處理,上述笨拙的慣用語對於遍歷字串中的字元並非必要

julia> for c in s
           println(c)
       end
∀

x

∃

y

如果您需要取得字串的有效索引,您可以使用 nextindprevind 函數來遞增/遞減到下一個/上一個有效索引,如上所述。您也可以使用 eachindex 函數來迭代有效字元索引

julia> collect(eachindex(s))
7-element Vector{Int64}:
  1
  4
  5
  6
  7
 10
 11

若要存取編碼的原始碼元(UTF-8 的位元組),您可以使用 codeunit(s,i) 函數,其中索引 i1 連續執行到 ncodeunits(s)codeunits(s) 函數會傳回一個 AbstractVector{UInt8} 包裝器,讓您可以將這些原始碼元(位元組)當成陣列存取。

Julia 中的字串可以包含無效的 UTF-8 碼元序列。此慣例允許將任何位元組序列視為 String。在這種情況下,規則是從左到右解析碼元序列時,字元是由最長的 8 位元碼元序列形成,該序列與下列其中一個位元模式的開頭相符(每個 x 可以是 01

  • 0xxxxxxx;
  • 110xxxxx 10xxxxxx;
  • 1110xxxx 10xxxxxx 10xxxxxx;
  • 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx;
  • 10xxxxxx;
  • 11111xxx.

特別是,這表示過長和過高的碼元序列及其前綴會視為單一無效字元,而不是多個無效字元。這個規則可以用一個範例來解釋

julia> s = "\xc0\xa0\xe2\x88\xe2|"
"\xc0\xa0\xe2\x88\xe2|"

julia> foreach(display, s)
'\xc0\xa0': [overlong] ASCII/Unicode U+0020 (category Zs: Separator, space)
'\xe2\x88': Malformed UTF-8 (category Ma: Malformed, bad data)
'\xe2': Malformed UTF-8 (category Ma: Malformed, bad data)
'|': ASCII/Unicode U+007C (category Sm: Symbol, math)

julia> isvalid.(collect(s))
4-element BitArray{1}:
 0
 0
 0
 1

julia> s2 = "\xf7\xbf\xbf\xbf"
"\U1fffff"

julia> foreach(display, s2)
'\U1fffff': Unicode U+1FFFFF (category In: Invalid, too high)

我們可以看到字串 s 中的前兩個碼元形成一個過長的空白字元編碼。它是無效的,但會在字串中被接受為單一字元。下一個兩個碼元形成一個有效的 3 位元組 UTF-8 序列開頭。然而,第五個碼元 \xe2 不是它的有效延續。因此,碼元 3 和 4 也會被解釋為此字串中的錯誤字元。類似地,碼元 5 形成一個錯誤字元,因為 | 不是它的有效延續。最後,字串 s2 包含一個過高的碼點。

Julia 預設使用 UTF-8 編碼,而對於新編碼的支持則可以透過套件來新增。例如,LegacyStrings.jl 套件實作了 UTF16StringUTF32String 類型。對於其他編碼的進一步討論以及如何實作對它們的支持,目前已超出本文檔的範圍。有關 UTF-8 編碼問題的進一步討論,請參閱下方有關位元組陣列文字的部分。transcode 函數用於在各種 UTF-xx 編碼之間轉換資料,主要是用於處理外部資料和函式庫。

串接

最常見且有用的字串操作之一是串接

julia> greet = "Hello"
"Hello"

julia> whom = "world"
"world"

julia> string(greet, ", ", whom, ".\n")
"Hello, world.\n"

重要的是要注意潛在的危險情況,例如串接無效的 UTF-8 字串。產生的字串可能包含與輸入字串不同的字元,而且其字元數目可能低於串接字串的字元數目總和,例如

julia> a, b = "\xe2\x88", "\x80"
("\xe2\x88", "\x80")

julia> c = string(a, b)
"∀"

julia> collect.([a, b, c])
3-element Vector{Vector{Char}}:
 ['\xe2\x88']
 ['\x80']
 ['∀']

julia> length.([a, b, c])
3-element Vector{Int64}:
 1
 1
 1

這種情況只會發生在無效的 UTF-8 字串中。對於有效的 UTF-8 字串,串接會保留字串中的所有字元,並且字串長度具有加法性。

Julia 也提供 * 來進行字串串接

julia> greet * ", " * whom * ".\n"
"Hello, world.\n"

雖然對於使用提供 + 來進行字串串接的語言的使用者來說,* 可能看起來是一個令人驚訝的選擇,但這種使用 * 的方式在數學中是有先例的,特別是在抽象代數中。

在數學中,+ 通常表示一個可交換運算,其中運算元的順序不重要。一個例子是矩陣加法,其中 A + B == B + A,對於任何形狀相同的矩陣 AB。相反,* 通常表示一個不可交換運算,其中運算元的順序很重要。一個例子是矩陣乘法,其中通常 A * B != B * A。與矩陣乘法一樣,字串串接是不可交換的:greet * whom != whom * greet。因此,* 是中綴字串串接運算子的更自然選擇,與常見的數學用法一致。

更精確地說,所有有限長度字串的集合 S 連同字串串接運算子 * 形成一個自由單群 (S, *)。此集合的單位元素是空字串,""。每當一個自由單群不可交換時,該運算通常表示為 \cdot* 或類似的符號,而不是 +,正如所述,通常表示可交換性。

插值

然而,使用串接來構造字串可能會變得有點麻煩。為了減少對這些冗長的呼叫 string 或重複乘法的需要,Julia 允許使用 $ 將插值插入字串文字中,就像在 Perl 中一樣

julia> greet = "Hello"; whom = "world";

julia> "$greet, $whom.\n"
"Hello, world.\n"

這更具可讀性和便利性,並且等同於上述字串串接——系統將這個明顯的單一字串文字重寫為呼叫 string(greet, ", ", whom, ".\n")

$ 之後最短的完整表達式被視為要將其值插值到字串中的表達式。因此,您可以使用括號將任何表達式插值到字串中

julia> "1 + 2 = $(1 + 2)"
"1 + 2 = 3"

串接和字串內插都呼叫 string 將物件轉換為字串形式。然而,string 實際上只回傳 print 的輸出,因此新的類型應該將方法新增到 printshow,而不是 string

大多數非 AbstractString 物件轉換為字串的方式與其作為字面值表達式輸入的方式非常接近

julia> v = [1,2,3]
3-element Vector{Int64}:
 1
 2
 3

julia> "v: $v"
"v: [1, 2, 3]"

stringAbstractStringAbstractChar 值的識別,因此這些值會以其本身的形式內插到字串中,不加引號和跳脫字元

julia> c = 'x'
'x': ASCII/Unicode U+0078 (category Ll: Letter, lowercase)

julia> "hi, $c"
"hi, x"

若要在字串字面值中包含字面值 $,請使用反斜線跳脫

julia> print("I have \$100 in my account.\n")
I have $100 in my account.

三引號字串字面值

當使用三引號 ("""...""") 建立字串時,它們會有一些特殊行為,對於建立較長的文字區塊很有用。

首先,三引號字串也會縮排到縮排最少的行的層級。這對於在縮排的程式碼中定義字串很有用。例如

julia> str = """
           Hello,
           world.
         """
"  Hello,\n  world.\n"

在這種情況下,關閉 """ 之前的最後一行 (空行) 會設定縮排層級。

縮排層級會判定為所有行中空格或標籤最長的公用起始順序,不包括開啟 """ 之後的行和僅包含空格或標籤的行 (包含關閉 """ 的行總是會包含在內)。然後,對於所有行,不包括開啟 """ 之後的文字,會移除公用起始順序 (包括僅包含空格和標籤的行,如果它們以這個順序開始),例如

julia> """    This
         is
           a test"""
"    This\nis\n  a test"

接下來,如果開頭的 """ 後面接著換行,則會從結果字串中移除換行。

"""hello"""

等於

"""
hello"""

但是

"""

hello"""

會在開頭包含一個字面換行。

移除換行會在縮排後執行。例如

julia> """
         Hello,
         world."""
"Hello,\nworld."

如果使用反斜線移除換行,也會尊重縮排

julia> """
         Averylong\
         word"""
"Averylongword"

尾隨空白不變更。

三引號字串字面值可以包含 " 字元,而不需要跳脫。

請注意,字面字串中的換行,無論是單引號或三引號,都會在字串中產生換行 (LF) 字元 \n,即使您的編輯器使用回車 \r (CR) 或 CRLF 組合來結束行。若要將 CR 包含在字串中,請使用明確的跳脫 \r;例如,您可以輸入字面字串 "a CRLF line ending\r\n"

常見操作

您可以使用標準比較運算子對字串進行字典順序比較

julia> "abracadabra" < "xylophone"
true

julia> "abracadabra" == "xylophone"
false

julia> "Hello, world." != "Goodbye, world."
true

julia> "1 + 2 = 3" == "1 + 2 = $(1 + 2)"
true

您可以使用 findfirstfindlast 函式搜尋特定字元的索引

julia> findfirst('o', "xylophone")
4

julia> findlast('o', "xylophone")
7

julia> findfirst('z', "xylophone")

您可以使用函式 findnextfindprev 從給定的偏移量開始搜尋字元

julia> findnext('o', "xylophone", 1)
4

julia> findnext('o', "xylophone", 5)
7

julia> findprev('o', "xylophone", 5)
4

julia> findnext('o', "xylophone", 8)

您可以使用 occursin 函式檢查字串中是否找到子字串

julia> occursin("world", "Hello, world.")
true

julia> occursin("o", "Xylophon")
true

julia> occursin("a", "Xylophon")
false

julia> occursin('o', "Xylophon")
true

最後一個範例顯示 occursin 也可以尋找字元字面值。

另外兩個方便的字串函數是 repeatjoin

julia> repeat(".:Z:.", 10)
".:Z:..:Z:..:Z:..:Z:..:Z:..:Z:..:Z:..:Z:..:Z:..:Z:."

julia> join(["apples", "bananas", "pineapples"], ", ", " and ")
"apples, bananas and pineapples"

其他一些有用的函數包括

非標準字串字面值

在某些情況下,您想要建構字串或使用字串語意,但標準字串建構的行為並非完全符合需求。對於此類情況,Julia 提供了非標準字串字面值。非標準字串字面值看起來像常規的雙引號字串字面值,但前面立即加上一個識別碼,並且可能與常規字串字面值有不同的行為。

正則表示式位元組陣列字面值版本號碼字面值,如下所述,是非標準字串字面值的範例。使用者和套件也可以定義新的非標準字串字面值。進一步的說明在 元程式設計 章節中提供。

正則表示式

有時您並非在尋找確切的字串,而是一個特定的模式。例如,假設您嘗試從一個大型文字檔案中萃取一個單一的日期。您不知道那個日期是什麼(這就是您在搜尋它的原因),但您知道它看起來會像 YYYY-MM-DD。正則表示式讓您可以指定這些模式並搜尋它們。

Julia 使用由 PCRE 函式庫提供的 Perl 相容正則表示式(regexes)的版本 2(有關更多詳細資料,請參閱 PCRE2 語法說明)。正則表示式與字串有兩種關聯:顯而易見的關聯是正則表示式用於在字串中尋找規則模式;另一個關聯是正則表示式本身作為字串輸入,解析成一個狀態機,可以用於有效率地搜尋字串中的模式。在 Julia 中,正則表示式使用非標準字串字面值輸入,字首為各種識別碼,以 r 開頭。最基本的正則表示式字面值,沒有開啟任何選項,只需使用 r"..."

julia> re = r"^\s*(?:#|$)"
r"^\s*(?:#|$)"

julia> typeof(re)
Regex

若要檢查正則表示式是否符合字串,請使用 occursin

julia> occursin(r"^\s*(?:#|$)", "not a comment")
false

julia> occursin(r"^\s*(?:#|$)", "# a comment")
true

正如您在此處所見,occursin 僅傳回 true 或 false,表示給定正規表示式是否在字串中發生比對。然而,一般來說,我們不只想知道字串是否比對,還想知道它如何比對。若要擷取有關比對的資訊,請改用 match 函數

julia> match(r"^\s*(?:#|$)", "not a comment")

julia> match(r"^\s*(?:#|$)", "# a comment")
RegexMatch("#")

如果正規表示式與給定字串不符,match 會傳回 nothing,這是一個特殊值,在互動式提示中不會列印任何內容。除了不列印之外,它是一個完全正常的數值,您可以透過程式設計方式測試它

m = match(r"^\s*(?:#|$)", line)
if m === nothing
    println("not a comment")
else
    println("blank or comment")
end

如果正規表示式確實比對,match 傳回的值會是 RegexMatch 物件。這些物件會記錄表示式如何比對,包括樣式比對的子字串,以及任何擷取的子字串(如果有)。此範例僅擷取與子字串比對的部分,但我們可能想要擷取註解字元之後的任何非空白文字。我們可以執行下列動作

julia> m = match(r"^\s*(?:#\s*(.*?)\s*$|$)", "# a comment ")
RegexMatch("# a comment ", 1="a comment")

在呼叫 match 時,您可以指定要從中開始搜尋的索引。例如

julia> m = match(r"[0-9]","aaaa1aaaa2aaaa3",1)
RegexMatch("1")

julia> m = match(r"[0-9]","aaaa1aaaa2aaaa3",6)
RegexMatch("2")

julia> m = match(r"[0-9]","aaaa1aaaa2aaaa3",11)
RegexMatch("3")

您可以從 RegexMatch 物件中擷取下列資訊

  • 比對的整個子字串:m.match
  • 擷取的子字串(以字串陣列表示):m.captures
  • 整個比對開始的偏移量:m.offset
  • 擷取的子字串的偏移量(以向量表示):m.offsets

當擷取不比對時,m.captures 會在該位置包含 nothing,而不是子字串,而 m.offsets 會有零偏移量(請回想 Julia 中的索引是從 1 開始,因此字串中的零偏移量無效)。以下是兩個有點牽強的範例

julia> m = match(r"(a|b)(c)?(d)", "acd")
RegexMatch("acd", 1="a", 2="c", 3="d")

julia> m.match
"acd"

julia> m.captures
3-element Vector{Union{Nothing, SubString{String}}}:
 "a"
 "c"
 "d"

julia> m.offset
1

julia> m.offsets
3-element Vector{Int64}:
 1
 2
 3

julia> m = match(r"(a|b)(c)?(d)", "ad")
RegexMatch("ad", 1="a", 2=nothing, 3="d")

julia> m.match
"ad"

julia> m.captures
3-element Vector{Union{Nothing, SubString{String}}}:
 "a"
 nothing
 "d"

julia> m.offset
1

julia> m.offsets
3-element Vector{Int64}:
 1
 0
 2

將擷取結果傳回陣列很方便,這樣就可以使用解構語法將它們繫結到局部變數。為了方便起見,RegexMatch 物件實作了迭代器方法,這些方法會傳遞到 captures 欄位,因此你可以直接解構比對物件

julia> first, second, third = m; first
"a"

也可以使用擷取群組的數字或名稱來索引 RegexMatch 物件,以存取擷取結果

julia> m=match(r"(?<hour>\d+):(?<minute>\d+)","12:45")
RegexMatch("12:45", hour="12", minute="45")

julia> m[:minute]
"45"

julia> m[2]
"45"

使用 replace 時,可以在取代字串中參照擷取結果,方法是使用 \n 參照第 n 個擷取群組,並在取代字串前面加上 s。擷取群組 0 參照整個比對物件。可以在取代中使用 \g<groupname> 參照命名擷取群組。例如

julia> replace("first second", r"(\w+) (?<agroup>\w+)" => s"\g<agroup> \1")
"second first"

為了消除歧義,也可以將編號擷取群組參照為 \g<n>,如下所示

julia> replace("a", r"." => s"\g<0>1")
"a1"

可以在閉合雙引號之後,使用 imsx 旗標的某種組合,來修改正規表達式的行為。這些旗標的意義與 Perl 中的相同,如下摘錄自 perlre 手冊頁 所述

i   Do case-insensitive pattern matching.

    If locale matching rules are in effect, the case map is taken
    from the current locale for code points less than 255, and
    from Unicode rules for larger code points. However, matches
    that would cross the Unicode rules/non-Unicode rules boundary
    (ords 255/256) will not succeed.

m   Treat string as multiple lines.  That is, change "^" and "$"
    from matching the start or end of the string to matching the
    start or end of any line anywhere within the string.

s   Treat string as single line.  That is, change "." to match any
    character whatsoever, even a newline, which normally it would
    not match.

    Used together, as r""ms, they let the "." match any character
    whatsoever, while still allowing "^" and "$" to match,
    respectively, just after and just before newlines within the
    string.

x   Tells the regular expression parser to ignore most whitespace
    that is neither backslashed nor within a character class. You
    can use this to break up your regular expression into
    (slightly) more readable parts. The '#' character is also
    treated as a metacharacter introducing a comment, just as in
    ordinary code.

例如,下列正規表達式啟用了所有三個旗標

julia> r"a+.*b+.*?d$"ism
r"a+.*b+.*?d$"ims

julia> match(r"a+.*b+.*?d$"ism, "Goodbye,\nOh, angry,\nBad world\n")
RegexMatch("angry,\nBad world")

r"..." 文字是在沒有內插和取消跳脫的情況下建構的(除了引號 " 之外,它仍然必須跳脫)。以下是顯示與標準字串文字之間差異的範例

julia> x = 10
10

julia> r"$x"
r"$x"

julia> "$x"
"10"

julia> r"\x"
r"\x"

julia> "\x"
ERROR: syntax: invalid escape sequence

也支援 r"""...""" 形式的三引號正規表達式字串(對於包含引號或換行符號的正規表達式來說,這可能會很方便)。

Regex() 建構函式可用於以程式設計方式建立有效的正規表示式字串。這允許在建立正規表示式字串時使用字串變數的內容和其他字串運算。Regex() 中的單一字串引數可以使用上述任何正規表示式代碼。以下是幾個範例

julia> using Dates

julia> d = Date(1962,7,10)
1962-07-10

julia> regex_d = Regex("Day " * string(day(d)))
r"Day 10"

julia> match(regex_d, "It happened on Day 10")
RegexMatch("Day 10")

julia> name = "Jon"
"Jon"

julia> regex_name = Regex("[\"( ]\\Q$name\\E[\") ]")  # interpolate value of name
r"[\"( ]\QJon\E[\") ]"

julia> match(regex_name, " Jon ")
RegexMatch(" Jon ")

julia> match(regex_name, "[Jon]") === nothing
true

請注意 \Q...\E 逸出順序的使用。\Q\E 之間的所有字元都會被解釋為字面字元。這對於比對在其他情況下會是正規表示式元字元的字元很方便。不過,在將此功能與字串內插一起使用時需要小心,因為內插的字串本身可能會包含 \E 順序,意外終止字面比對。使用者輸入在包含在正規表示式中之前需要先清除。

位元組陣列字面值

另一個有用的非標準字串字面值是位元組陣列字串字面值:b"..."。此表單讓您可以使用字串表示法來表示唯讀字面值位元組陣列,也就是 UInt8 值的陣列。這些物件的類型是 CodeUnits{UInt8, String}。位元組陣列字面值的規則如下

  • ASCII 字元和 ASCII 逸出會產生單一位元組。
  • \x 和八進位逸出順序會產生對應於逸出值的位元組
  • Unicode 逸出順序會產生以 UTF-8 編碼該碼點的位元組順序。

這些規則之間有些重疊,因為 \x 和小於 0x80 (128) 的八進位轉義的行為由前兩個規則涵蓋,但這些規則在此一致。這些規則加在一起,讓您可以輕鬆使用 ASCII 字元、任意位元組值和 UTF-8 序列來產生位元組陣列。以下是使用這三者的範例

julia> b"DATA\xff\u2200"
8-element Base.CodeUnits{UInt8, String}:
 0x44
 0x41
 0x54
 0x41
 0xff
 0xe2
 0x88
 0x80

ASCII 字串「DATA」對應到位元組 68、65、84、65。\xff 產生單一位元組 255。Unicode 轉義 \u2200 以三個位元組 226、136、128 編碼為 UTF-8。請注意,產生的位元組陣列不對應到有效的 UTF-8 字串

julia> isvalid("DATA\xff\u2200")
false

如前所述,CodeUnits{UInt8, String} 類型就像 UInt8 的唯讀陣列,如果您需要標準向量,可以使用 Vector{UInt8} 進行轉換

julia> x = b"123"
3-element Base.CodeUnits{UInt8, String}:
 0x31
 0x32
 0x33

julia> x[1]
0x31

julia> x[1] = 0x32
ERROR: CanonicalIndexError: setindex! not defined for Base.CodeUnits{UInt8, String}
[...]

julia> Vector{UInt8}(x)
3-element Vector{UInt8}:
 0x31
 0x32
 0x33

另外,請注意 \xff\uff 之間的顯著區別:前者轉義序列編碼位元組 255,而後者轉義序列表示碼點 255,它在 UTF-8 中編碼為兩個位元組

julia> b"\xff"
1-element Base.CodeUnits{UInt8, String}:
 0xff

julia> b"\uff"
2-element Base.CodeUnits{UInt8, String}:
 0xc3
 0xbf

字元字面值使用相同的行為。

對於小於 \u80 的碼點,每個碼點的 UTF-8 編碼剛好是對應 \x 轉義產生的單一位元組,因此可以安全地忽略區別。但是,對於 \x80\xff 的轉義與 \u80\uff 相比,有一個重大的差異:前者轉義全部編碼單一位元組,除非後接非常特定的延續位元組,否則不會形成有效的 UTF-8 資料,而後者轉義全部表示具有兩個位元組編碼的 Unicode 碼點。

如果這一切讓您感到非常困惑,請嘗試閱讀 "每個軟體開發人員絕對、肯定必須了解 Unicode 和字元集的絕對最低限度"。這是 Unicode 和 UTF-8 的絕佳入門,並且可能有助於減輕對此事的困惑。

版本號碼字面值

版本號碼可以用非標準字串文字的形式輕鬆表達,格式為 v"..."。版本號碼文字會建立 VersionNumber 物件,遵循 語意化版本控制 規範,因此由主版本、次要版本和修補程式數字值組成,後面接著預發行版和組建的字母數字註解。例如,v"0.2.1-rc1+win64" 會分解成主版本 0、次要版本 2、修補程式版本 1、預發行版 rc1 和組建 win64。輸入版本文字時,除了主版本號碼之外,其他部分都是選填的,因此例如 v"0.2" 等同於 v"0.2.0"(預發行版/組建註解為空),v"2" 等同於 v"2.0.0",以此類推。

VersionNumber 物件最常使用於輕鬆正確地比較兩個(或更多)版本。例如,常數 VERSION 將 Julia 版本號碼儲存在 VersionNumber 物件中,因此可以使用簡單的陳述式定義一些特定版本的行為,例如

if v"0.2" <= VERSION < v"0.3-"
    # do something specific to 0.2 release series
end

請注意,在上面的範例中,使用了非標準版本號碼 v"0.3-",後面加上 -:這個符號是 Julia 對標準的延伸,用來表示低於任何 0.3 發行版的版本,包含所有預發行版。因此,在上面的範例中,程式碼只會在穩定的 0.2 版本中執行,並排除 v"0.3.0-rc1" 等版本。為了同時允許不穩定的(即預發行版)0.2 版本,下限檢查應該修改為:v"0.2-" <= VERSION

另一個非標準版本規範延伸允許使用後面的 + 來表達組建版本的上限,例如 VERSION > v"0.2-rc1+" 可以用來表示任何高於 0.2-rc1 及其任何組建的版本:對於版本 v"0.2-rc1+win64",它會傳回 false,對於 v"0.2-rc2" 則會傳回 true

在比較中使用此類特殊版本是一種良好的做法(特別是,除非有充分的理由不使用,否則應始終對上限使用尾隨的 -),但它們不能用作任何內容的實際版本號,因為它們在語義版本控制方案中無效。

除了用於 VERSION 常量之外,VersionNumber 物件廣泛用於 Pkg 模組,以指定套件版本及其依賴關係。

原始字串文字

不含內插或取消轉義的原始字串可以用 raw"..." 形式的非標準字串文字表示。原始字串文字會建立一般的 String 物件,其中包含輸入的內容,完全照輸入的內容,不含任何內插或取消轉義。這對於包含使用 $\ 作為特殊字元的其他語言的程式碼或標記的字串很有用。

例外情況是引號仍必須取消轉義,例如 raw"\"" 等同於 "\""。為了表達所有字串,反斜線也必須取消轉義,但僅當出現在引號字元之前時。

julia> println(raw"\\ \\\"")
\\ \"

請注意,前兩個反斜線會逐字顯示在輸出中,因為它們不位於引號字元之前。但是,下一個反斜線字元會取消轉義其後面的反斜線,最後一個反斜線會取消轉義引號,因為這些反斜線出現在引號之前。