字串¶
在先前的課程中,我們已經熟悉了大多數程式的主要組成部分:字串。讓我們回顧一下基本屬性
字串是編碼為 UTF-8 的 Unicode 字元的序列。字串是不可變的:如果您對字串應用修改,您實際上會得到一個具有修改內容的新字串。原始字串保持不變。
字串通常以雙引號字元 ("
) 括起來的字面值形式寫入。
插值¶
字串插值是組合字串的便捷方法:字串字面值內的 #{...}
會在字串的這個位置插入大括號之間的表達式的值。
name = "Crystal"
puts "Hello #{name}"
插值內的表達式應保持簡短,僅限於變數或簡單的方法呼叫。較複雜的表達式會降低程式碼的可讀性。
表達式的值不一定要是字串。任何型別都可以,並且會透過呼叫 #to_s
方法轉換為字串表示。此方法為任何物件定義。讓我們嘗試使用數字
name = 6
puts "Hello #{name}!"
注意
插值的替代方法是串連。您可以撰寫 "Hello " + name + "!"
,而不是 "Hello #{name}!"
。但是這樣會比較笨重,而且對於非字串型別會有一些陷阱。一般來說,插值比串連更受歡迎。
跳脫¶
有些字元無法直接在字串字面值中寫入。例如,雙引號:如果在字串內使用,編譯器會將其解譯為結束分隔符號。
這個問題的解決方案是跳脫:如果雙引號前面有一個反斜線 (\
),則會將其解譯為跳脫序列,並且兩個字元一起編碼為雙引號字元。
puts "I say: \"Hello World!\""
還有其他的跳脫序列:例如,換行符號 (\n
) 或製表符號 (\t
) 等不可列印的字元。如果您想要寫入字面反斜線,則跳脫序列為雙反斜線 (\\
)。空字元 (碼位 0
) 是 Crystal 字串中的一般字元。在某些程式語言中,此字元表示字串的結尾。但是在 Crystal 中,它僅由其 #size
屬性決定。
puts "I say: \"Hello \\\n\tWorld!\""
提示
您可以在字串字面值參考中找到關於可用跳脫序列的更多資訊。
替代分隔符號¶
有些字串字面值可能包含許多雙引號 – 例如,想想具有引號引數值的 HTML 標籤。必須使用反斜線跳脫每一個引號會很麻煩。替代字面值分隔符號是一個方便的替代方法。%(...)
等效於 "..."
,只是分隔符號以括號 ((
和 )
) 而不是雙引號表示。
puts %(I say: "Hello World!")
跳脫序列和插值仍然以相同的方式運作。
提示
您可以在字串字面值參考中找到關於替代分隔符號的更多資訊。
Unicode¶
Unicode 是一種國際標準,用於表示許多不同書寫系統中的文字。除了英文和許多其他語言使用的拉丁字母之外,它還包含許多其他字元集。Unicode 標準不僅適用於純文字,還包含表情符號和圖示。
以下範例使用 Unicode 字元 U+1F310
(地球與經緯線) 來向世界發送訊息
puts "Hello 🌐"
處理 Unicode 符號有時可能會有點棘手。某些字元可能不受您的編輯器字體支援,某些字元甚至無法列印。作為替代方法,Unicode 字元可以用跳脫序列表示。反斜線後跟字母 u
表示 Unicode 碼位。碼位值以十六進位數字寫入,並以大括號括住。如果碼位正好有四個數字,則可以省略大括號。
puts "Hello \u{1F310}"
轉換¶
假設您想要變更字串的某些內容。也許要大聲說出訊息並將其全部設為大寫?String#upcase
方法會將所有小寫字元轉換為它們的大寫等效字元。相反的方法是 String#downcase
。還有一些類似的方法,可讓我們以不同的樣式表達我們的訊息
message = "Hello World! Greetings from Crystal."
puts "normal: #{message}"
puts "upcased: #{message.upcase}"
puts "downcased: #{message.downcase}"
puts "camelcased: #{message.camelcase}"
puts "capitalized: #{message.capitalize}"
puts "reversed: #{message.reverse}"
puts "titleized: #{message.titleize}"
puts "underscored: #{message.underscore}"
#camelcase
和 #underscore
方法不會變更這個特定的字串,但是您可以嘗試使用 "snake_cased"
或 "CamelCased"
輸入。
資訊¶
讓我們更詳細地了解字串以及我們可以知道的內容。首先,字串有一個長度,即它包含的字元數。此值以 String#size
提供。
message = "Hello World! Greetings from Crystal."
p! message.size
若要判斷字串是否為空,您可以檢查大小是否為零,或者只使用簡寫 String#empty?
empty_string = ""
p! empty_string.size == 0,
empty_string.empty?
如果字串為空,或者僅包含空白字元,則 String#blank?
方法會傳回 true
。相關的方法是 String#presence
,如果字串為空白,則會傳回 nil
,否則會傳回字串本身。
blank_string = ""
p! blank_string.blank?,
blank_string.presence
相等與比較¶
您可以使用相等運算子 (==
) 測試兩個字串是否相等,並使用比較運算子 (<=>
) 比較它們。兩者都會嚴格地逐字元比較字串。請記住,<=>
會傳回一個整數,表示兩個運算元之間的關係,如果比較結果為 0
,則 ==
會傳回 true
,也就是說,兩個值比較結果相等。
但是,也有一個 #compare
方法,提供不區分大小寫的比較。
message = "Hello World!"
p! message == "Hello World",
message == "Hello Crystal",
message == "hello world",
message.compare("hello world", case_insensitive: false),
message.compare("hello world", case_insensitive: true)
部分組成¶
有時候,我們並不需要知道字串是否完全匹配,而只是想知道一個字串是否包含另一個字串。例如,讓我們使用 #includes?
方法來檢查訊息是否與 Crystal 有關。
message = "Hello World!"
p! message.includes?("Crystal"),
message.includes?("World")
有時,字串的開頭或結尾會特別重要。這時,#starts_with?
和 #ends_with?
方法就派上用場了。
message = "Hello World!"
p! message.starts_with?("Hello"),
message.starts_with?("Bye"),
message.ends_with?("!"),
message.ends_with?("?")
子字串索引¶
我們可以透過 #index
方法取得關於子字串位置更詳細的資訊。它會回傳子字串首次出現時,第一個字元的索引值。結果 0
的意義與 starts_with?
相同。
p! "Crystal is awesome".index("Crystal"),
"Crystal is awesome".index("s"),
"Crystal is awesome".index("aw")
這個方法有一個可選的 offset
引數,可用於從字串開頭以外的不同位置開始搜尋。當子字串可能多次出現時,這會很有用。
message = "Crystal is awesome"
p! message.index("s"),
message.index("s", offset: 4),
message.index("s", offset: 10)
#rindex
方法的作用相同,但它是從字串的結尾開始搜尋。
message = "Crystal is awesome"
p! message.rindex("s"),
message.rindex("s", 13),
message.rindex("s", 8)
如果沒有找到子字串,結果會是一個稱為 nil
的特殊值。它的意思是「沒有值」。當子字串沒有索引時,這是合理的。
查看 #index
的回傳型別,我們可以看到它會回傳 Int32
或 Nil
。
a = "Crystal is awesome".index("aw")
p! a, typeof(a)
b = "Crystal is awesome".index("meh")
p! b, typeof(b)
提示
我們將在下一課更深入地探討 nil
。
提取子字串¶
子字串是字串的一部分。如果您想提取字串的部分內容,可以使用幾種方法。
索引存取器 #[]
允許透過字元索引和大小來參照子字串。字元索引從 0
開始,到長度(即 #size
的值)減一結束。第一個引數指定子字串中第一個字元的索引,第二個引數指定子字串的長度。 message[6, 5]
會提取一個從索引六開始,長度為五個字元的子字串。
message = "Hello World!"
p! message[6, 5]
假設我們已經確定字串以 Hello
開頭並以 !
結尾,並且想要提取中間的內容。如果訊息是 Hello Crystal
,我們不會得到完整的單字 Crystal
,因為它長度超過五個字元。
一個解決方案是從整個字串的長度減去開頭和結尾的長度,來計算子字串的長度。
message = "Hello World!"
p! message[6, message.size - 6 - 1]
有一個更簡單的方法可以做到這一點:索引存取器可以與字元索引的 Range
一起使用。範圍字面值由一個起始值和一個結束值組成,以兩個點(..
)連接。第一個值表示子字串的起始索引,就像之前一樣,但第二個值是結束索引(與長度相反)。現在我們不需要在計算中重複起始索引,因為結束索引只是大小減二(一個用於結束索引,另一個用於排除最後一個字元)。
它可以更簡單:負索引值會自動關聯到字串的結尾,因此我們不需要明確地從字串大小計算結束索引。
message = "Hello World!"
p! message[6..(message.size - 2)],
message[6..-2]
替換¶
以非常相似的方式,我們可以修改字串。讓我們確保我們正確地問候 Crystal,而沒有其他。我們呼叫 #sub
而不是存取子字串。第一個引數同樣是一個範圍,用於指示要被第二個引數的值替換的位置。
message = "Hello World!"
p! message.sub(6..-2, "Crystal")
#sub
方法非常通用,可以用不同的方式使用。我們也可以將搜尋字串作為第一個引數傳遞,它會將該子字串替換為第二個引數的值。
message = "Hello World!"
p! message.sub("World", "Crystal")
#sub
只會替換搜尋字串的第一個實例。它的老大哥 #gsub
則會應用於所有實例。
message = "Hello World! How are you, World?"
p! message.sub("World", "Crystal"),
message.gsub("World", "Crystal")
提示
您可以在字串字面值參考和String API 文件中找到更詳細的資訊。