Swift - 字符串和字符(Strings and Characters)

??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、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

由于多行字符串字面量使用了三個雙引號,而不是一個,所以你可以在多行字符串字面量里直接使用雙引號(")而不必加上轉(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 的StringCharacter類型是完全兼容 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ù)量并不總是與包含相同字符的NSStringlength屬性相同。NSStringlength屬性是利用 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是空串,startIndexendIndex是相等的。

通過調(diào)用 Stringindex(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 ! "

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

插入和刪除

調(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 中,您也可以使用在 ArrayDictionarySet 中。

子字符串

當(dāng)你從字符串中獲取一個子字符串 —— 例如,使用下標(biāo)或者 prefix(_:) 之類的方法 —— 就可以得到一個 SubString 的實例,而非另外一個 String。Swift 里的 SubString 絕大部分函數(shù)都跟 String 一樣,意味著你可以使用同樣的方式去操作 SubStringString。然而,跟 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)存里保存字符集。而 StringSubString 的區(qū)別在于性能優(yōu)化上,SubString 可以重用原 String 的內(nèi)存空間,或者另一個 SubString 的內(nèi)存空間(String 也有同樣的優(yōu)化,但如果兩個 String 共享內(nèi)存的話,它們就會相等)。這一優(yōu)化意味著你在修改 StringSubString 之前都不需要消耗性能去復(fù)制內(nèi)存。就像前面說的那樣,SubString 不適合長期存儲 —— 因為它重用了原 String 的內(nèi)存空間,原 String 的內(nèi)存空間必須保留直到它的 SubString 不再被使用為止。

上面的例子,greeting 是一個 String,意味著它在內(nèi)存里有一片空間保存字符集。而由于 beginninggreetingSubString,它重用了 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)相等,詳細描述在字符串/字符相等。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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