Swift 語言的字串與字元

xslidian發表於2014-06-08

字串string)是字元的有序集合,如 "hello, world""albatross"。Swift 語言中的字串由 String 型別表示,對應著 Character 型別值的集合。

Swift 的 StringCharacter 型別可高速處理文字,且相容 Unicode 規範。建立與處理字串的語法輕量且可讀性強,與 C 語言的字串語法相近。字串的連線操作非常簡單,只需將兩個字串用 + 運算子相加。字串的值是否可變取決於它是常量還是變數,這點與 Swift 其他型別完全一樣。

Swift 的 String 型別不僅語法簡潔,還採用了高速的現代化字串實現方案。 每個字串由編碼獨立的 Unicode 字元組成,每個字元均支援以不同的 Unicode 表達形式訪問。

Swift 的字串還支援在較長的字串中插入常量、變數、字面量以及表示式的值,該過程稱為字串內插。這使得顯示、儲存以及輸出自定義的字串值更為簡便。

注:Swift 的 String 型別與底層 Foundation 框架的 NSString 類無縫銜接。如果你在用 Cocoa / Cocoa Touch 的 Foundation 框架,則除本章講解的 String 功能以外,對建立的任何 String 值,均可呼叫到 NSString 類的全部 API。還可以將 String 值傳遞給任何要求輸入 NSString 例項的 API 方法。

關於 String 與 Foundation / Cocoa 框架結合使用的更多資訊,請見 Swift 與 Cocoa 及 Objective-C 的結合

字串字面量

程式碼中可以以字串字面量string literal)的形式嵌入預先定義的 String 值。字串字面量是由一對雙引號("")包圍的文字字元的固定序列。

字串字面量可用來為常量或變數提供初始值:

  1. let 某字串 = "某字串字面值"

注意,Swift 將 某字串 這個常量推斷為了 String 型別,因為它初始化時使用了字串字面值。

字串字面量可包含下述特殊字元:

  • 轉義的特殊字元 \0(null 字元),\\ (反斜槓本身),\t(水平製表符),\n(換行符),\r(回車符),\"(雙引號)以及 \'(單引號)

  • 單位元組的 Unicode 標量,寫作 \xnn,其中 nn 為兩個十六進位制數位

  • 雙位元組的 Unicode 標量,寫作 \unnnn,其中 nnnn 為四個十六進位制數位

  • 四位元組的 Unicode 標量,寫作 \Unnnnnnnn,其中 nnnnnnnn 為八個十六進位制數位

下面的程式碼是書寫這幾種特殊字元的例子。wiseWords 常量包含兩個經過轉義的雙引號字元。dollarSignblackHeart 以及 sparklingHeart 常量展示了 Unicode 標量字元的三種不同書寫格式:

  1. let wiseWords = "\"Imagination is more important than knowledge\" - Einstein"
  2. // "Imagination is more important than knowledge" - Einstein
  3. let dollarSign = "\x24"        // $,Unicode 標量 U+0024
  4. let blackHeart = "\u2665"     // ♥,Unicode 標量 U+2665
  5. let sparklingHeart = "\U0001F496" // ?,Unicode 標量 U+1F496

初始化一個空字串

若要建立一個空的 String 值,作為構建較長字串的第一步,你既可以將空字串字面量賦值給一個變數,也可以用初始化語法初始化一個新的 String 例項:

  1. var 空字串 = ""            // 空字串字面量
  2. var 另一個空字串 = String() // 初始化語法
  3. // 這兩個字串均為空,互為等同關係

要想知道 String 的值是否為空,可檢查其 isEmpty 屬性(布林型):

  1. if 空字串.isEmpty {
  2.     println("沒有內容")
  3. }
  4. // 輸出 "沒有內容"

    字串的可變性

要宣告一個特定的 String 是否可以修改(即可變mutable),可將其賦值給一個變數(可以修改)或常量(不可修改):

  1. var 字串變數 = "Horse"
  2. 字串變數 += " and carriage"
  3. // 字串變數 現在為 "Horse and carriage"
  4. let 字串常量 = "Highlander"
  5. 字串常量 += " and another Highlander"
  6. // 報告編譯時錯誤 - 常量字串不可修改

