Swift 字符串和字符

字符串是一系列字符,例如"“你好,世界"hello, world"或"albatross"。Swift字符串由String類型表示。可以通過各種方式訪問字符串的內(nèi)容,包括作為字符值的集合。

Swift的String和Character類型提供了一種快速、兼容Unicode的方法來處理代碼中的文本。字符串創(chuàng)建和操作的語法,字符串文字語法類似于C,是輕量級(jí)和易讀性的。字符串連接就像用+運(yùn)算符組合兩個(gè)字符串一樣簡(jiǎn)單,字符串可變性是通過在常量或變量之間選擇來管理的,就像Swift中的任何其他值一樣。還可以使用字符串將常量、變量、文本和表達(dá)式插入較長(zhǎng)的字符串中,這一過程稱為字符串插值。這使得為顯示、存儲(chǔ)和打印來創(chuàng)建自定義字符串值變得容易。

盡管語法如此簡(jiǎn)單,但Swift的String類型是一種快速、現(xiàn)代的字符串實(shí)現(xiàn)。每個(gè)字符串都由編碼獨(dú)立的Unicode字符組成,并支持在各種Unicode表示中訪問這些字符。

注意
Swift的String類型與Foundation中的NSString類相關(guān)聯(lián)。Foundation還擴(kuò)展了String以公開NSString定義的方法。這意味著,如果導(dǎo)入Foundation,則可以訪問字符串上的那些NSString方法,而無需強(qiáng)制轉(zhuǎn)換。
有關(guān)在Foundation和Cocoa中使用String的更多信息,請(qǐng)參閱String和NSString之間的橋接。

字符串文字

可以將預(yù)定義的字符串值作為字符串文字包含在代碼中。字符串文字是由雙引號(hào)(")包圍的字符序列。
使用字符串文字作為常量或變量的初始值:

let someString = "Some string literal value"

請(qǐng)注意,Swift會(huì)為someString常量推斷一種String類型,因?yàn)樗怯米址谋局党跏蓟摹?/p>

多行字符串文字

如果需要跨多行的字符串,請(qǐng)使用多行字符串文字—由三個(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è)多行字符串文字包括它的開始引號(hào)和結(jié)束引號(hào)之間的所有行。字符串開始于左引號(hào)(""")后的第一行,結(jié)束于右引號(hào)前的一行,這意味著下面的字符串都不以換行符開頭或結(jié)尾:

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

當(dāng)源代碼在多行字符串文字中包含換行符時(shí),該換行符也會(huì)出現(xiàn)在字符串的值中。如果要使用換行符使源代碼更易于閱讀,但又不希望換行符成為字符串值的一部分,請(qǐng)?jiān)谶@些行的末尾寫一個(gè)反斜杠(\):

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é)尾的多行字符串文字,請(qǐng)將空行作為第一行或最后一行。例如:

let lineBreaks = """

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

"""

多行字符串可以縮進(jìn)以匹配周圍的代碼。右引號(hào) (""")前的空格告訴Swift,在所有其他行之前忽略哪些空格。但是,如果我們?cè)谝恍械拈_頭加上右引號(hào)之前的空格,則會(huì)包含該空格。


截屏2021-02-03 下午5.14.47.png

在上面的示例中,即使整個(gè)多行字符串文本縮進(jìn),字符串中的第一行和最后一行也不會(huì)以任何空格開頭。中間的行比右引號(hào)有更多空格的縮進(jìn),所以它從額外的四個(gè)空格縮進(jìn)開始。

字符串文字中的特殊字符

字符串文字可以包括以下特殊字符:

  • 轉(zhuǎn)義的特殊字符\0(空字符)、\\(反斜杠)、\t(水平制表符)、\n(換行符)、\r(回車符)、"(雙引號(hào))和'(單引號(hào))
  • 一個(gè)任意的Unicode標(biāo)量值,寫為\u{n},其中n是一個(gè)1-8位的十六進(jìn)制數(shù)(Unicode在下面的Unicode中討論)

下面的代碼顯示了這些特殊字符中的四個(gè)示例。wiseWords常量包含兩個(gè)轉(zhuǎn)義雙引號(hào)。dollarSign、blackHeart和SparkingHeart常量演示了Unicode標(biāo)量格式:

