這里的教程為Swift官方教程中文版。

字符串和字符(Strings and Characters)


1.0 翻譯:wh1100717 校對:Hawstein

2.0 翻譯+校對:DianQK

2.1 翻譯:DianQK 校對:shanks, Realank,

2.2 校對:SketchK

3.0 校對:CMB,版本日期:2016-09-13
3.0.1, shanks, 2016-11-11

4.0 翻譯:kemchenj 2017-09-21

本頁包含內(nèi)容:

字符串是例如"hello, world","albatross"這樣的有序的Character(字符)類型的值的集合。通過String類型來表示。 一個String的內(nèi)容可以用許多方式讀取,包括作為一個Character值的集合。

Swift 的StringCharacter類型提供了快速和兼容 Unicode 的方式供你的代碼使用。創(chuàng)建和操作字符串的語法與 C 語言中字符串操作相似,輕量并且易讀。 字符串連接操作只需要簡單地通過+符號將兩個字符串相連即可。與 Swift 中其他值一樣,能否更改字符串的值,取決于其被定義為常量還是變量。你也可以在字符串內(nèi)插過程中使用字符串插入常量、變量、字面量表達(dá)成更長的字符串,這樣可以很容易的創(chuàng)建自定義的字符串值,進(jìn)行展示、存儲以及打印。

盡管語法簡易,但String類型是一種快速、現(xiàn)代化的字符串實現(xiàn)。 每一個字符串都是由編碼無關(guān)的 Unicode 字符組成,并支持訪問字符的多種 Unicode 表示形式(representations)。

注意:
Swift 的String類型與 Foundation NSString類進(jìn)行了無縫橋接。Foundation 也可以對String進(jìn)行擴(kuò)展,暴露在NSString中定義的方法。 這意味著,如果你在String中調(diào)用這些NSString的方法,將不用進(jìn)行轉(zhuǎn)換。
更多關(guān)于在 Foundation 和 Cocoa 中使用String的信息請查看 Using Swift with Cocoa and Objective-C (Swift 4)。

字符串字面量

你可以在代碼里使用一段預(yù)定義的字符串值作為字符串字面量。字符串字面量是由一對雙引號包裹著的具有固定順序的字符集。

字符串字面量可以用于為常量和變量提供初始值:

let someString = "Some string literal value"

注意someString常量通過字符串字面量進(jìn)行初始化,Swift 會推斷該常量為String類型。

多行字符串字面量

如果你需要一個字符串是跨越多行的,那就使用多行字符串字面量 —— 由一對三個雙引號包裹著的具有固定順序的文本字符集:

let quotation = """
The White Rabbit put on his spectacles.  "Where shall I begin,
please your Majesty?" he asked.

"Begin at the beginning," the King said gravely, "and go on
till you come to the end; then stop."
"""

一個多行字符串字面量包含了所有的在開啟和關(guān)閉引號(""")中的行。這個字符從開啟引號(""")之后的第一行開始,到關(guān)閉引號(""")之前為止。這就意味著字符串開啟引號之后(""")或者結(jié)束引號(""")之前都沒有換行符號。(譯者:下面兩個字符串其實是一樣的,雖然第二個使用了多行字符串的形式)

let singleLineString = "These are the same."
let multilineString = """
These are the same.
"""

如果你的代碼中,多行字符串字面量包含換行符的話,則多行字符串字面量中也會包含換行符。如果你想換行,以便加強代碼的可讀性,但是你又不想在你的多行字符串字面量中出現(xiàn)換行符的話,你可以用在行尾寫一個反斜杠(\)作為續(xù)行符。

let softWrappedQuotation = """
The White Rabbit put on his spectacles.  "Where shall I begin, \
please your Majesty?" he asked.

"Begin at the beginning," the King said gravely, "and go on \
till you come to the end; then stop."
"""

為了讓一個多行字符串字面量開始和結(jié)束于換行符,請將換行寫在第一行和最后一行,例如:

let lineBreaks = """

This string starts with a line break.
It also ends with a line break.

"""

一個多行字符串字面量能夠縮進(jìn)來匹配周圍的代碼。關(guān)閉引號(""")之前的空白字符串告訴Swift編譯器其他各行多少空白字符串需要忽略。然而,如果你在某行的前面寫的空白字符串超出了關(guān)閉引號(""")之前的空白字符串,則超出部分將被包含在多行字符串字面量中。

在上面的例子中,盡管整個多行字符串字面量都是縮進(jìn)的(源代碼縮進(jìn)),第一行和最后一行沒有以空白字符串開始(實際的變量值)。中間一行的縮進(jìn)用空白字符串(源代碼縮進(jìn))比關(guān)閉引號(""")之前的空白字符串多,所以,它的行首將有4個空格。