注:該實現方案與 Objective-C / Cocoa 的字串可變性有所差異,後者通過選擇例項所屬的類(NSStringNSMutableString)來宣告字串是否可變。

字串屬於傳值型別

Swift 的 String 型別是一種傳值型別value type)。如果將一個 String 值傳遞給函式或方法,或將其賦值給一個常量或變數,則該 String 值會被複製過去。這兩種情況均會為現有 String 值建立新的副本,實際傳遞或賦值的是其副本,而非原始例項。傳值型別的說明請見 結構與列舉型別均為傳值型別

注:該行為與 Cocoa 的 NSString 不同。Cocoa 的 NSString 例項在傳遞給函式或方法,或賦值給變數時,實際傳遞或賦值的是同一個 NSString引用。除非特別指定,期間不會發生複製字串的操作。

Swift 中 String 的“預設複製”行為可確保函式或方法傳遞 String 值給你時,這個 String 值的確屬於你,而與其呼叫方無關。可以肯定的是,除非你自己去修改它,你接收到的字串絕對不會變。

在實現級別,Swift 的編譯器會優化字串的記憶體佔用,僅在絕對需要時才會實際建立字串的副本。因此,雖然字串屬於傳值型別,你仍然總能達到最佳效能。

字元操作

Swift 的 String 型別表示 Character 值的有序集合。每個 Character 值代表單個 Unicode 字元。字串中的各個 Character 值可以通過 for-in 迴圈遍歷得到:

  1. for 字元 in "Dog!?" {
  2.     println(字元)
  3. }
  4. // D
  5. // o
  6. // g
  7. // !
  8. // ?

for-in 迴圈的說明請見 for 迴圈

還可以通過 Character 型別說明,用單字元的字串字面量,單獨建立 Character 常量或變數:

  1. let 日元符號: Character = "¥"

    字元數目統計

要獲取字串中字元的個數,可以呼叫全域性函式 countElements,並將字串作為唯一的引數傳入:

  1. let 珍稀動物園 = "考拉 ?, 蝸牛 ?, 企鵝 ?, 單峰駱駝 ?"
  2. println("珍稀動物園 有 \(countElements(珍稀動物園)) 個字元")
  3. // 輸出 "珍稀動物園 有 24 個字元"

注:不同的 Unicode 字元,以及同一個 Unicode 字元的不同表示,在記憶體中所需的儲存空間可能不同。因此,要想計算出字串的長度,必須遍歷整個字串,依次統計每一個字元。如果你在處理特別長的字串值,要謹記,countElements 函式需要遍歷整個字串方可求出其精確的字元數目。

還需注意,countElements 返回的字元數目,與包含同樣內容的 NSString 物件的 length 屬性並不總是一樣大。NSString 的長度根據該字串的 UTF-16 形式的 16 位碼元數得出,而非根據字串內 Unicode 字元的個數得出。為了體現這一事實,在 Swift 語言中,NSStringlength 屬性需通過 String 值的 utf16count 屬性訪問。

字串與字元的連線

StringCharacter 值可以用加法運算子(+)加到一起(即連線concatenate),得到一個新的 String 值:

  1. let 字串1 = "hello"
  2. let 字串2 = " there"
  3. let 字元1: Character = "!"
  4. let 字元2: Character = "?"
  5. let 字串加字元 = 字串1 + 字元1       // 等於 "hello!"
  6. let 字串加字串 = 字串1 + 字串2   // 等於 "hello there"
  7. let 字元加字串 = 字元1 + 字串1       // 等於 "!hello"
  8. let 字元加字元 = 字元1 + 字元2          // 等於 "!?"

還可以用加法賦值運算子(+=)將 StringCharacter 值追加到現有 String 變數的末尾:

  1. var 說明 = "look over"
  2. 說明 += 字串2
  3. // 說明 現在等於 "look over there"
  4. var 歡迎語 = "good morning"
  5. 歡迎語 += 字元1
  6. // 歡迎語 現在等於 "good morning!"

注:不能將 StringCharacter 追加到現有 Character 變數的末尾,因為 Character 的值只能包含一個字元。

