??Swift 的String和Character類型提供了快速和兼容 Unicode 的方式供你的代碼使用。 字符串連接操作只需要簡單地通過+符號將兩個字符串相連即可。與 Swift 中其他值一樣,能否更改字符串的值,取決于其被定義為常量還是變量。每一個字符串都是由編碼無關(guān)的 Unicode 字符組成,并支持訪問字符的多種 Unicode 表示形式。
字符串字面量
字符串字面量可以用于為常量和變量提供初始值:
let someString = "Some string literal value"
注意someString常量通過字符串字面量進行初始化,Swift 會推斷該常量為String類型。
多行字符串字面量
如果你需要一個字符串是跨越多行的,那就使用多行字符串字面量 —— 由一對三個雙引號包裹著的具有固定順序的文本字符集(轉(zhuǎn)義符還是需要的):
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."
"""
可以用在行尾寫一個反斜杠(\)作為續(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."
"""
字符串字面量的特殊字符
字符串字面量可以包含以下特殊字符:
- 轉(zhuǎn)義字符
\0(空字符)、\\(反斜線)、\t(水平制表符)、\n(換行符)、\r(回車符)、\"(雙引號)、\'(單引號)。 - Unicode 標(biāo)量,寫成
\u{n}(u為小寫),其中n為任意一到八位十六進制數(shù)且可用的 Unicode 位碼。
下面的代碼為各種特殊字符的使用示例。 wiseWords常量包含了兩個雙引號。 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
由于多行字符串字面量使用了三個雙引號,而不是一個,所以你可以在多行字符串字面量里直接使用雙引號(")而不必加上轉(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"
字符串是值類型
Swift 的String類型是值類型。 如果您創(chuàng)建了一個新的字符串,那么當(dāng)其進行常量、變量賦值操作,或在函數(shù)/方法中傳遞時,會進行值拷貝。 任何情況下,都會對已有字符串值創(chuàng)建新副本,并對該新副本進行傳遞或賦值操作。
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) 中進行了詳細描述。
另外,通過標(biāo)明一個Character類型并用字符字面量進行賦值,可以建立一個獨立的字符常量或變量:
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!"
字符串插值
字符串插值是一種構(gòu)建新字符串的方式,可以在其中包含常量、變量、字面量和表達式。字符串字面量和多行字符串字面量都可以使用字符串插值。 您插入的字符串字面量的每一項都在以反斜線為前綴的圓括號中:
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的值也作為字符串中后面表達式的一部分。 該表達式計算Double(multiplier) * 2.5的值并將結(jié)果 (7.5) 插入到字符串中。 在這個例子中,表達式寫為\(Double(multiplier) * 2.5)并包含在字符串字面量中。
注意:
插值字符串中寫在括號中的表達式不能包含非轉(zhuǎn)義反斜杠 (\),并且不能包含回車或換行符。不過,插值字符串可以包含其他字面量。
Unicode
Unicode是一個國際標(biāo)準(zhǔn),用于文本的編碼和表示。 它使您可以用標(biāo)準(zhǔn)格式表示來自任意語言幾乎所有的字符,并能夠?qū)ξ谋疚募蚓W(wǎng)頁這樣的外部資源中的字符進行讀寫操作。 Swift 的String和Character類型是完全兼容 Unicode 標(biāo)準(zhǔn)的。
Unicode 標(biāo)量
Swift 的String類型是基于 Unicode 標(biāo)量 建立的。 Unicode 標(biāo)量是對應(yīng)字符或者修飾符的唯一的21位數(shù)字.
計算字符數(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"
另外需要注意的是通過
count屬性返回的字符數(shù)量并不總是與包含相同字符的NSString的length屬性相同。NSString的length屬性是利用 UTF-16 表示的十六位代碼單元數(shù)字,而不是 Unicode 可擴展的字符群集。
訪問和修改字符串
你可以通過字符串的屬性和方法來訪問和修改它,當(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是空串,startIndex和endIndex是相等的。
通過調(diào)用 String 的 index(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 ! "
注意:
您可以使用startIndex和endIndex屬性或者index(before:)、index(after:)和index(_:offsetBy:)方法在任意一個確認(rèn)的并遵循Collection協(xié)議的類型里面,如上文所示是使用在String中,您也可以使用在Array、Dictionary和Set中。
插入和刪除
調(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、Dictionary和Set中。
子字符串
當(dāng)你從字符串中獲取一個子字符串 —— 例如,使用下標(biāo)或者 prefix(_:) 之類的方法 —— 就可以得到一個 SubString 的實例,而非另外一個 String。Swift 里的 SubString 絕大部分函數(shù)都跟 String 一樣,意味著你可以使用同樣的方式去操作 SubString 和 String。然而,跟 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)存里保存字符集。而 String 和 SubString 的區(qū)別在于性能優(yōu)化上,SubString 可以重用原 String 的內(nèi)存空間,或者另一個 SubString 的內(nèi)存空間(String 也有同樣的優(yōu)化,但如果兩個 String 共享內(nèi)存的話,它們就會相等)。這一優(yōu)化意味著你在修改 String 和 SubString 之前都不需要消耗性能去復(fù)制內(nèi)存。就像前面說的那樣,SubString 不適合長期存儲 —— 因為它重用了原 String 的內(nèi)存空間,原 String 的內(nèi)存空間必須保留直到它的 SubString 不再被使用為止。
上面的例子,greeting 是一個 String,意味著它在內(nèi)存里有一片空間保存字符集。而由于 beginning 是 greeting 的 SubString,它重用了 greeting 的內(nèi)存空間。相反,newString 是一個 String —— 它是使用 SubString 創(chuàng)建的,擁有一片自己的內(nèi)存空間。
比較字符串
Swift 提供了三種方式來比較文本值:字符串字符相等、前綴相等和后綴相等。
字符串/字符相等
字符串/字符可以用等于操作符(==)和不等于操作符(!=),詳細描述在比較運算符:
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"
如果兩個字符串(或者兩個字符)的可擴展的字形群集是標(biāo)準(zhǔn)相等的,那就認(rèn)為它們是相等的。在這個情況下,即使可擴展的字形群集是有不同的 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(_:)方法都是在每個字符串中逐字符比較其可擴展的字符群集是否標(biāo)準(zhǔn)相等,詳細描述在字符串/字符相等。