字符串字面量的特殊字符

字符串字面量可以包含以下特殊字符:

  • 轉(zhuǎn)義字符\0(空字符)、\\(反斜線)、\t(水平制表符)、\n(換行符)、\r(回車符)、\"(雙引號)、\'(單引號)。
  • Unicode 標(biāo)量,寫成\u{n}(u為小寫),其中n為任意一到八位十六進(jìn)制數(shù)且可用的 Unicode 位碼。

下面的代碼為各種特殊字符的使用示例。 wiseWords常量包含了兩個雙引號。 dollarSign、blackHeartsparklingHeart常量演示了三種不同格式的 Unicode 標(biāo)量:

let wiseWords = "\"Imagination is more important than knowledge\" - Einstein"
// "Imageination is more important than knowledge" - Enistein
let dollarSign = "\u{24}"             // $, Unicode 標(biāo)量 U+0024
let blackHeart = "\u{2665}"           // ?, Unicode 標(biāo)量 U+2665
let sparklingHeart = "\u{1F496}"      // ??, Unicode 標(biāo)量 U+1F496

由于多行字符串字面量使用了三個雙引號,而不是一個,所以你可以在多行字符串字面量里直接使用雙引號(")而不必加上轉(zhuǎn)義符(\)。要在多行字符串字面量中使用 """ 的話,就需要使用至少一個轉(zhuǎn)義符(\):

let threeDoubleQuotes = """
Escaping the first quote \"""
Escaping all three quotes \"\"\"
"""

初始化空字符串

要創(chuàng)建一個空字符串作為初始值,可以將空的字符串字面量賦值給變量,也可以初始化一個新的String實例:

var emptyString = ""               // 空字符串字面量
var anotherEmptyString = String()  // 初始化方法
// 兩個字符串均為空并等價。

您可以通過檢查其Bool類型的isEmpty屬性來判斷該字符串是否為空:

if emptyString.isEmpty {
    print("Nothing to see here")
}
// 打印輸出:"Nothing to see here"

字符串可變性

您可以通過將一個特定字符串分配給一個變量來對其進(jìn)行修改,或者分配給一個常量來保證其不會被修改:

var variableString = "Horse"
variableString += " and carriage"
// variableString 現(xiàn)在為 "Horse and carriage"

let constantString = "Highlander"
constantString += " and another Highlander"
// 這會報告一個編譯錯誤 (compile-time error) - 常量字符串不可以被修改。

注意:
在 Objective-C 和 Cocoa 中,您需要通過選擇兩個不同的類(NSStringNSMutableString)來指定字符串是否可以被修改。

字符串是值類型

Swift 的String類型是值類型。 如果您創(chuàng)建了一個新的字符串,那么當(dāng)其進(jìn)行常量、變量賦值操作,或在函數(shù)/方法中傳遞時,會進(jìn)行值拷貝。 任何情況下,都會對已有字符串值創(chuàng)建新副本,并對該新副本進(jìn)行傳遞或賦值操作。 值類型在 結(jié)構(gòu)體和枚舉是值類型 中進(jìn)行了詳細(xì)描述。

Swift 默認(rèn)字符串拷貝的方式保證了在函數(shù)/方法中傳遞的是字符串的值。 很明顯無論該值來自于哪里,都是您獨自擁有的。 您可以確信傳遞的字符串不會被修改,除非你自己去修改它。

在實際編譯時,Swift 編譯器會優(yōu)化字符串的使用,使實際的復(fù)制只發(fā)生在絕對必要的情況下,這意味著您將字符串作為值類型的同時可以獲得極高的性能。

使用字符

您可通過for-in循環(huán)來遍歷字符串,獲取字符串中每一個字符的值:

for character in "Dog!??" {
    print(character)
}
// D
// o
// g
// !
// ??

for-in循環(huán)在 For 循環(huán) 中進(jìn)行了詳細(xì)描述。

另外,通過標(biāo)明一個Character類型并用字符字面量進(jìn)行賦值,可以建立一個獨立的字符常量或變量:

let exclamationMark: Character = "!"

字符串可以通過傳遞一個值類型為Character的數(shù)組作為自變量來初始化:

let catCharacters: [Character] = ["C", "a", "t", "!", "??"]
let catString = String(catCharacters)
print(catString)
// 打印輸出:"Cat!??"

連接字符串和字符

字符串可以通過加法運算符(+)相加在一起(或稱“連接”)創(chuàng)建一個新的字符串:

let string1 = "hello"
let string2 = " there"
var welcome = string1 + string2
// welcome 現(xiàn)在等于 "hello there"

您也可以通過加法賦值運算符 (+=) 將一個字符串添加到一個已經(jīng)存在字符串變量上:

var instruction = "look over"
instruction += string2
// instruction 現(xiàn)在等于 "look over there"

您可以用append()方法將一個字符附加到一個字符串變量的尾部:

let exclamationMark: Character = "!"
welcome.append(exclamationMark)
// welcome 現(xiàn)在等于 "hello there!"

注意:
您不能將一個字符串或者字符添加到一個已經(jīng)存在的字符變量上,因為字符變量只能包含一個字符。

如果你需要使用多行字符串字面量來拼接字符串,并且你需要字符串每一行都以換行符結(jié)尾,包括最后一行:

let badStart = """
one
two
"""
let end = """
three
"""
print(badStart + end)
// 打印兩行:
// one
// twothree

let goodStart = """
one
two

"""
print(goodStart + end)
// 打印三行:
// one
// two
// three

上面的代碼,把 badStartend 拼接起來的字符串非我們想要的結(jié)果。因為 badStart 最后一行沒有換行符,它與 end 的第一行結(jié)合到了一起。相反的,goodStart 的每一行都以換行符結(jié)尾,所以它與 end 拼接的字符串總共有三行,正如我們期望的那樣。

字符串插值

字符串插值是一種構(gòu)建新字符串的方式,可以在其中包含常量、變量、字面量和表達(dá)式。字符串字面量多行字符串字面量都可以使用字符串插值。 您插入的字符串字面量的每一項都在以反斜線為前綴的圓括號中:

let multiplier = 3
let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
// message 是 "3 times 2.5 is 7.5"

在上面的例子中,multiplier作為\(multiplier)被插入到一個字符串常量量中。 當(dāng)創(chuàng)建字符串執(zhí)行插值計算時此占位符會被替換為multiplier實際的值。

multiplier的值也作為字符串中后面表達(dá)式的一部分。 該表達(dá)式計算Double(multiplier) * 2.5的值并將結(jié)果 (7.5) 插入到字符串中。 在這個例子中,表達(dá)式寫為\(Double(multiplier) * 2.5)并包含在字符串字面量中。

注意:
插值字符串中寫在括號中的表達(dá)式不能包含非轉(zhuǎn)義反斜杠 (\),并且不能包含回車或換行符。不過,插值字符串可以包含其他字面量。

Unicode

Unicode是一個國際標(biāo)準(zhǔn),用于文本的編碼和表示。 它使您可以用標(biāo)準(zhǔn)格式表示來自任意語言幾乎所有的字符,并能夠?qū)ξ谋疚募蚓W(wǎng)頁這樣的外部資源中的字符進(jìn)行讀寫操作。 Swift 的StringCharacter類型是完全兼容 Unicode 標(biāo)準(zhǔn)的。

