字符串是是一系列字符的集合,例如 "hello, world"
,"albatross"
。Swift 的字符串通過 String
類型來表示。而 String
內(nèi)容的訪問方式有多種,例如以 Character
值的集合。
Swift 的 String
和 Character
類型提供了一種快速且兼容 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
類型與 FoundationNSString
類進(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è)空格。
字符串字面量可以包含以下特殊字符:
\0
(空字符)、\\
(反斜線)、\t
(水平制表符)、\n
(換行符)、\r
(回車符)、\"
(雙引號(hào))、\'
(單引號(hào))。\u{n}
(u 為小寫),其中 n
為任意一到八位十六進(jìn)制數(shù)且可用的 Unicode 位碼。下面的代碼為各種特殊字符的使用示例。 wiseWords
常量包含了兩個(gè)雙引號(hào)。 dollarSign
、blackHeart
和 sparklingHeart
常量演示了三種不同格式的 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ò)展分隔符中,這樣字符串中的特殊字符將會(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è)不同的類(
NSString
和NSMutableString
)來指定字符串是否可以被修改。
在 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
上面的代碼,把 badStart
和 end
拼接起來的字符串非我們想要的結(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是一個(gè)用于在不同書寫系統(tǒng)中對(duì)文本進(jìn)行編碼、表示和處理的國際標(biāo)準(zhǔn)。它使你可以用標(biāo)準(zhǔn)格式表示來自任意語言幾乎所有的字符,并能夠?qū)ξ谋疚募蚓W(wǎng)頁這樣的外部資源中的字符進(jìn)行讀寫操作。Swift 的 String
和 Character
類型是完全兼容 Unicode 標(biāo)準(zhǔn)的。
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。
每一個(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 是 ????
如果想要獲得一個(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ù)量并不總是與包含相同字符的NSString
的length
屬性相同。NSString
的length
屬性是利用 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
是空串,startIndex
和 endIndex
是相等的。
通過調(diào)用 String
的 index(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 ! ”
注意
你可以使用
startIndex
和endIndex
屬性或者index(before:)
、index(after:)
和index(_:offsetBy:)
方法在任意一個(gè)確認(rèn)的并遵循Collection
協(xié)議的類型里面,如上文所示是使用在String
中,你也可以使用在Array
、Dictionary
和Set
中。
調(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
、Dictionary
和Set
中。
當(dāng)你從字符串中獲取一個(gè)子字符串 —— 例如,使用下標(biāo)或者 prefix(_:)
之類的方法 —— 就可以得到一個(gè) SubString
的實(shí)例,而非另外一個(gè) String
。Swift 里的 SubString
絕大部分函數(shù)都跟 String
一樣,意味著你可以使用同樣的方式去操作 SubString
和 String
。然而,跟 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)存里保存字符集。而 String
和 SubString
的區(qū)別在于性能優(yōu)化上,SubString
可以重用原 String
的內(nèi)存空間,或者另一個(gè) SubString
的內(nèi)存空間(String
也有同樣的優(yōu)化,但如果兩個(gè) String
共享內(nèi)存的話,它們就會(huì)相等)。這一優(yōu)化意味著你在修改 String
和 SubString
之前都不需要消耗性能去復(fù)制內(nèi)存。就像前面說的那樣,SubString
不適合長期存儲(chǔ) —— 因?yàn)樗赜昧嗽?String
的內(nèi)存空間,原 String
的內(nèi)存空間必須保留直到它的 SubString
不再被使用為止。
上面的例子,greeting
是一個(gè) String
,意味著它在內(nèi)存里有一片空間保存字符集。而由于 beginning
是 greeting
的 SubString
,它重用了 greeting
的內(nèi)存空間。相反,newString
是一個(gè) String
—— 它是使用 SubString
創(chuàng)建的,擁有一片自己的內(nèi)存空間。下面的圖展示了他們之間的關(guān)系:
注意
String
和SubString
都遵循StringProtocol<//apple_ref/swift/intf/s:s14StringProtocolP>
協(xié)議,這意味著操作字符串的函數(shù)使用StringProtocol
會(huì)更加方便。你可以傳入String
或SubString
去調(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ì)描述在 字符串/字符相等。
當(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 兼容的方式訪問字符串的值:
utf8
屬性進(jìn)行訪問)utf16
屬性進(jìn)行訪問)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???"
你可以通過遍歷 String
的 utf8
屬性來訪問它的 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
值(68
、111
、103
)代表了字符 D
、o
和 g
,它們的 UTF-8 表示與 ASCII 表示相同。接下來的三個(gè) 10 進(jìn)制 codeUnit
值(226
、128
、188
)是 DOUBLE EXCLAMATION MARK
的3字節(jié) UTF-8 表示。最后的四個(gè) codeUnit
值(240
、159
、144
、182
)是 DOG FACE
的4字節(jié) UTF-8 表示。
你可以通過遍歷 String
的 utf16
屬性來訪問它的 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
、111
、103
)代表了字符 D
、o
和 g
,它們的 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
值(55357
和 56374
)是 DOG FACE
字符的 UTF-16 表示。第一個(gè)值為 U+D83D
(十進(jìn)制值為 55357
),第二個(gè)值為 U+DC36
(十進(jìn)制值為 56374
)。
你可以通過遍歷 String
值的 unicodeScalars
屬性來訪問它的 Unicode 標(biāo)量表示。其為 UnicodeScalarView
類型的屬性,UnicodeScalarView
是 UnicodeScalar
類型的值的集合。
每一個(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
屬性仍然代表字符 D
、o
和 g
。
第四個(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
// ?
// ??