let wiseWords = "\"Imagination is more important than knowledge\" - Einstein"
// "Imagination is more important than knowledge" - Einstein
let dollarSign = "\u{24}"        // $,  Unicode scalar U+0024
let blackHeart = "\u{2665}"      // ?,  Unicode scalar U+2665
let sparklingHeart = "\u{1F496}" // ??, Unicode scalar U+1F496

由于多行字符串文字使用三個(gè)雙引號(hào)而不是一個(gè)雙引號(hào),因此可以在多行字符串文字中包含雙引號(hào)("),而不必轉(zhuǎn)義它。若要在多行字符串中包含文本""",請(qǐng)至少轉(zhuǎn)義一個(gè)引號(hào)。例如:

let threeDoubleQuotationMarks = """
Escaping the first quotation mark \"""
Escaping all three quotation marks \"\"\"
"""

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

可以在擴(kuò)展分隔符中放置字符串文字,以便在字符串中包含特殊字符,而不調(diào)用它們的效果。將字符串放在引號(hào)(“)內(nèi),并用數(shù)字符號(hào)(#)圍繞。例如,打印字符串文字#"Line 1\nLine 2"#打印換行轉(zhuǎn)義序列(\n),而不是跨兩行打印字符串。

如果需要字符串文字中字符的特殊效果,請(qǐng)匹配轉(zhuǎn)義符(\)后面字符串中的數(shù)字符號(hào)數(shù)。例如,如果字符串是#"Line 1\nLine 2"#,并且要換行,則可以改用#"Line 1\#nLine 2"#。類似地,###"Line1\###nLine2"###也會(huì)換行。

使用擴(kuò)展分隔符創(chuàng)建的字符串文字也可以是多行字符串文字??梢允褂脭U(kuò)展分隔符將文本"""包含在多行字符串中,從而覆蓋以文本結(jié)尾的默認(rèn)行為。例如:

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

初始化空字符串

創(chuàng)建空字符串值來作為生成較長(zhǎng)字符串的起點(diǎn),請(qǐng)將空字符串文字賦給變量,或使用初始化方法初始化新字符串實(shí)例:

var emptyString = ""               // empty string literal
var anotherEmptyString = String()  // initializer syntax
// these two strings are both empty, and are equivalent to each other

通過檢查字符串的Boolean isEmpty屬性來確定字符串值是否為空:

if emptyString.isEmpty {
    print("Nothing to see here")
}
// Prints "Nothing to see here"

字符串易變性

我們可以通過將特定字符串賦給變量(在這種情況下可以修改)或常量(在這種情況下不能修改)來表示是否可以修改(或改變)一個(gè)特定的字符串:

var variableString = "Horse"
variableString += " and carriage"
// variableString is now "Horse and carriage"

let constantString = "Highlander"
constantString += " and another Highlander"
// this reports a compile-time error - a constant string cannot be modified

注意
這種方法不同于Objective-C和Cocoa中的字符串修改,因?yàn)樗鼈兪窃趦蓚€(gè)類(NSString和NSMutableString)之間進(jìn)行選擇,以指示字符串是否可以變異。

字符串是值類型

Swift的字符串類型是值類型。如果我們創(chuàng)建了一個(gè)新的字符串值,當(dāng)它被傳遞給函數(shù)或方法時(shí),或者當(dāng)它被賦給常量或變量時(shí),該字符串值就會(huì)被復(fù)制。在每種情況下,都會(huì)創(chuàng)建現(xiàn)有字符串值的新副本,并傳遞或分配新副本,而不是原始版本。值類型在結(jié)構(gòu)和枚舉是值類型中描述。
Swift的這種默認(rèn)復(fù)制字符串行為,可以確保當(dāng)一個(gè)函數(shù)或方法傳遞給我們一個(gè)字符串值時(shí),不管它來自何處,我們都可以明確地?fù)碛性撟址怠N覀兛梢源_信,傳遞給我們的字符串不會(huì)被修改,除非我們自己修改它。
Swift的編譯器在背后優(yōu)化了字符串的使用,使得真正的復(fù)制只在絕對(duì)必要的時(shí)候發(fā)生。這意味著我們?cè)趯⒆址鳛橹殿愋褪褂脮r(shí)總是可以獲得很好的性能。

使用字符

通過使用for-in循環(huán)對(duì)字符串進(jìn)行迭代,可以訪問字符串的各個(gè)字符值:

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

for-in循環(huán)在for-in循環(huán)中進(jìn)行了描述。
或者,可以通過提供字符類型注釋,從單個(gè)字符串文字創(chuàng)建獨(dú)立的字符常量或變量:

let exclamationMark: Character = "!"

可以通過將字符值數(shù)組作為參數(shù)傳遞給其初始值設(shè)定項(xiàng)來構(gòu)造字符串值:

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

連接字符串和字符

可以使用加法運(yùn)算符(+)將字符串值相加(或串聯(lián))以創(chuàng)建新的字符串值:

let string1 = "hello"
let string2 = " there"
var welcome = string1 + string2
// welcome now equals "hello there"

我們還可以使用加法賦值運(yùn)算符(+=)將字符串值追加到現(xiàn)有字符串變量:

var instruction = "look over"
instruction += string2
// instruction now equals "look over there"

我們可以使用String類型的append() 方法將字符值追加到字符串變量:

let exclamationMark: Character = "!"
welcome.append(exclamationMark)
// welcome now equals "hello there!"

注意
不能將字符串或字符追加到現(xiàn)有字符變量,因?yàn)樽址抵荒馨瑔蝹€(gè)字符。

如果使用多行字符串文字來構(gòu)建較長(zhǎng)的字符串,則字符串中的每一行都會(huì)以換行符結(jié)束,包括最后一行。例如:

let badStart = """
one
two
"""
let end = """
three
"""
print(badStart + end)
// Prints two lines:
// one
// twothree

let goodStart = """
one
two

"""
print(goodStart + end)
// Prints three lines:
// one
// two
// three

在上面的代碼中,連接badStart和end會(huì)產(chǎn)生一個(gè)兩行字符串,這不是所需的結(jié)果。因?yàn)閎adStart的最后一行不會(huì)以換行符結(jié)束,所以該行會(huì)與end的第一行合并。相反,goodStart的兩行都以換行符結(jié)束,因此當(dāng)它與end結(jié)合時(shí),結(jié)果有三行,正如預(yù)期的那樣。

字符串插值

字符串插值是一種通過將常量、變量、文字和表達(dá)式的值,包含在字符串文字中來構(gòu)造新字符串值的方法??梢栽趩涡泻投嘈凶址淖种惺褂米址逯怠2迦胱址淖值拿恳豁?xiàng)都用一對(duì)圓括號(hào)括起來,前綴是反斜杠(\):

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

在上面的示例中,multiplier的值作為 (multiplier) 插入到字符串文本中。當(dāng)計(jì)算字符串插值以創(chuàng)建實(shí)際字符串時(shí),此占位符將替換為乘數(shù)的實(shí)際值。
multiplier的值也是字符串后面較大表達(dá)式的一部分。此表達(dá)式計(jì)算Double(multiplier) * 2.5的值,并將結(jié)果(7.5)插入字符串。在本例中,當(dāng)表達(dá)式包含在字符串文本中時(shí),它被寫為\Double(multiplier) * 2.5。
可以使用擴(kuò)展字符串分隔符創(chuàng)建包含字符的字符串,否則這些字符將被視為字符串插值。例如:

print(#"Write an interpolated string in Swift using \(multiplier)."#)
// Prints "Write an interpolated string in Swift using \(multiplier)."

要在使用擴(kuò)展分隔符的字符串中使用字符串插值,請(qǐng)將反斜杠后的數(shù)字符號(hào)數(shù)與字符串開頭和結(jié)尾的數(shù)字符號(hào)數(shù)匹配。例如:

print(#"6 times 7 is \#(6 * 7)."#)
// Prints "6 times 7 is 42."

注意
在插入字符串的圓括號(hào)內(nèi)編寫的表達(dá)式不能包含未轉(zhuǎn)義的反斜杠(\)、回車符或換行符。但是,它們可以包含其他字符串文字。

Unicode碼

Unicode是一種國(guó)際標(biāo)準(zhǔn),用于編碼、表示和處理不同書寫系統(tǒng)中的文本。它使我們能夠以標(biāo)準(zhǔn)化的形式表示任何語言中的幾乎任何字符,并在文本文件或網(wǎng)頁等外部源中讀寫這些字符。Swift的字符串和字符類型完全符合Unicode,如本節(jié)所述。

Unicode標(biāo)量值

在后臺(tái),Swift的原生字符串類型是從Unicode標(biāo)量值構(gòu)建的。Unicode標(biāo)量值是字符或修飾符的唯一21位數(shù)字,例如U+0061表示拉丁文小寫字母A(“a”),或U+1F425表示前向小雞(“??").
請(qǐng)注意,并非所有21位Unicode標(biāo)量值都分配給一個(gè)字符,有些標(biāo)量是為將來的分配或UTF-16編碼中使用而保留的。已分配給字符的標(biāo)量值通常也有一個(gè)名稱,例如上面示例中的拉丁文小寫字母a和面向前方的雛雞。

擴(kuò)展的圖形集簇

Swift字符類型的每個(gè)實(shí)例都表示一個(gè)擴(kuò)展的grapheme集群。擴(kuò)展的grapheme集群是由一個(gè)或多個(gè)Unicode標(biāo)量組成的序列,當(dāng)這些標(biāo)量組合在一起時(shí),會(huì)產(chǎn)生一個(gè)人類可讀的字符。
下面是一個(gè)例子。字母é可以表示為單個(gè)Unicode標(biāo)量é(帶銳音符的拉丁文小寫字母E,或U+00E9)。但是,同一個(gè)字母也可以表示為一對(duì)標(biāo)量——一個(gè)標(biāo)準(zhǔn)字母e(拉丁文小寫字母e,或U+0065),后跟組合的銳音符標(biāo)量(U+0301)。組合的銳音符標(biāo)量以圖形方式應(yīng)用于它前面的標(biāo)量,當(dāng)它由支持Unicode的文本呈現(xiàn)系統(tǒng)呈現(xiàn)時(shí),將e轉(zhuǎn)換為é。
在這兩種情況下,字母é都表示為單個(gè)Swift字符值,該值表示擴(kuò)展的grapheme集群。在第一種情況下,集群包含一個(gè)標(biāo)量;在第二種情況下,集群包含兩個(gè)標(biāo)量:

let eAcute: Character = "\u{E9}"                         // é
let combinedEAcute: Character = "\u{65}\u{301}"          // e followed by ?
// eAcute is é, combinedEAcute is e?

擴(kuò)展的grapheme集群是一種靈活的方法,可以將許多復(fù)雜的腳本字符表示為單個(gè)字符值。例如,韓國(guó)語字母表中的朝鮮文音節(jié)可以表示為預(yù)合成或分解的序列。在Swift中,這兩種表示都可以作為單個(gè)字符值:

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

擴(kuò)展的grapheme集群允許用于封閉標(biāo)記的標(biāo)量(例如組合封閉圓或U+20DD)將其他Unicode標(biāo)量封閉為單個(gè)字符值的一部分:

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

區(qū)域指示器符號(hào)的Unicode標(biāo)量可以成對(duì)組合以形成單個(gè)字符值,例如區(qū)域指示器符號(hào)字母U(U+1F1FA)域指示器符號(hào)字母S(U+1F1F8)的組合:

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

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

要檢索字符串中字符值的計(jì)數(shù),請(qǐng)使用該字符串的count屬性:

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

請(qǐng)注意,Swift對(duì)字符值使用擴(kuò)展的grapheme集群意味著,字符串連接和修改可能并不總是會(huì)影響字符串的字符計(jì)數(shù)。

例如,如果使用四個(gè)字符的單詞cafe來初始化一個(gè)新字符串,然后在字符串末尾附加一個(gè)組合銳音符(U+0301),則生成的字符串的字符計(jì)數(shù)仍將為4,第四個(gè)字符為e?,而不是e:

var word = "cafe"
print("the number of characters in \(word) is \(word.count)")
// Prints "the number of characters in cafe is 4"

word += "\u{301}"    // COMBINING ACUTE ACCENT, U+0301

print("the number of characters in \(word) is \(word.count)")
// Prints "the number of characters in cafe? is 4"

注意
擴(kuò)展的grapheme集群可以由多個(gè)Unicode標(biāo)量組成。這意味著不同的字符和同一字符的不同表示,可能需要不同數(shù)量的內(nèi)存來存儲(chǔ)。因此,Swift中的字符在字符串表示中占用的內(nèi)存量并不相同。因此,如果不遍歷字符串來確定其擴(kuò)展的grapheme集群邊界的話,那么就無法計(jì)算出字符串中的字符數(shù)。如果我們使用的是特別長(zhǎng)的字符串值,請(qǐng)注意count屬性必須迭代整個(gè)字符串中的Unicode標(biāo)量,以便確定該字符串的字符個(gè)數(shù)。
count屬性返回的字符計(jì)數(shù),并不總是與包含相同字符的NSString的length屬性相同。NSString的長(zhǎng)度基于字符串的UTF-16表示中16位編碼單元的數(shù)量,而不是字符串中Unicode擴(kuò)展的grapheme集群的數(shù)量。

訪問和修改字符串

我們可以通過字符串的方法和屬性或使用下標(biāo)語法來訪問和修改字符串。

字符串索引

每個(gè)字符串值都有一個(gè)關(guān)聯(lián)的索引類型,字符串索引,對(duì)應(yīng)于字符串中每個(gè)字符的位置。
如上所述,不同的字符可能需要不同數(shù)量的內(nèi)存來存儲(chǔ),因此為了確定哪個(gè)字符位于特定位置,必須從字符串的開始或結(jié)束處迭代每個(gè)Unicode標(biāo)量。因此,Swift字符串不能被整數(shù)值索引(不是很明白為什么)。
使用startIndex屬性訪問字符串第一個(gè)字符的位置。endIndex屬性是字符串中最后一個(gè)字符之后的位置。因此,endIndex屬性不是字符串下標(biāo)的有效參數(shù)。如果字符串為空,則startIndex和endIndex相等。
使用String的index(before:)index(after:)方法訪問給定索引之前和之后的索引。要訪問距離給定索引較遠(yuǎn)的索引,可以使用index(_:offsetBy:)方法,而不是多次調(diào)用其中一個(gè)方法。
可以使用下標(biāo)語法訪問特定字符串索引處的字符。

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

嘗試訪問字符串范圍之外的索引或字符串范圍之外的索引處的字符將觸發(fā)運(yùn)行時(shí)錯(cuò)誤。

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

使用indices屬性訪問字符串中單個(gè)字符的所有索引。

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

注意
我們可以對(duì)遵守Collection協(xié)議的任何類型,都可以使用startIndex和endIndex屬性以及index(before:),index(after:)index(_:offsetBy:) 方法。這包括字符串,以及集合類型,如數(shù)組、字典和集合。

插入和刪除

若要在指定索引處的字符串中插入單個(gè)字符,請(qǐng)使用 insert(_:at:) 方法;若要在指定索引處插入另一個(gè)字符串的內(nèi)容,請(qǐng)使用insert(contentsOf:at:)方法。

var welcome = "hello"
welcome.insert("!", at: welcome.endIndex)
// welcome now equals "hello!"

welcome.insert(contentsOf: " there", at: welcome.index(before: welcome.endIndex))
// welcome now equals "hello there!"

若要從指定索引處的字符串中刪除單個(gè)字符,請(qǐng)使用remove(at:)方法;若要?jiǎng)h除指定范圍內(nèi)的子字符串,請(qǐng)使用removeSubrange(_:)方法:

welcome.remove(at: welcome.index(before: welcome.endIndex))
// welcome now equals "hello there"

let range = welcome.index(welcome.endIndex, offsetBy: -6)..<welcome.endIndex
welcome.removeSubrange(range)
// welcome now equals "hello"

注意
我們對(duì)任何遵守RangeReplaceableCollection協(xié)議的類型,都可以使用insert(_:at:)、insert(contentsOf:at:)remove(at:)removeSubrange(_:)方法。這包括字符串,以及集合類型,如數(shù)組、字典和集合。

子字符串

例如,使用下標(biāo)或prefix(_:)之類的方法從字符串獲取子字符串時(shí),結(jié)果就是表示子字符串的實(shí)例,而不是另一個(gè)字符串。Swift中的子字符串具有與字符串大多數(shù)相同的方法,這意味著我們可以使用與處理字符串相同的方法處理子字符串。但是,與字符串不同的是,在對(duì)字符串執(zhí)行操作時(shí),得到的子字符串的存儲(chǔ)使用時(shí)間很短。如果我們準(zhǔn)備將結(jié)果存儲(chǔ)更長(zhǎng)的時(shí)間時(shí),可以將子字符串轉(zhuǎn)換為String的實(shí)例。例如:

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

// Convert the result to a String for long-term storage.
let newString = String(beginning)

與字符串一樣,每個(gè)子字符串都有一個(gè)存儲(chǔ)子字符串的字符的內(nèi)存區(qū)域。字符串和子字符串之間的區(qū)別在于,從性能優(yōu)化的角度來看,子字符串可以重用存儲(chǔ)原始字符串的部分內(nèi)存,或存儲(chǔ)另一個(gè)子字符串的部分內(nèi)存。(字符串具有類似的優(yōu)化,但如果兩個(gè)字符串共享內(nèi)存,則它們相等。)這個(gè)性能優(yōu)化意味著我們不必為復(fù)制內(nèi)存耗費(fèi)性能成本,直到我們修改字符串或子字符串。如上所述,子字符串不適合長(zhǎng)期存儲(chǔ),因?yàn)樗鼈?strong>重用了原始字符串的存儲(chǔ),所以只要使用任何子字符串,整個(gè)原始字符串都必須保存在內(nèi)存中。
在上面的例子中,greeting是一個(gè)字符串,這意味著它有一個(gè)存儲(chǔ)組成字符串的字符的內(nèi)存區(qū)域。因?yàn)殚_頭是greeting的子字符串,所以它重用了greeting使用的內(nèi)存。相反,newString是一個(gè)由子字符串創(chuàng)建的字符串,它有自己的存儲(chǔ)空間。下圖顯示了這些關(guān)系:

截屏2021-02-03 下午5.33.54.png

注意
字符串和子字符串都遵守StringProtocol協(xié)議,這意味著字符串操作函數(shù)通??梢苑奖愕亟邮躍tringProtocol值??梢允褂米址蜃幼址嫡{(diào)用此類函數(shù)。

比較字符串

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

字符串和字符相等

使用“等于”運(yùn)算符(==)和“不等于”運(yùn)算符(!=),如比較運(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")
}
// Prints "These two strings are considered equal"

如果兩個(gè)字符串值(或兩個(gè)字符值)的擴(kuò)展grapheme簇在規(guī)范上是等價(jià)的,則認(rèn)為它們相等。如果擴(kuò)展的字組簇具有相同的語言含義和外觀,則它們?cè)谝?guī)范上是等價(jià)的,即使它們是由不同的Unicode標(biāo)量在幕后組成的。

例如,帶銳音符(U+00E9)的拉丁文小寫字母E在規(guī)范上等同于拉丁文小寫字母E(U+0065)后跟組合銳音符(U+0301)。這兩個(gè)擴(kuò)展的字組簇都是表示字符的有效方法,因此它們被認(rèn)為是規(guī)范等效的:

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

// "Voulez-vous un cafe??" using 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")
}
// Prints "These two strings are considered equal"

相反,英語中使用的拉丁文大寫字母A(U+0041,或“A”)與俄語中使用的西里爾文大寫字母A(U+0410,或“А”)并不相等。這些字符在視覺上相似,但語言意義不同:

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

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

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

注意
Swift中的字符串和字符比較不區(qū)分區(qū)域設(shè)置。

前綴和后綴相等

若要檢查字符串是否具有特定的字符串前綴或后綴,請(qǐng)調(diào)用該字符串的hasPrefix(_:)hasSuffix(_:)方法,這兩種方法都采用了單個(gè)string類型參數(shù)并返回布爾值。
下面的例子考慮的是一組字符串,表示莎士比亞《羅密歐與朱麗葉》前兩幕的場(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"
]

你可以使用hasPrefix(_:)方法和romeoAndJuliet數(shù)組來計(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")
// Prints "There are 5 scenes in Act 1"

同樣,使用hasSuffix(_:)方法計(jì)算發(fā)生在卡普萊特的豪宅和勞倫斯修士牢房?jī)?nèi)或周圍的場(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")
// Prints "6 mansion scenes; 2 cell scenes"

注意
hasPrefix(_:)hasSuffix(_:)方法在每個(gè)字符串中的擴(kuò)展grapheme集群之間執(zhí)行逐字符的規(guī)范等價(jià)比較,如字符串和字符相等中所述。

字符串的Unicode表示

將Unicode字符串寫入文本文件或其他存儲(chǔ)時(shí),該字符串中的Unicode標(biāo)量將以幾種Unicode定義的編碼形式之一進(jìn)行編碼。每個(gè)表單將字符串編碼為稱為編碼單元的小塊。其中包括UTF-8編碼形式(將字符串編碼為8位編碼單元)、UTF-16編碼形式(將字符串編碼為16位編碼單元)和UTF-32編碼形式(將字符串編碼為32位編碼單元)。

Swift提供了幾種不同的方法來訪問字符串的Unicode表示??梢允褂胒or-in語句對(duì)字符串進(jìn)行迭代,以將其單個(gè)字符值作為Unicode擴(kuò)展的grapheme集群進(jìn)行訪問。在“使用字符”中介紹了此過程。

或者,訪問其他三種符合Unicode的表示形式之一中的字符串值:

  • UTF-8編碼單元的集合(使用字符串的utf8屬性訪問)
  • UTF-16編碼單元的集合(使用字符串的utf16屬性訪問)
  • 21位Unicode標(biāo)量值的集合,相當(dāng)于字符串的UTF-32編碼形式(使用字符串的unicodeScalars屬性訪問)

下面的每個(gè)示例都顯示了以下字符串的不同表示形式,該字符串由字符D、o、g、?(雙感嘆號(hào),或Unicode標(biāo)量U+203C)和?? 字符(狗臉,或Unicode標(biāo)量U+1F436)組成:

let dogString = "Dog???"

UTF-8表示法

我們可以通過迭代字符串的utf8屬性來訪問它的UTF-8表示形式。此屬性的類型為String.UTF8View,它是無符號(hào)8位(UInt8)值的集合,字符串的UTF-8表示形式中的每個(gè)字節(jié)對(duì)應(yīng)一個(gè)值:

截屏2021-02-03 下午5.40.08.png

for codeUnit in dogString.utf8 {
    print("\(codeUnit) ", terminator: "")
}
print("")
// Prints "68 111 103 226 128 188 240 159 144 182 "

在上面的示例中,前三個(gè)十進(jìn)制codeUnit值(68111103)表示字符D、o和g,其UTF-8表示形式與其ASCII表示形式相同。接下來的三個(gè)十進(jìn)制codeUnit值(226、128、188)是雙感嘆號(hào)字符的三字節(jié)UTF-8表示。最后四個(gè)codeUnit值(240、159、144、182)是狗臉字符的四字節(jié)UTF-8表示。

UTF-16表示法

通過迭代字符串的utf16屬性,可以訪問字符串的UTF-16表示形式。此屬性的類型為String.UTF16View,它是無符號(hào)16位(UInt16)值的集合,在字符串的UTF-16表示形式中,每個(gè)16位編碼單元對(duì)應(yīng)一個(gè)值:


截屏2021-02-03 下午5.45.07.png
for codeUnit in dogString.utf16 {
    print("\(codeUnit) ", terminator: "")
}
print("")
// Prints "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)是十六進(jìn)制值203C的十進(jìn)制等價(jià)值,它表示雙感嘆號(hào)字符的Unicode標(biāo)量U+203C。在UTF-16中,這個(gè)字符可以表示為單個(gè)編碼單元。
第五和第六個(gè)codeUnit值(55357和56374)是狗臉字符的UTF-16的表示對(duì)。這些值是高位表示U+D83D(十進(jìn)制值55357)和低位表示U+DC36(十進(jìn)制值56374)。

Unicode標(biāo)量表示法

我們可以通過迭代字符串值的 unicodeScalars 屬性來訪問字符串值的Unicode標(biāo)量表示。此屬性的類型為UnicodeScalarView,它是UnicodeScalar類型的值的集合。
每個(gè)UnicodeScalar都有一個(gè)value屬性,返回標(biāo)量的21位值,用UInt32值表示:


截屏2021-02-03 下午5.47.58.png
for scalar in dogString.unicodeScalars {
    print("\(scalar.value) ", terminator: "")
}
print("")
// Prints "68 111 103 8252 128054 "

前三個(gè)UnicodeScalar值(68, 111, 103)的value屬性,還是表示字符D、o和g。
第四個(gè)codeUnit值(8252)也是十六進(jìn)制值203C的十進(jìn)制等價(jià)值,它表示雙感嘆號(hào)字符的Unicode標(biāo)量U+203C。
第五個(gè)也是最后一個(gè)UnicodeScalar 128054的value屬性是十六進(jìn)制值1F436的十進(jìn)制等價(jià)值,它表示狗臉字符的Unicode標(biāo)量U+1F436。
作為查詢其值屬性的替代方法,每個(gè)UnicodeScalar值也可用于構(gòu)造新的字符串值,例如使用字符串插值:

for scalar in dogString.unicodeScalars {
    print("\(scalar) ")
}
// D
// o
// g
// ?
// ??
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容