Unicode 標(biāo)量

Swift 的String類型是基于 Unicode 標(biāo)量 建立的。 Unicode 標(biāo)量是對應(yīng)字符或者修飾符的唯一的21位數(shù)字,例如U+0061表示小寫的拉丁字母(LATIN SMALL LETTER A)("a"),U+1F425表示小雞表情(FRONT-FACING BABY CHICK) ("??")。

注意: Unicode 碼位(code poing) 的范圍是U+0000U+D7FF或者U+E000U+10FFFF。Unicode 標(biāo)量不包括 Unicode 代理項(surrogate pair) 碼位,其碼位范圍是U+D800U+DFFF

注意不是所有的21位 Unicode 標(biāo)量都代表一個字符,因為有一些標(biāo)量是留作未來分配的。已經(jīng)代表一個典型字符的標(biāo)量都有自己的名字,例如上面例子中的LATIN SMALL LETTER AFRONT-FACING BABY CHICK。

可擴(kuò)展的字形群集

每一個 Swift 的Character類型代表一個可擴(kuò)展的字形群。 一個可擴(kuò)展的字形群是一個或多個可生成人類可讀的字符 Unicode 標(biāo)量的有序排列。
舉個例子,字母é可以用單一的 Unicode 標(biāo)量é(LATIN SMALL LETTER E WITH ACUTE, 或者U+00E9)來表示。然而一個標(biāo)準(zhǔn)的字母e(LATIN SMALL LETTER E或者U+0065) 加上一個急促重音(COMBINING ACTUE ACCENT)的標(biāo)量(U+0301),這樣一對標(biāo)量就表示了同樣的字母é。 這個急促重音的標(biāo)量形象的將e轉(zhuǎn)換成了é。

