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

字符串和字符

字符串是是一系列字符的集合,例如 "hello, world","albatross"。Swift 的字符串通過 String 類型來表示。而 String 內(nèi)容的訪問方式有多種,例如以 Character 值的集合。

Swift 的 StringCharacter 類型提供了一種快速且兼容 Unicode 的方式來處理代碼中的文本內(nèi)容。創(chuàng)建和操作字符串的語法與 C 語言中字符串操作相似,輕量并且易讀。通過 + 符號(hào)就可以非常簡(jiǎn)單的實(shí)現(xiàn)兩個(gè)字符串的拼接操作。與 Swift 中其他值一樣,能否更改字符串的值,取決于其被定義為常量還是變量。你可以在已有字符串中插入常量、變量、字面量和表達(dá)式從而形成更長的字符串,這一過程也被稱為字符串插值。尤其是在為顯示、存儲(chǔ)和打印創(chuàng)建自定義字符串值時(shí),字符串插值操作尤其有用。

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

注意

Swift 的 String 類型與 Foundation NSString 類進(jìn)行了無縫橋接。Foundation 還對(duì) String 進(jìn)行擴(kuò)展使其可以訪問 NSString 類型中定義的方法。這意味著調(diào)用那些 NSString 的方法,你無需進(jìn)行任何類型轉(zhuǎn)換。

更多關(guān)于在 Foundation 和 Cocoa 中使用 String 的信息請(qǐng)查看 Bridging Between String and NSString。

字符串字面量

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

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

let someString = "Some string literal value"

注意,Swift 之所以推斷 someString 常量為字符串類型,是因?yàn)樗褂昧俗置媪糠绞竭M(jìn)行初始化。

多行字符串字面量

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

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."
"""

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

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

如果你的代碼中,多行字符串字面量包含換行符的話,則多行字符串字面量中也會(huì)包含換行符。如果你想換行,以便加強(qiáng)代碼的可讀性,但是你又不想在你的多行字符串字面量中出現(xiàn)換行符的話,你可以用在行尾寫一個(gè)反斜杠(\)作為續(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."
"""

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

let lineBreaks = """

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

"""

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

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

字符串字面量的特殊字符

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

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

下面的代碼為各種特殊字符的使用示例。 wiseWords 常量包含了兩個(gè)雙引號(hào)。 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

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

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

擴(kuò)展字符串分隔符

您可以將字符串文字放在擴(kuò)展分隔符中,這樣字符串中的特殊字符將會(huì)被直接包含而非轉(zhuǎn)義后的效果。將字符串放在引號(hào)(")中并用數(shù)字符號(hào)(#)括起來。例如,打印字符串文字 #"Line 1 \nLine 2"# 會(huì)打印換行符轉(zhuǎn)義序列(\n)而不是給文字換行。

如果需要字符串文字中字符的特殊效果,請(qǐng)匹配轉(zhuǎn)義字符(\)后面添加與起始位置個(gè)數(shù)相匹配的 # 符。 例如,如果您的字符串是 #"Line 1 \nLine 2"# 并且您想要換行,則可以使用 #"Line 1 \#nLine 2"# 來代替。 同樣,###"Line1 \###nLine2"### 也可以實(shí)現(xiàn)換行效果。

擴(kuò)展分隔符創(chuàng)建的字符串文字也可以是多行字符串文字。 您可以使用擴(kuò)展分隔符在多行字符串中包含文本 """,覆蓋原有的結(jié)束文字的默認(rèn)行為。例如:

let threeMoreDoubleQuotationMarks = #"""
Here are three more double quotes: """
"""#

初始化空字符串

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

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

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

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

字符串可變性

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

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

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

注意

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

字符串是值類型

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

Swift 默認(rèn)拷貝字符串的行為保證了在函數(shù)/方法向你傳遞的字符串所屬權(quán)屬于你,無論該值來自于哪里。你可以確信傳遞的字符串不會(huì)被修改,除非你自己去修改它。

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

使用字符

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

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

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

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

let exclamationMark: Character = "!"

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

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

連接字符串和字符

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

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

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

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

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

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

注意

你不能將一個(gè)字符串或者字符添加到一個(gè)已經(jīng)存在的字符變量上,因?yàn)樽址兞恐荒馨粋€(gè)字符。

如果你需要使用多行字符串字面量來拼接字符串,并且你需要字符串每一行都以換行符結(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é)果。因?yàn)?badStart 最后一行沒有換行符,它與 end 的第一行結(jié)合到了一起。相反的,goodStart 的每一行都以換行符結(jié)尾,所以它與 end 拼接的字符串總共有三行,正如我們期望的那樣。

字符串插值

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

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

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

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

注意

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

Unicode

Unicode是一個(gè)用于在不同書寫系統(tǒng)中對(duì)文本進(jìn)行編碼、表示和處理的國際標(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)量是對(duì)應(yīng)字符或者修飾符的唯一的 21 位數(shù)字,例如 U+0061 表示小寫的拉丁字母(LATIN SMALL LETTER A)("a"),U+1F425 表示小雞表情(FRONT-FACING BABY CHICK)("??")。

請(qǐng)注意,并非所有 21 位 Unicode 標(biāo)量值都分配給字符,某些標(biāo)量被保留用于將來分配或用于 UTF-16 編碼。已分配的標(biāo)量值通常也有一個(gè)名稱,例如上面示例中的 LATIN SMALL LETTER A 和 FRONT-FACING BABY CHICK。

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