字串內插

字串內插string interpolation)指,將常量、變數、字面量以及表示式的值插入字串字面量中,並根據這些值構造新的 String 值。字串字面量中插入的每一項均需用一對括號包圍,並在左括號前加上反斜槓:

  1. let 乘數 = 3
  2. let 訊息 = "\(乘數) 乘以 2.5 得 \(Double(乘數) * 2.5)"
  3. // 訊息 為 "3 乘以 2.5 得 7.5"

在上例中, 乘數 的值以 \(乘數) 的形式插入字串字面量中。在根據字串內插式求出實際字串的過程中,該佔位符會被 乘數 的實際值替換。

在同一個字串中,後面一個較長的表示式也用到了 乘數 的值。該表示式會計算 Double(乘數) * 2.5 的值,並將結果(7.5)插入最終字串。為了做到這點,該表示式以 \(Double(乘數) * 2.5) 的形式嵌入在字串字面量中。

注:字串內插式中,括號裡面的表示式不能包含未轉義的雙引號(")或反斜槓(\),也不能包含回車或換行符。

字串比較

Swift 提供了三種比較 String 值的方法:字串相等、字首匹配、字尾匹配。

字串相等

如果兩個 String 值所包含的字元及其順序完全相同,則認為兩者相等:

  1. let quotation = "We're a lot alike, you and I."
  2. let sameQuotation = "We're a lot alike, you and I."
  3. if quotation == sameQuotation {
  4.     println("這兩個字串符合相等的定義")
  5. }
  6. // 輸出 "這兩個字串符合相等的定義"

字首/字尾匹配

要檢查某字串是否以指定的字串開頭或結尾,可呼叫該字串的 hasPrefixhasSuffix 方法,這兩個方法均接受單個型別為 String 的引數,並返回布林值。兩個方法均對基本字串與指定的字首/字尾字串按字元逐一比較。

下例為字串構成的陣列,內容為莎士比亞戲劇《羅密歐與朱麗葉》(Romeo and Juliet)前兩幕 各場景的地點說明:

  1. let romeoAndJuliet = [
  2.     "Act 1 Scene 1: Verona, A public place",
  3.     "Act 1 Scene 2: Capulet's mansion",
  4.     "Act 1 Scene 3: A room in Capulet's mansion",
  5.     "Act 1 Scene 4: A street outside Capulet's mansion",
  6.     "Act 1 Scene 5: The Great Hall in Capulet's mansion",
  7.     "Act 2 Scene 1: Outside Capulet's mansion",
  8.     "Act 2 Scene 2: Capulet's orchard",
  9.     "Act 2 Scene 3: Outside Friar Lawrence's cell",
  10.     "Act 2 Scene 4: A street in Verona",
  11.     "Act 2 Scene 5: Capulet's mansion",
  12.     "Act 2 Scene 6: Friar Lawrence's cell"
  13. ]

可以對 romeoAndJuliet 陣列中元素使用 hasPrefix 方法,來統計該劇第一幕(Act 1)的場景數目:

  1. var act1SceneCount = 0
  2. for scene in romeoAndJuliet {
  3.     if scene.hasPrefix("Act 1 ") {
  4.         ++act1SceneCount
  5.     }
  6. }
  7. println("第一幕中有 \(act1SceneCount) 個場景")
  8. // 輸出 "第一幕中有 5 個場景"

類似地,可以用 hasSuffix 方法來統計發生在凱普萊特家(Capulet’s mansion)以及勞倫斯神父的寺院(Friar Lawrence’s cell)及其相關的場景數目:

  1. var mansionCount = 0
  2. var cellCount = 0
  3. for scene in romeoAndJuliet {
  4.     if scene.hasSuffix("Capulet's mansion") {
  5.         ++mansionCount
  6.     } else if scene.hasSuffix("Friar Lawrence's cell") {
  7.         ++cellCount
  8.     }
  9. }
  10. println("\(mansionCount) 個凱普萊特家相關場景;\(cellCount) 個寺院相關場景")
  11. // 輸出 "6 個凱普萊特家相關場景;2 個寺院相關場景"

相關文章