在這兩種情況中,字母é代表了一個單一的 Swift 的Character值,同時代表了一個可擴(kuò)展的字形群。 在第一種情況,這個字形群包含一個單一標(biāo)量;而在第二種情況,它是包含兩個標(biāo)量的字形群:

let eAcute: Character = "\u{E9}"                         // é
let combinedEAcute: Character = "\u{65}\u{301}"          // e 后面加上 ?
// eAcute 是 é, combinedEAcute 是 é

可擴(kuò)展的字符群集是一個靈活的方法,用許多復(fù)雜的腳本字符表示單一的Character值。 例如,來自朝鮮語字母表的韓語音節(jié)能表示為組合或分解的有序排列。 在 Swift 都會表示為同一個單一的Character值:

let precomposed: Character = "\u{D55C}"                  // ?
let decomposed: Character = "\u{1112}\u{1161}\u{11AB}"   // ?, ?, ?
// precomposed 是 ?, decomposed 是 ?

可拓展的字符群集可以使包圍記號(例如COMBINING ENCLOSING CIRCLE或者U+20DD)的標(biāo)量包圍其他 Unicode 標(biāo)量,作為一個單一的Character值:

let enclosedEAcute: Character = "\u{E9}\u{20DD}"
// enclosedEAcute 是 é?

地域性指示符號的 Unicode 標(biāo)量可以組合成一個單一的Character值,例如REGIONAL INDICATOR SYMBOL LETTER U(U+1F1FA)和REGIONAL INDICATOR SYMBOL LETTER S(U+1F1F8):

let regionalIndicatorForUS: Character = "\u{1F1FA}\u{1F1F8}"
// regionalIndicatorForUS 是 ????

計算字符數(shù)量

如果想要獲得一個字符串中Character值的數(shù)量,可以使用count屬性:

let unusualMenagerie = "Koala ??, Snail ??, Penguin ??, Dromedary ??"
print("unusualMenagerie has \(unusualMenagerie.count) characters")
// 打印輸出 "unusualMenagerie has 40 characters"

注意在 Swift 中,使用可拓展的字符群集作為Character值來連接或改變字符串時,并不一定會更改字符串的字符數(shù)量。

例如,如果你用四個字符的單詞cafe初始化一個新的字符串,然后添加一個COMBINING ACTUE ACCENT(U+0301)作為字符串的結(jié)尾。最終這個字符串的字符數(shù)量仍然是4,因為第四個字符是é,而不是e

var word = "cafe"
print("the number of characters in \(word) is \(word.count)")
// 打印輸出 "the number of characters in cafe is 4"

word += "\u{301}"    // 拼接一個重音, U+0301

print("the number of characters in \(word) is \(word.count)")
// 打印輸出 "the number of characters in café is 4"