每一個(gè) Swift 的 Character 類型代表一個(gè)可擴(kuò)展的字形群。而一個(gè)可擴(kuò)展的字形群構(gòu)成了人類可讀的單個(gè)字符,它由一個(gè)或多個(gè)(當(dāng)組合時(shí)) Unicode 標(biāo)量的序列組成。

舉個(gè)例子,字母 é 可以用單一的 Unicode 標(biāo)量 é(LATIN SMALL LETTER E WITH ACUTE, 或者 U+00E9)來表示。然而一個(gè)標(biāo)準(zhǔn)的字母 e(LATIN SMALL LETTER E 或者 U+0065) 加上一個(gè)急促重音(COMBINING ACTUE ACCENT)的標(biāo)量(U+0301),這樣一對(duì)標(biāo)量就表示了同樣的字母 é。 這個(gè)急促重音的標(biāo)量形象的將 e 轉(zhuǎn)換成了 é。

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

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

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

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

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

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

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

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

計(jì)算字符數(shù)量

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

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

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

例如,如果你用四個(gè)字符的單詞 cafe 初始化一個(gè)新的字符串,然后添加一個(gè) COMBINING ACTUE ACCENT(U+0301)作為字符串的結(jié)尾。最終這個(gè)字符串的字符數(shù)量仍然是 4,因?yàn)榈谒膫€(gè)字符是 é,而不是 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}"    // 拼接一個(gè)重音,U+0301

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

注意

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

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

訪問和修改字符串

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

字符串索引

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

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

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

通過調(diào)用 Stringindex(before:)index(after:) 方法,可以立即得到前面或后面的一個(gè)索引。你還可以通過調(diào)用 index(_:offsetBy:) 方法來獲取對(duì)應(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

試圖獲取越界索引對(duì)應(yīng)的 Character,將引發(fā)一個(gè)運(yùn)行時(shí)錯(cuò)誤。

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

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

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

注意

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

插入和刪除

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

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:) 方法可以在一個(gè)字符串的指定索引刪除一個(gè)字符,調(diào)用 removeSubrange(_:) 方法可以在一個(gè)字符串的指定索引刪除一個(gè)子字符串。

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(_:) 方法在任意一個(gè)確認(rèn)的并遵循 RangeReplaceableCollection 協(xié)議的類型里面,如上文所示是使用在 String 中,你也可以使用在 Array、DictionarySet 中。

子字符串

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

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

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

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

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

注意

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

比較字符串

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

字符串/字符相等

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

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”

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

例如,LATIN SMALL LETTER E WITH ACUTE(U+00E9)就是標(biāo)準(zhǔn)相等于 LATIN SMALL LETTER E(U+0065)后面加上 COMBINING ACUTE ACCENT(U+0301)。這兩個(gè)字符群集都是表示字符 é 的有效方式,所以它們被認(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)。兩個(gè)字符看著是一樣的,但卻有不同的語言意義:

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(_:) 方法來檢查字符串是否擁有特定前綴/后綴,兩個(gè)方法均接收一個(gè) String 類型的參數(shù),并返回一個(gè)布爾值。

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

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(_:) 方法來計(jì)算話劇中第一幕的場(chǎng)景數(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(_:) 方法來計(jì)算發(fā)生在不同地方的場(chǎng)景數(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(_:) 方法都是在每個(gè)字符串中逐字符比較其可擴(kuò)展的字符群集是否標(biāo)準(zhǔn)相等,詳細(xì)描述在 字符串/字符相等。

字符串的 Unicode 表示形式

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

Swift 提供了幾種不同的方式來訪問字符串的 Unicode 表示形式。你可以利用 for-in 來對(duì)字符串進(jìn)行遍歷,從而以 Unicode 可擴(kuò)展的字符群集的方式訪問每一個(gè) 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)組成的字符串中的每一個(gè)字符代表著一種不同的表示:

let dogString = "Dog???"

UTF-8 表示

你可以通過遍歷 Stringutf8 屬性來訪問它的 UTF-8 表示。其為 String.UTF8View 類型的屬性,UTF8View 是無符號(hào) 8 位(UInt8)值的集合,每一個(gè) UInt8 值都是一個(gè)字符的 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

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

UTF-16 表示

你可以通過遍歷 Stringutf16 屬性來訪問它的 UTF-16 表示。其為 String.UTF16View 類型的屬性,UTF16View 是無符號(hào)16位(UInt16)值的集合,每一個(gè) UInt16 都是一個(gè)字符的 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

同樣,前三個(gè) codeUnit 值(68、111103)代表了字符 D、og,它們的 UTF-16 代碼單元和 UTF-8 完全相同(因?yàn)檫@些 Unicode 標(biāo)量表示 ASCII 字符)。

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

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

Unicode 標(biāo)量表示

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

每一個(gè) UnicodeScalar 擁有一個(gè) value 屬性,可以返回對(duì)應(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

前三個(gè) UnicodeScalar 值(68、111、103)的 value 屬性仍然代表字符 Dog。

第四個(gè) codeUnit 值(8252)仍然是一個(gè)等于十六進(jìn)制 203C 的十進(jìn)制值。這個(gè)代表了 DOUBLE EXCLAMATION MARK 字符的 Unicode 標(biāo)量 U+203C。

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

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

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