# 字符串和字符
*字符串*是一系列字符的集合,例如 `"hello, world"`,`"albatross"`。Swift 的字符串通過 `String` 類型來表示。而 `String` 內容的訪問方式有多種,例如以 `Character` 值的集合。
Swift 的 `String` 和 `Character` 類型提供了一種快速且兼容 Unicode 的方式來處理代碼中的文本內容。創建和操作字符串的語法與 C 語言中字符串操作相似,輕量并且易讀。通過 `+` 符號就可以非常簡單的實現兩個字符串的拼接操作。與 Swift 中其他值一樣,能否更改字符串的值,取決于其被定義為常量還是變量。你可以在已有字符串中插入常量、變量、字面量和表達式從而形成更長的字符串,這一過程也被稱為字符串插值。尤其是在為顯示、存儲和打印創建自定義字符串值時,字符串插值操作尤其有用。
盡管語法簡易,但 Swift 中的 `String` 類型的實現卻很快速和現代化。每一個字符串都是由編碼無關的 Unicode 字符組成,并支持訪問字符的多種 Unicode 表示形式。
> 注意
>
> Swift 的 `String` 類型與 Foundation `NSString` 類進行了無縫橋接。Foundation 還對 `String` 進行擴展使其可以訪問 `NSString` 類型中定義的方法。這意味著調用那些 `NSString` 的方法,你無需進行任何類型轉換。
>
> 更多關于在 Foundation 和 Cocoa 中使用 `String` 的信息請查看 *[Bridging Between String and NSString](https://developer.apple.com/documentation/swift/string#2919514)*。
## 字符串字面量 {#string-literals}
你可以在代碼里使用一段預定義的字符串值作為字符串字面量。字符串字面量是由一對雙引號包裹著的具有固定順序的字符集。
字符串字面量可以用于為常量和變量提供初始值:
```swift
let someString = "Some string literal value"
```
注意,Swift 之所以推斷 `someString` 常量為字符串類型,是因為它使用了字面量方式進行初始化。
### 多行字符串字面量 {#multiline-string-literals}
如果你需要一個字符串是跨越多行的,那就使用多行字符串字面量 — 由一對三個雙引號包裹著的具有固定順序的文本字符集:
```swift
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."
"""
```
一個多行字符串字面量包含了所有的在開啟和關閉引號(`"""`)中的行。這個字符從開啟引號(`"""`)之后的第一行開始,到關閉引號(`"""`)之前為止。這就意味著字符串開啟引號之后(`"""`)或者結束引號(`"""`)之前都沒有換行符號。(譯者:下面兩個字符串其實是一樣的,雖然第二個使用了多行字符串的形式)
```swift
let singleLineString = "These are the same."
let multilineString = """
These are the same.
"""
```
如果你的代碼中,多行字符串字面量包含換行符的話,則多行字符串字面量中也會包含換行符。如果你想換行,以便加強代碼的可讀性,但是你又不想在你的多行字符串字面量中出現換行符的話,你可以用在行尾寫一個反斜杠(`\`)作為續行符。
```swift
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."
"""
```
為了讓一個多行字符串字面量開始和結束于換行符,請將換行寫在第一行和最后一行,例如:
```swift
let lineBreaks = """
This string starts with a line break.
It also ends with a line break.
"""
```
一個多行字符串字面量能夠縮進來匹配周圍的代碼。關閉引號(`"""`)之前的空白字符串告訴 Swift 編譯器其他各行多少空白字符串需要忽略。然而,如果你在某行的前面寫的空白字符串超出了關閉引號(`"""`)之前的空白字符串,則超出部分將被包含在多行字符串字面量中。

在上面的例子中,盡管整個多行字符串字面量都是縮進的(源代碼縮進),第一行和最后一行沒有以空白字符串開始(實際的變量值)。中間一行的縮進用空白字符串(源代碼縮進)比關閉引號(`"""`)之前的空白字符串多,所以,它的行首將有4個空格。
### 字符串字面量的特殊字符 {#special-characters-in-string-literals}
字符串字面量可以包含以下特殊字符:
* 轉義字符 `\0`(空字符)、`\\`(反斜線)、`\t`(水平制表符)、`\n`(換行符)、`\r`(回車符)、`\"`(雙引號)、`\'`(單引號)。
* Unicode 標量,寫成 `\u{n}`(u 為小寫),其中 `n` 為任意一到八位十六進制數且可用的 Unicode 位碼。
下面的代碼為各種特殊字符的使用示例。
`wiseWords` 常量包含了兩個雙引號。
`dollarSign`、`blackHeart` 和 `sparklingHeart` 常量演示了三種不同格式的 Unicode 標量:
```swift
let wiseWords = "\"Imagination is more important than knowledge\" - Einstein"
// "Imageination is more important than knowledge" - Enistein
let dollarSign = "\u{24}" // $,Unicode 標量 U+0024
let blackHeart = "\u{2665}" // ?,Unicode 標量 U+2665
let sparklingHeart = "\u{1F496}" // ??,Unicode 標量 U+1F496
```
由于多行字符串字面量使用了三個雙引號,而不是一個,所以你可以在多行字符串字面量里直接使用雙引號(`"`)而不必加上轉義符(`\`)。要在多行字符串字面量中使用 `"""` 的話,就需要使用至少一個轉義符(`\`):
```swift
let threeDoubleQuotes = """
Escaping the first quote \"""
Escaping all three quotes \"\"\"
"""
```
### 擴展字符串分隔符 {#extended-string-delimiters}
您可以將字符串文字放在擴展分隔符中,這樣字符串中的特殊字符將會被直接包含而非轉義后的效果。將字符串放在引號(`"`)中并用數字符號(`#`)括起來。例如,打印字符串文字 `#"Line 1 \nLine 2"#` 會打印換行符轉義序列(`\n`)而不是給文字換行。
如果需要字符串文字中字符的特殊效果,請匹配轉義字符(`\`)后面添加與起始位置個數相匹配的 `#` 符。 例如,如果您的字符串是 `#"Line 1 \nLine 2"#` 并且您想要換行,則可以使用 `#"Line 1 \#nLine 2"#` 來代替。 同樣,`###"Line1 \###nLine2"###` 也可以實現換行效果。
擴展分隔符創建的字符串文字也可以是多行字符串文字。 您可以使用擴展分隔符在多行字符串中包含文本 `"""`,覆蓋原有的結束文字的默認行為。例如:
```swift
let threeMoreDoubleQuotationMarks = #"""
Here are three more double quotes: """
"""#
```
## 初始化空字符串 {#initializing-an-empty-string}
要創建一個空字符串作為初始值,可以將空的字符串字面量賦值給變量,也可以初始化一個新的 `String` 實例:
```swift
var emptyString = "" // 空字符串字面量
var anotherEmptyString = String() // 初始化方法
// 兩個字符串均為空并等價。
```
你可以通過檢查 `Bool` 類型的 `isEmpty` 屬性來判斷該字符串是否為空:
```swift
if emptyString.isEmpty {
print("Nothing to see here")
}
// 打印輸出:“Nothing to see here”
```
## 字符串可變性 {#string-mutability}
你可以通過將一個特定字符串分配給一個變量來對其進行修改,或者分配給一個常量來保證其不會被修改:
```swift
var variableString = "Horse"
variableString += " and carriage"
// variableString 現在為 "Horse and carriage"
let constantString = "Highlander"
constantString += " and another Highlander"
// 這會報告一個編譯錯誤(compile-time error) - 常量字符串不可以被修改。
```
> 注意
>
> 在 Objective-C 和 Cocoa 中,需要通過選擇兩個不同的類(`NSString` 和 `NSMutableString`)來指定字符串是否可以被修改。
## 字符串是值類型 {#strings-are-value-types}
在 Swift 中 `String` 類型是*值類型*。如果你創建了一個新的字符串,那么當其進行常量、變量賦值操作,或在函數/方法中傳遞時,會進行值拷貝。在前述任一情況下,都會對已有字符串值創建新副本,并對該新副本而非原始字符串進行傳遞或賦值操作。值類型在 [結構體和枚舉是值類型](./09_Structures_And_Classes.md#structures-and-enumerations-are-value-types) 中進行了詳細描述。
Swift 默認拷貝字符串的行為保證了在函數/方法向你傳遞的字符串所屬權屬于你,無論該值來自于哪里。你可以確信傳遞的字符串不會被修改,除非你自己去修改它。
在實際編譯時,Swift 編譯器會優化字符串的使用,使實際的復制只發生在絕對必要的情況下,這意味著你將字符串作為值類型的同時可以獲得極高的性能。
## 使用字符 {#working-with-characters}
你可通過 `for-in` 循環來遍歷字符串,獲取字符串中每一個字符的值:
```swift
for character in "Dog!??" {
print(character)
}
// D
// o
// g
// !
// ??
```
`for-in` 循環在 [For 循環](./05_Control_Flow.md#for-loops) 中進行了詳細描述。
另外,通過標明一個 `Character` 類型并用字符字面量進行賦值,可以建立一個獨立的字符常量或變量:
```swift
let exclamationMark: Character = "!"
```
字符串可以通過傳遞一個值類型為 `Character` 的數組作為自變量來初始化:
```swift
let catCharacters: [Character] = ["C", "a", "t", "!", "??"]
let catString = String(catCharacters)
print(catString)
// 打印輸出:“Cat!??”
```
## 連接字符串和字符 {#concatenating-strings-and-characters}
字符串可以通過加法運算符(`+`)相加在一起(或稱“連接”)創建一個新的字符串:
```swift
let string1 = "hello"
let string2 = " there"
var welcome = string1 + string2
// welcome 現在等于 "hello there"
```
你也可以通過加法賦值運算符(`+=`)將一個字符串添加到一個已經存在字符串變量上:
```swift
var instruction = "look over"
instruction += string2
// instruction 現在等于 "look over there"
```
你可以用 `append()` 方法將一個字符附加到一個字符串變量的尾部:
```swift
let exclamationMark: Character = "!"
welcome.append(exclamationMark)
// welcome 現在等于 "hello there!"
```
> 注意
>
> 你不能將一個字符串或者字符添加到一個已經存在的字符變量上,因為字符變量只能包含一個字符。
如果你需要使用多行字符串字面量來拼接字符串,并且你需要字符串每一行都以換行符結尾,包括最后一行:
```swift
let badStart = """
one
two
"""
let end = """
three
"""
print(badStart + end)
// 打印兩行:
// one
// twothree
let goodStart = """
one
two
"""
print(goodStart + end)
// 打印三行:
// one
// two
// three
```
上面的代碼,把 `badStart` 和 `end` 拼接起來的字符串非我們想要的結果。因為 `badStart` 最后一行沒有換行符,它與 `end` 的第一行結合到了一起。相反的,`goodStart` 的每一行都以換行符結尾,所以它與 `end` 拼接的字符串總共有三行,正如我們期望的那樣。
## 字符串插值 {#string-interpolation}
*字符串插值*是一種構建新字符串的方式,可以在其中包含常量、變量、字面量和表達式。**字符串字面量**和**多行字符串字面量**都可以使用字符串插值。你插入的字符串字面量的每一項都在以反斜線為前綴的圓括號中:
```swift
let multiplier = 3
let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
// message 是 "3 times 2.5 is 7.5"
```
在上面的例子中,`multiplier` 作為 `\(multiplier)` 被插入到一個字符串常量量中。當創建字符串執行插值計算時此占位符會被替換為 `multiplier` 實際的值。
`multiplier` 的值也作為字符串中后面表達式的一部分。該表達式計算 `Double(multiplier) * 2.5` 的值并將結果(`7.5`)插入到字符串中。在這個例子中,表達式寫為 `\(Double(multiplier) * 2.5)` 并包含在字符串字面量中。
你可以使用擴展字符串分隔符創建字符串,來包含不想作為字符串插值處理的字符。例如:
```swift
print(#"Write an interpolated string in Swift using \(multiplier)."#)
// 打印 "Write an interpolated string in Swift using \(multiplier)."
```
如果要在使用擴展字符串分隔符的字符串中使用字符串插值,需要在反斜杠后面添加與開頭和結尾數量相同擴展字符串分隔符。例如:
```swift
print(#"6 times 7 is \#(6 * 7)."#)
// 打印 "6 times 7 is 42."
```
> 注意
>
> 插值字符串中寫在括號中的表達式不能包含非轉義反斜杠(`\`),并且不能包含回車或換行符。不過,插值字符串可以包含其他字面量。
## Unicode {#unicode}
*Unicode*是一個用于在不同書寫系統中對文本進行編碼、表示和處理的國際標準。它使你可以用標準格式表示來自任意語言幾乎所有的字符,并能夠對文本文件或網頁這樣的外部資源中的字符進行讀寫操作。Swift 的 `String` 和 `Character` 類型是完全兼容 Unicode 標準的。
### Unicode 標量 {#unicode-scalars}
Swift 的 `String` 類型是基于 *Unicode 標量* 建立的。Unicode 標量是對應字符或者修飾符的唯一的 21 位數字,例如 `U+0061` 表示小寫的拉丁字母(`LATIN SMALL LETTER A`)("`a`"),`U+1F425` 表示小雞表情(`FRONT-FACING BABY CHICK`)("`??`")。
請注意,并非所有 21 位 Unicode 標量值都分配給字符,某些標量被保留用于將來分配或用于 UTF-16 編碼。已分配的標量值通常也有一個名稱,例如上面示例中的 LATIN SMALL LETTER A 和 FRONT-FACING BABY CHICK。
### 可擴展的字形群集 {#extended-grapheme-clusters}
每一個 Swift 的 `Character` 類型代表一個*可擴展的字形群*。而一個可擴展的字形群構成了人類可讀的單個字符,它由一個或多個(當組合時) Unicode 標量的序列組成。
舉個例子,字母 `é` 可以用單一的 Unicode 標量 `é`(`LATIN SMALL LETTER E WITH ACUTE`, 或者 `U+00E9`)來表示。然而一個標準的字母 `e`(`LATIN SMALL LETTER E` 或者 `U+0065`) 加上一個急促重音(`COMBINING ACTUE ACCENT`)的標量(`U+0301`),這樣一對標量就表示了同樣的字母 `é`。
這個急促重音的標量形象的將 `e` 轉換成了 `é`。
在這兩種情況中,字母 `é` 代表了一個單一的 Swift 的 `Character` 值,同時代表了一個可擴展的字形群。在第一種情況,這個字形群包含一個單一標量;而在第二種情況,它是包含兩個標量的字形群:
```swift
let eAcute: Character = "\u{E9}" // é
let combinedEAcute: Character = "\u{65}\u{301}" // e 后面加上 ?
// eAcute 是 é, combinedEAcute 是 é
```
可擴展的字形集是一個將許多復雜的腳本字符表示為單個字符值的靈活方式。例如,來自朝鮮語字母表的韓語音節能表示為組合或分解的有序排列。在 Swift 都會表示為同一個單一的 `Character` 值:
```swift
let precomposed: Character = "\u{D55C}" // ?
let decomposed: Character = "\u{1112}\u{1161}\u{11AB}" // ?, ?, ?
// precomposed 是 ?, decomposed 是 ?
```
可拓展的字符群集可以使包圍記號(例如 `COMBINING ENCLOSING CIRCLE` 或者 `U+20DD`)的標量包圍其他 Unicode 標量,作為一個單一的 `Character` 值:
```swift
let enclosedEAcute: Character = "\u{E9}\u{20DD}"
// enclosedEAcute 是 é?
```
地域性指示符號的 Unicode 標量可以組合成一個單一的 `Character` 值,例如 `REGIONAL INDICATOR SYMBOL LETTER U`(`U+1F1FA`)和 `REGIONAL INDICATOR SYMBOL LETTER S`(`U+1F1F8`):
```swift
let regionalIndicatorForUS: Character = "\u{1F1FA}\u{1F1F8}"
// regionalIndicatorForUS 是 ????
```
## 計算字符數量 {#counting-characters}
如果想要獲得一個字符串中 `Character` 值的數量,可以使用 `count` 屬性:
```swift
let unusualMenagerie = "Koala ??, Snail ??, Penguin ??, Dromedary ??"
print("unusualMenagerie has \(unusualMenagerie.count) characters")
// 打印輸出“unusualMenagerie has 40 characters”
```
注意在 Swift 中,使用可拓展的字符群集作為 `Character` 值來連接或改變字符串時,并不一定會更改字符串的字符數量。
例如,如果你用四個字符的單詞 `cafe` 初始化一個新的字符串,然后添加一個 `COMBINING ACTUE ACCENT`(`U+0301`)作為字符串的結尾。最終這個字符串的字符數量仍然是 `4`,因為第四個字符是 `é`,而不是 `e`:
```swift
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”
```
> 注意
>
> 可擴展的字形群可以由多個 Unicode 標量組成。這意味著不同的字符以及相同字符的不同表示方式可能需要不同數量的內存空間來存儲。所以 Swift 中的字符在一個字符串中并不一定占用相同的內存空間數量。因此在沒有獲取字符串的可擴展的字符群的范圍時候,就不能計算出字符串的字符數量。如果你正在處理一個長字符串,需要注意 `count` 屬性必須遍歷全部的 Unicode 標量,來確定字符串的字符數量。
>
> 另外需要注意的是通過 `count` 屬性返回的字符數量并不總是與包含相同字符的 `NSString` 的 `length` 屬性相同。`NSString` 的 `length` 屬性是利用 UTF-16 表示的十六位代碼單元數字,而不是 Unicode 可擴展的字符群集。
## 訪問和修改字符串 {#accessing-and-modifying-a-string}
你可以通過字符串的屬性和方法來訪問和修改它,當然也可以用下標語法完成。
### 字符串索引 {#string-indices}
每一個 `String` 值都有一個關聯的索引(*index*)類型,`String.Index`,它對應著字符串中的每一個 `Character` 的位置。
前面提到,不同的字符可能會占用不同數量的內存空間,所以要知道 `Character` 的確定位置,就必須從 `String` 開頭遍歷每一個 Unicode 標量直到結尾。因此,Swift 的字符串不能用整數(integer)做索引。
使用 `startIndex` 屬性可以獲取一個 `String` 的第一個 `Character` 的索引。使用 `endIndex` 屬性可以獲取最后一個 `Character` 的后一個位置的索引。因此,`endIndex` 屬性不能作為一個字符串的有效下標。如果 `String` 是空串,`startIndex` 和 `endIndex` 是相等的。
通過調用 `String` 的 `index(before:)` 或 `index(after:)` 方法,可以立即得到前面或后面的一個索引。你還可以通過調用 `index(_:offsetBy:)` 方法來獲取對應偏移量的索引,這種方式可以避免多次調用 `index(before:)` 或 `index(after:)` 方法。
你可以使用下標語法來訪問 `String` 特定索引的 `Character`。
```swift
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
```
試圖獲取越界索引對應的 `Character`,將引發一個運行時錯誤。
```swift
greeting[greeting.endIndex] // error
greeting.index(after: greeting.endIndex) // error
```
使用 `indices` 屬性會創建一個包含全部索引的范圍(`Range`),用來在一個字符串中訪問單個字符。
```swift
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:)` 方法在任意一個確認的并遵循 `Collection` 協議的類型里面,如上文所示是使用在 `String` 中,你也可以使用在 `Array`、`Dictionary` 和 `Set` 中。
### 插入和刪除 {#inserting-and-removing}
調用 `insert(_:at:)` 方法可以在一個字符串的指定索引插入一個字符,調用 `insert(contentsOf:at:)` 方法可以在一個字符串的指定索引插入一個段字符串。
```swift
var welcome = "hello"
welcome.insert("!", at: welcome.endIndex)
// welcome 變量現在等于 "hello!"
welcome.insert(contentsOf:" there", at: welcome.index(before: welcome.endIndex))
// welcome 變量現在等于 "hello there!"
```
調用 `remove(at:)` 方法可以在一個字符串的指定索引刪除一個字符,調用 `removeSubrange(_:)` 方法可以在一個字符串的指定索引刪除一個子字符串。
```swift
welcome.remove(at: welcome.index(before: welcome.endIndex))
// welcome 現在等于 "hello there"
let range = welcome.index(welcome.endIndex, offsetBy: -6)..<welcome.endIndex
welcome.removeSubrange(range)
// welcome 現在等于 "hello"
```
> 注意
>
> 你可以使用 `insert(_:at:)`、`insert(contentsOf:at:)`、`remove(at:)` 和 `removeSubrange(_:)` 方法在任意一個確認的并遵循 `RangeReplaceableCollection` 協議的類型里面,如上文所示是使用在 `String` 中,你也可以使用在 `Array`、`Dictionary` 和 `Set` 中。
## 子字符串 {#substrings}
當你從字符串中獲取一個子字符串 —— 例如,使用下標或者 `prefix(_:)` 之類的方法 —— 就可以得到一個 `Substring` 的實例,而非另外一個 `String`。Swift 里的 `Substring` 絕大部分函數都跟 `String` 一樣,意味著你可以使用同樣的方式去操作 `Substring` 和 `String`。然而,跟 `String` 不同的是,你只有在短時間內需要操作字符串時,才會使用 `Substring`。當你需要長時間保存結果時,就把 `Substring` 轉化為 `String` 的實例:
```swift
let greeting = "Hello, world!"
let index = greeting.firstIndex(of: ",") ?? greeting.endIndex
let beginning = greeting[..<index]
// beginning 的值為 "Hello"
// 把結果轉化為 String 以便長期存儲。
let newString = String(beginning)
```
就像 `String`,每一個 `Substring` 都會在內存里保存字符集。而 `String` 和 `Substring` 的區別在于性能優化上,`Substring` 可以重用原 `String` 的內存空間,或者另一個 `Substring` 的內存空間(`String` 也有同樣的優化,但如果兩個 `String` 共享內存的話,它們就會相等)。這一優化意味著你在修改 `String` 和 `Substring` 之前都不需要消耗性能去復制內存。就像前面說的那樣,`Substring` 不適合長期存儲 —— 因為它重用了原 `String` 的內存空間,原 `String` 的內存空間必須保留直到它的 `Substring` 不再被使用為止。
上面的例子,`greeting` 是一個 `String`,意味著它在內存里有一片空間保存字符集。而由于 `beginning` 是 `greeting` 的 `Substring`,它重用了 `greeting` 的內存空間。相反,`newString` 是一個 `String` —— 它是使用 `Substring` 創建的,擁有一片自己的內存空間。下面的圖展示了他們之間的關系:

> 注意
>
> `String` 和 `Substring` 都遵循 [`StringProtocol`](https://developer.apple.com/documentation/swift/stringprotocol) 協議,這意味著操作字符串的函數使用 `StringProtocol` 會更加方便。你可以傳入 `String` 或 `Substring` 去調用函數。
## 比較字符串 {#comparing-strings}
Swift 提供了三種方式來比較文本值:字符串字符相等、前綴相等和后綴相等。
### 字符串/字符相等 {#string-and-character-equality}
字符串/字符可以用等于操作符(`==`)和不等于操作符(`!=`),詳細描述在 [比較運算符](./02_Basic_Operators.md#comparison-operators):
```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”
```
如果兩個字符串(或者兩個字符)的可擴展的字形群集是標準相等,那就認為它們是相等的。只要可擴展的字形群集有同樣的語言意義和外觀則認為它們標準相等,即使它們是由不同的 Unicode 標量構成。
例如,`LATIN SMALL LETTER E WITH ACUTE`(`U+00E9`)就是標準相等于 `LATIN SMALL LETTER E`(`U+0065`)后面加上 `COMBINING ACUTE ACCENT`(`U+0301`)。這兩個字符群集都是表示字符 `é` 的有效方式,所以它們被認為是標準相等的:
```swift
// "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`)。兩個字符看著是一樣的,但卻有不同的語言意義:
```swift
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 中,字符串和字符并不區分地域(not locale-sensitive)。
### 前綴/后綴相等 {#prefix-and-suffix-equality}
通過調用字符串的 `hasPrefix(_:)`/`hasSuffix(_:)` 方法來檢查字符串是否擁有特定前綴/后綴,兩個方法均接收一個 `String` 類型的參數,并返回一個布爾值。
下面的例子以一個字符串數組表示莎士比亞話劇《羅密歐與朱麗葉》中前兩場的場景位置:
```swift
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"
]
```
你可以調用 `hasPrefix(_:)` 方法來計算話劇中第一幕的場景數:
```swift
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(_:)` 方法來計算發生在不同地方的場景數:
```swift
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(_:)` 方法都是在每個字符串中逐字符比較其可擴展的字符群集是否標準相等,詳細描述在 [字符串/字符相等](#string-and-character-equality)。
## 字符串的 Unicode 表示形式 {#unicode-representations-of-strings}
當一個 Unicode 字符串被寫進文本文件或者其他儲存時,字符串中的 Unicode 標量會用 Unicode 定義的幾種 `編碼格式`(encoding forms)編碼。每一個字符串中的小塊編碼都被稱 `代碼單元`(code units)。這些包括 UTF-8 編碼格式(編碼字符串為 8 位的代碼單元), UTF-16 編碼格式(編碼字符串位 16 位的代碼單元),以及 UTF-32 編碼格式(編碼字符串32位的代碼單元)。
Swift 提供了幾種不同的方式來訪問字符串的 Unicode 表示形式。你可以利用 `for-in` 來對字符串進行遍歷,從而以 Unicode 可擴展的字符群集的方式訪問每一個 `Character` 值。該過程在 [使用字符](#working-with-characters) 中進行了描述。
另外,能夠以其他三種 Unicode 兼容的方式訪問字符串的值:
* UTF-8 代碼單元集合(利用字符串的 `utf8` 屬性進行訪問)
* UTF-16 代碼單元集合(利用字符串的 `utf16` 屬性進行訪問)
* 21 位的 Unicode 標量值集合,也就是字符串的 UTF-32 編碼格式(利用字符串的 `unicodeScalars` 屬性進行訪問)
下面由 `D`,`o`,`g`,`?`(`DOUBLE EXCLAMATION MARK`, Unicode 標量 `U+203C`)和 `??`(`DOG FACE`,Unicode 標量為 `U+1F436`)組成的字符串中的每一個字符代表著一種不同的表示:
```swift
let dogString = "Dog???"
```
### UTF-8 表示 {#UTF-8-representation}
你可以通過遍歷 `String` 的 `utf8` 屬性來訪問它的 `UTF-8` 表示。其為 `String.UTF8View` 類型的屬性,`UTF8View` 是無符號 8 位(`UInt8`)值的集合,每一個 `UInt8` 值都是一個字符的 UTF-8 表示:
<table style='text-align:center'>
<tr height="77">
<td>Character</td>
<td>D<br>U+0044</td>
<td>o<br>U+006F</td>
<td>g<br>U+0067</td>
<td colspan="3">?<br>U+203C</td>
<td colspan="4">??<br>U+1F436</td>
</tr>
<tr height="77">
<td height="77">UTF-8<br>Code Unit</td>
<td>68</td>
<td>111</td>
<td>103</td>
<td>226</td>
<td>128</td>
<td>188</td>
<td>240</td>
<td>159</td>
<td>144</td>
<td>182</td>
</tr>
<tr>
<td height="77">Position</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
</tr>
</table>
```swift
for codeUnit in dogString.utf8 {
print("\(codeUnit) ", terminator: "")
}
print("")
// 68 111 103 226 128 188 240 159 144 182
```
上面的例子中,前三個 10 進制 `codeUnit` 值(`68`、`111`、`103`)代表了字符 `D`、`o` 和 `g`,它們的 UTF-8 表示與 ASCII 表示相同。接下來的三個 10 進制 `codeUnit` 值(`226`、`128`、`188`)是 `DOUBLE EXCLAMATION MARK` 的3字節 UTF-8 表示。最后的四個 `codeUnit` 值(`240`、`159`、`144`、`182`)是 `DOG FACE` 的4字節 UTF-8 表示。
### UTF-16 表示 {#UTF-16-representation}
你可以通過遍歷 `String` 的 `utf16` 屬性來訪問它的 `UTF-16` 表示。其為 `String.UTF16View` 類型的屬性,`UTF16View` 是無符號16位(`UInt16`)值的集合,每一個 `UInt16` 都是一個字符的 UTF-16 表示:
<table style='text-align:center'>
<tr height="77">
<td>Character</td>
<td>D<br>U+0044</td>
<td>o<br>U+006F</td>
<td>g<br>U+0067</td>
<td>?<br>U+203C</td>
<td colspan="2">??<br>U+1F436</td>
</tr>
<tr height="77">
<td height="77">UTF-16<br>Code Unit</td>
<td>68</td>
<td>111</td>
<td>103</td>
<td>8252</td>
<td>55357</td>
<td>56374</td>
</tr>
<tr>
<td height="77">Position</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
</tr>
</table>
```swift
for codeUnit in dogString.utf16 {
print("\(codeUnit) ", terminator: "")
}
print("")
// 68 111 103 8252 55357 56374
```
同樣,前三個 `codeUnit` 值(`68`、`111`、`103`)代表了字符 `D`、`o` 和 `g`,它們的 UTF-16 代碼單元和 UTF-8 完全相同(因為這些 Unicode 標量表示 ASCII 字符)。
第四個 `codeUnit` 值(`8252`)是一個等于十六進制 `203C` 的的十進制值。這個代表了 `DOUBLE EXCLAMATION MARK` 字符的 Unicode 標量值 `U+203C`。這個字符在 UTF-16 中可以用一個代碼單元表示。
第五和第六個 `codeUnit` 值(`55357` 和 `56374`)是 `DOG FACE` 字符的 UTF-16 表示。第一個值為 `U+D83D`(十進制值為 `55357`),第二個值為 `U+DC36`(十進制值為 `56374`)。
### Unicode 標量表示 {#unicode-scalars-representation}
你可以通過遍歷 `String` 值的 `unicodeScalars` 屬性來訪問它的 Unicode 標量表示。其為 `UnicodeScalarView` 類型的屬性,`UnicodeScalarView` 是 `UnicodeScalar` 類型的值的集合。
每一個 `UnicodeScalar` 擁有一個 `value` 屬性,可以返回對應的 21 位數值,用 `UInt32` 來表示:
<table style='text-align:center'>
<tr height="77">
<td>Character</td>
<td>D<br>U+0044</td>
<td>o<br>U+006F</td>
<td>g<br>U+0067</td>
<td>?<br>U+203C</td>
<td>??<br>U+1F436</td>
</tr>
<tr height="77">
<td height="77">Unicode Scalar<br>Code Unit</td>
<td>68</td>
<td>111</td>
<td>103</td>
<td>8252</td>
<td>128054</td>
</tr>
<tr>
<td height="77">Position</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
</tr>
</table>
```swift
for scalar in dogString.unicodeScalars {
print("\(scalar.value) ", terminator: "")
}
print("")
// 68 111 103 8252 128054
```
前三個 `UnicodeScalar` 值(`68`、`111`、`103`)的 `value` 屬性仍然代表字符 `D`、`o` 和 `g`。
第四個 `codeUnit` 值(`8252`)仍然是一個等于十六進制 `203C` 的十進制值。這個代表了 `DOUBLE EXCLAMATION MARK` 字符的 Unicode 標量 `U+203C`。
第五個 `UnicodeScalar` 值的 `value` 屬性,`128054`,是一個十六進制 `1F436` 的十進制表示。其等同于 `DOG FACE` 的 Unicode 標量 `U+1F436`。
作為查詢它們的 `value` 屬性的一種替代方法,每個 `UnicodeScalar` 值也可以用來構建一個新的 `String` 值,比如在字符串插值中使用:
```swift
for scalar in dogString.unicodeScalars {
print("\(scalar) ")
}
// D
// o
// g
// ?
// ??
```
- 1.關于 Swift
- 2.Swift 初見
- 2-1基礎部分
- 2-2基本運算符
- 2-3字符串和字符
- 2-4集合類型
- 2-5控制流
- 2-6函數
- 2-7閉包
- 2-8枚舉
- 2-9類和結構體
- 2-10屬性
- 2-11方法
- 2-12下標
- 2-13繼承
- 2-14構造過程
- 2-15析構過程
- 2-16可選鏈
- 2-17錯誤處理
- 2-18類型轉換
- 2-19嵌套類型
- 2-20擴展
- 2-21協議
- 2-22泛型
- 2-23不透明類型
- 2-24自動引用計數
- 2-25內存安全
- 2-26訪問控制
- 2-27高級運算符
- 3-1關于語言參考
- 3-2詞法結構
- 3-3類型
- 3-4表達式
- 3-5語句
- 3-6聲明
- 3-7特性
- 3-8模式
- 3-9泛型參數
- 4語法總結