注意:
可擴(kuò)展的字符群集可以組成一個或者多個 Unicode 標(biāo)量。這意味著不同的字符以及相同字符的不同表示方式可能需要不同數(shù)量的內(nèi)存空間來存儲。所以 Swift 中的字符在一個字符串中并不一定占用相同的內(nèi)存空間數(shù)量。因此在沒有獲取字符串的可擴(kuò)展的字符群的范圍時候,就不能計算出字符串的字符數(shù)量。如果您正在處理一個長字符串,需要注意count屬性必須遍歷全部的 Unicode 標(biāo)量,來確定字符串的字符數(shù)量。

另外需要注意的是通過count屬性返回的字符數(shù)量并不總是與包含相同字符的NSStringlength屬性相同。NSStringlength屬性是利用 UTF-16 表示的十六位代碼單元數(shù)字,而不是 Unicode 可擴(kuò)展的字符群集。

訪問和修改字符串

你可以通過字符串的屬性和方法來訪問和修改它,當(dāng)然也可以用下標(biāo)語法完成。

字符串索引

每一個String值都有一個關(guān)聯(lián)的索引(index)類型,String.Index,它對應(yīng)著字符串中的每一個Character的位置。

前面提到,不同的字符可能會占用不同數(shù)量的內(nèi)存空間,所以要知道Character的確定位置,就必須從String開頭遍歷每一個 Unicode 標(biāo)量直到結(jié)尾。因此,Swift 的字符串不能用整數(shù)(integer)做索引。

使用startIndex屬性可以獲取一個String的第一個Character的索引。使用endIndex屬性可以獲取最后一個Character的后一個位置的索引。因此,endIndex屬性不能作為一個字符串的有效下標(biāo)。如果String是空串,startIndexendIndex是相等的。

通過調(diào)用 Stringindex(before:)index(after:) 方法,可以立即得到前面或后面的一個索引。您還可以通過調(diào)用 index(_:offsetBy:) 方法來獲取對應(yīng)偏移量的索引,這種方式可以避免多次調(diào)用 index(before:)index(after:) 方法。

你可以使用下標(biāo)語法來訪問 String 特定索引的 Character。

let greeting = "Guten Tag!"
greeting[greeting.startIndex]
// G
greeting[greeting.index(before: greeting.endIndex)]
// !
greeting[greeting.index(after: greeting.startIndex)]
// u
let index = greeting.index(greeting.startIndex, offsetBy: 7)
greeting[index]
// a

試圖獲取越界索引對應(yīng)的 Character,將引發(fā)一個運行時錯誤。

greeting[greeting.endIndex] // error
greeting.index(after: endIndex) // error

使用 indices 屬性會創(chuàng)建一個包含全部索引的范圍(Range),用來在一個字符串中訪問單個字符。

for index in greeting.indices {
   print("\(greeting[index]) ", terminator: "")
}
// 打印輸出 "G u t e n T a g ! "

注意:
您可以使用 startIndexendIndex 屬性或者 index(before:)index(after:)index(_:offsetBy:) 方法在任意一個確認(rèn)的并遵循 Collection 協(xié)議的類型里面,如上文所示是使用在 String 中,您也可以使用在 ArrayDictionarySet中。

插入和刪除

調(diào)用 insert(_:at:) 方法可以在一個字符串的指定索引插入一個字符,調(diào)用 insert(contentsOf:at:) 方法可以在一個字符串的指定索引插入一個段字符串。

var welcome = "hello"
welcome.insert("!", at: welcome.endIndex)
// welcome 變量現(xiàn)在等于 "hello!"

welcome.insert(contentsOf:" there", at: welcome.index(before: welcome.endIndex))
// welcome 變量現(xiàn)在等于 "hello there!"

調(diào)用 remove(at:) 方法可以在一個字符串的指定索引刪除一個字符,調(diào)用 removeSubrange(_:) 方法可以在一個字符串的指定索引刪除一個子字符串。

welcome.remove(at: welcome.index(before: welcome.endIndex))
// welcome 現(xiàn)在等于 "hello there"

let range = welcome.index(welcome.endIndex, offsetBy: -6)..<welcome.endIndex
welcome.removeSubrange(range)
// welcome 現(xiàn)在等于 "hello"

注意: 您可以使用 insert(_:at:)、insert(contentsOf:at:)remove(at:)removeSubrange(_:) 方法在任意一個確認(rèn)的并遵循 RangeReplaceableCollection 協(xié)議的類型里面,如上文所示是使用在 String 中,您也可以使用在 Array、DictionarySet 中。

子字符串

當(dāng)你從字符串中獲取一個子字符串 —— 例如,使用下標(biāo)或者 prefix(_:) 之類的方法 —— 就可以得到一個 SubString 的實例,而非另外一個 String。Swift 里的 SubString 絕大部分函數(shù)都跟 String 一樣,意味著你可以使用同樣的方式去操作 SubStringString。然而,跟 String 不同的是,你只有在短時間內(nèi)需要操作字符串時,才會使用 SubString。當(dāng)你需要長時間保存結(jié)果時,就把 SubString 轉(zhuǎn)化為 String 的實例:

let greeting = "Hello, world!"
let index = greeting.index(of: ",") ?? greeting.endIndex
let beginning = greeting[..<index]
// beginning 的值為 "Hello"

// 把結(jié)果轉(zhuǎn)化為 String 以便長期存儲。
let newString = String(beginning)

就像 String,每一個 SubString 都會在內(nèi)存里保存字符集。而 StringSubString 的區(qū)別在于性能優(yōu)化上,SubString 可以重用原 String 的內(nèi)存空間,或者另一個 SubString 的內(nèi)存空間(String 也有同樣的優(yōu)化,但如果兩個 String 共享內(nèi)存的話,它們就會相等)。這一優(yōu)化意味著你在修改 StringSubString 之前都不需要消耗性能去復(fù)制內(nèi)存。就像前面說的那樣,SubString 不適合長期存儲 —— 因為它重用了原 String 的內(nèi)存空間,原 String 的內(nèi)存空間必須保留直到它的 SubString 不再被使用為止。

上面的例子,greeting 是一個 String,意味著它在內(nèi)存里有一片空間保存字符集。而由于 beginninggreetingSubString,它重用了 greeting 的內(nèi)存空間。相反,newString 是一個 String —— 它是使用 SubString 創(chuàng)建的,擁有一片自己的內(nèi)存空間。下面的圖展示了他們之間的關(guān)系:

注意 StringSubString 都遵循 StringProtocol<//apple_ref/swift/intf/s:s14StringProtocolP> 協(xié)議,這意味著操作字符串的函數(shù)使用 StringProtocol 會更加方便。你可以傳入 StringSubString 去調(diào)用函數(shù)。

比較字符串

Swift 提供了三種方式來比較文本值:字符串字符相等、前綴相等和后綴相等。

字符串/字符相等

字符串/字符可以用等于操作符(==)和不等于操作符(!=),詳細(xì)描述在比較運算符

let quotation = "We're a lot alike, you and I."
let sameQuotation = "We're a lot alike, you and I."
if quotation == sameQuotation {
    print("These two strings are considered equal")
}
// 打印輸出 "These two strings are considered equal"

如果兩個字符串(或者兩個字符)的可擴(kuò)展的字形群集是標(biāo)準(zhǔn)相等的,那就認(rèn)為它們是相等的。在這個情況下,即使可擴(kuò)展的字形群集是有不同的 Unicode 標(biāo)量構(gòu)成的,只要它們有同樣的語言意義和外觀,就認(rèn)為它們標(biāo)準(zhǔn)相等。

例如,LATIN SMALL LETTER E WITH ACUTE(U+00E9)就是標(biāo)準(zhǔn)相等于LATIN SMALL LETTER E(U+0065)后面加上COMBINING ACUTE ACCENT(U+0301)。這兩個字符群集都是表示字符é的有效方式,所以它們被認(rèn)為是標(biāo)準(zhǔn)相等的:

// "Voulez-vous un café?" 使用 LATIN SMALL LETTER E WITH ACUTE
let eAcuteQuestion = "Voulez-vous un caf\u{E9}?"

// "Voulez-vous un café?" 使用 LATIN SMALL LETTER E and COMBINING ACUTE ACCENT
let combinedEAcuteQuestion = "Voulez-vous un caf\u{65}\u{301}?"

if eAcuteQuestion == combinedEAcuteQuestion {
    print("These two strings are considered equal")
}
// 打印輸出 "These two strings are considered equal"

相反,英語中的LATIN CAPITAL LETTER A(U+0041,或者A)不等于俄語中的CYRILLIC CAPITAL LETTER A(U+0410,或者A)。兩個字符看著是一樣的,但卻有不同的語言意義:

let latinCapitalLetterA: Character = "\u{41}"

let cyrillicCapitalLetterA: Character = "\u{0410}"

if latinCapitalLetterA != cyrillicCapitalLetterA {
    print("These two characters are not equivalent")
}
// 打印 "These two characters are not equivalent"

注意:
在 Swift 中,字符串和字符并不區(qū)分地域(not locale-sensitive)。

前綴/后綴相等

通過調(diào)用字符串的hasPrefix(_:)/hasSuffix(_:)方法來檢查字符串是否擁有特定前綴/后綴,兩個方法均接收一個String類型的參數(shù),并返回一個布爾值。

下面的例子以一個字符串?dāng)?shù)組表示莎士比亞話劇《羅密歐與朱麗葉》中前兩場的場景位置:

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

您可以調(diào)用hasPrefix(_:)方法來計算話劇中第一幕的場景數(shù):

var act1SceneCount = 0
for scene in romeoAndJuliet {
    if scene.hasPrefix("Act 1 ") {
        act1SceneCount += 1
    }
}
print("There are \(act1SceneCount) scenes in Act 1")
// 打印輸出 "There are 5 scenes in Act 1"

相似地,您可以用hasSuffix(_:)方法來計算發(fā)生在不同地方的場景數(shù):

var mansionCount = 0
var cellCount = 0
for scene in romeoAndJuliet {
    if scene.hasSuffix("Capulet's mansion") {
        mansionCount += 1
    } else if scene.hasSuffix("Friar Lawrence's cell") {
        cellCount += 1
    }
}
print("\(mansionCount) mansion scenes; \(cellCount) cell scenes")
// 打印輸出 "6 mansion scenes; 2 cell scenes"

注意:
hasPrefix(_:)hasSuffix(_:)方法都是在每個字符串中逐字符比較其可擴(kuò)展的字符群集是否標(biāo)準(zhǔn)相等,詳細(xì)描述在字符串/字符相等。

字符串的 Unicode 表示形式

當(dāng)一個 Unicode 字符串被寫進(jìn)文本文件或者其他儲存時,字符串中的 Unicode 標(biāo)量會用 Unicode 定義的幾種編碼格式(encoding forms)編碼。每一個字符串中的小塊編碼都被稱代碼單元(code units)。這些包括 UTF-8 編碼格式(編碼字符串為8位的代碼單元), UTF-16 編碼格式(編碼字符串位16位的代碼單元),以及 UTF-32 編碼格式(編碼字符串32位的代碼單元)。

Swift 提供了幾種不同的方式來訪問字符串的 Unicode 表示形式。 您可以利用for-in來對字符串進(jìn)行遍歷,從而以 Unicode 可擴(kuò)展的字符群集的方式訪問每一個Character值。 該過程在 使用字符 中進(jìn)行了描述。

另外,能夠以其他三種 Unicode 兼容的方式訪問字符串的值:

  • UTF-8 代碼單元集合 (利用字符串的utf8屬性進(jìn)行訪問)
  • UTF-16 代碼單元集合 (利用字符串的utf16屬性進(jìn)行訪問)
  • 21位的 Unicode 標(biāo)量值集合,也就是字符串的 UTF-32 編碼格式 (利用字符串的unicodeScalars屬性進(jìn)行訪問)

下面由D,o,g,?(DOUBLE EXCLAMATION MARK, Unicode 標(biāo)量 U+203C)和??(DOG FACE,Unicode 標(biāo)量為U+1F436)組成的字符串中的每一個字符代表著一種不同的表示:

let dogString = "Dog???"

UTF-8 表示

您可以通過遍歷Stringutf8屬性來訪問它的UTF-8表示。 其為String.UTF8View類型的屬性,UTF8View是無符號8位 (UInt8) 值的集合,每一個UInt8值都是一個字符的 UTF-8 表示:

Character D
U+0044
o
U+006F
g
U+0067
?
U+203C
??
U+1F436
UTF-8
Code Unit
68 111 103 226 128 188 240 159 144 182
Position 0 1 2 3 4 5 6 7 8 9
for codeUnit in dogString.utf8 {
    print("\(codeUnit) ", terminator: "")
}
print("")
// 68 111 103 226 128 188 240 159 144 182

上面的例子中,前三個10進(jìn)制codeUnit值 (68, 111, 103) 代表了字符D、og,它們的 UTF-8 表示與 ASCII 表示相同。 接下來的三個10進(jìn)制codeUnit值 (226, 128, 188) 是DOUBLE EXCLAMATION MARK的3字節(jié) UTF-8 表示。 最后的四個codeUnit值 (240, 159, 144, 182) 是DOG FACE的4字節(jié) UTF-8 表示。

UTF-16 表示

您可以通過遍歷Stringutf16屬性來訪問它的UTF-16表示。 其為String.UTF16View類型的屬性,UTF16View是無符號16位 (UInt16) 值的集合,每一個UInt16都是一個字符的 UTF-16 表示:

Character D
U+0044
o
U+006F
g
U+0067
?
U+203C
??
U+1F436
UTF-16
Code Unit
68 111 103 8252 55357 56374
Position 0 1 2 3 4 5
for codeUnit in dogString.utf16 {
    print("\(codeUnit) ", terminator: "")
}
print("")
// 68 111 103 8252 55357 56374

同樣,前三個codeUnit值 (68, 111, 103) 代表了字符D、og,它們的 UTF-16 代碼單元和 UTF-8 完全相同(因為這些 Unicode 標(biāo)量表示 ASCII 字符)。

第四個codeUnit值 (8252) 是一個等于十六進(jìn)制203C的的十進(jìn)制值。這個代表了DOUBLE EXCLAMATION MARK字符的 Unicode 標(biāo)量值U+203C。這個字符在 UTF-16 中可以用一個代碼單元表示。

第五和第六個codeUnit值 (5535756374) 是DOG FACE字符的 UTF-16 表示。 第一個值為U+D83D(十進(jìn)制值為55357),第二個值為U+DC36(十進(jìn)制值為56374)。

Unicode 標(biāo)量表示

您可以通過遍歷String值的unicodeScalars屬性來訪問它的 Unicode 標(biāo)量表示。 其為UnicodeScalarView類型的屬性,UnicodeScalarViewUnicodeScalar類型的值的集合。 UnicodeScalar是21位的 Unicode 代碼點。

每一個UnicodeScalar擁有一個value屬性,可以返回對應(yīng)的21位數(shù)值,用UInt32來表示:

Character D
U+0044
o
U+006F
g
U+0067
?
U+203C
??
U+1F436
Unicode Scalar
Code Unit
68 111 103 8252 128054
Position 0 1 2 3 4
for scalar in dogString.unicodeScalars {
    print("\(scalar.value) ", terminator: "")
}
print("")
// 68 111 103 8252 128054

前三個UnicodeScalar值(68, 111, 103)的value屬性仍然代表字符D、og。 第四個codeUnit值(8252)仍然是一個等于十六進(jìn)制203C的十進(jìn)制值。這個代表了DOUBLE EXCLAMATION MARK字符的 Unicode 標(biāo)量U+203C

第五個UnicodeScalar值的value屬性,128054,是一個十六進(jìn)制1F436的十進(jìn)制表示。其等同于DOG FACE的 Unicode 標(biāo)量U+1F436

作為查詢它們的value屬性的一種替代方法,每個UnicodeScalar值也可以用來構(gòu)建一個新的String值,比如在字符串插值中使用:

for scalar in dogString.unicodeScalars {
    print("\(scalar) ")
}
// D
// o
// g
// ?
// ??
? 基本運算符 集合類型 ?
?