【Swift 3.1】03 - 字符串和字符 (Strings and Characters)

字符串和字符 (Strings and Characters)

自從蘋(píng)果2014年發(fā)布Swift,到現(xiàn)在已經(jīng)兩年多了,而Swift也來(lái)到了3.1版本。去年利用工作之余,共花了兩個(gè)多月的時(shí)間把官方的Swift編程指南看完。現(xiàn)在整理一下筆記,回顧一下以前的知識(shí)。有需要的同學(xué)可以去看官方文檔>>


字符串字面值 (String Literals)

使用字符串字面值來(lái)初始化一個(gè)常量或者變量:

let someString = "Some string literal value"

Swift根據(jù)字面值來(lái)推斷出someStringString類(lèi)型。

初始化一個(gè)空字符串

為了創(chuàng)建更長(zhǎng)的字符串,我們通常要先初始化一個(gè)空字符串,我們可以使用下面兩種方法:

var emptyString = ""                // 空字符串字面值
var anotherEmptyString = String()   // 使用默認(rèn)構(gòu)造函數(shù)
// 這兩個(gè)字符串都是空的,并且是相等的

通過(guò)檢查字符串的布爾類(lèi)型屬性isEmpty來(lái)判斷一個(gè)字符串是否為空:

if emptyString.isEmpty {
    print("這里什么都沒(méi)有")
}
// Prints "這里什么都沒(méi)有"

字符串的可變性 (String Mutability)

使用let聲明一個(gè)不可變的字符串,用var聲明一個(gè)可變的字符串:

var variableString = "Horse"
variableString += "and carriage"
// variableString 現(xiàn)在是 "Horse and carriage"

let constatString = "Highlander"
constantString += "and another Highlander"
// 會(huì)產(chǎn)生一個(gè)編譯錯(cuò)誤,因?yàn)閏onstantString是一個(gè)常量,不能被改變

在OC中,需要使用NSStringNSMutableString來(lái)分別定義不可變和可變字符串。

字符串是值類(lèi)型 (Strings Are Value Types)

Swift的String類(lèi)型是值類(lèi)型。如果我們創(chuàng)建一個(gè)新的字符串,那么把這個(gè)字符串以復(fù)制的形式傳給一個(gè)函數(shù)或方法,或者把它賦值給另外一個(gè)常量或常量。

在Swift的底層實(shí)現(xiàn),Swift的編譯器會(huì)優(yōu)化字符串的使用,在有必要的時(shí)候才會(huì)進(jìn)行真正的復(fù)制,這就意味著把字符串作為值類(lèi)型在使用的時(shí)候,都有非常好的性能。

與字符交互 (Working with Characters)

使用for-in循環(huán)遍歷字符串的characters屬性來(lái)訪(fǎng)問(wèn)單個(gè)字符:

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

字符串可以通過(guò)傳遞一個(gè)字符數(shù)組給String的構(gòu)造函數(shù)來(lái)初始化:

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

連接字符串和字符 (Concatenating Strings and Characters)

使用加號(hào)運(yùn)算符(+)來(lái)拼接兩個(gè)字符串:

let string1 = "hello"
let string2 = "there"
let welcome = string1 + string2
// welcome 等于 "hello there"

使用加法賦值運(yùn)算符來(lái)拼接一個(gè)已經(jīng)存在字符串:

var instruction = "look over"
instruction += string2
// instruction 等于 "look over there"

使用String類(lèi)型的append()方法來(lái)拼接:

let exclamationMark: Character = "!"
welcome.append(exclamationMark)
// welcome 等于 "hello there!"

注意:不能添加一個(gè)String或者Character到一個(gè)已經(jīng)存在的Character變量,因?yàn)?code>Character類(lèi)型的值只能包含一個(gè)字符。

字符串插值 (String Interpolation)

字符串插值可以把常量、變量、字面值和其他表達(dá)式混合在一起,拼接成一個(gè)新的字符串。

let multiplier = 3
let message = "\(multiplier) 乘以 2.5 等于 \(Double(multiplier) * 2.5)"
// message is "3 乘以 2.5 等于 7.5"

Unicode

Unicode是在不同系統(tǒng)中編碼、展示和處理文本的國(guó)際標(biāo)準(zhǔn)。可以讓我們以國(guó)際化的形式在不同的語(yǔ)言中展示幾乎任何字符,從外部文件(例如text文件或網(wǎng)頁(yè))讀取那些字符或者把那些字符寫(xiě)到外部文件。Swift的StringCharacter類(lèi)型是完全兼容Unicode的。

Unicode標(biāo)量 (Unicode Scalars)

在底層,Swift的原生String類(lèi)型是從Unicode標(biāo)量值建立的。一個(gè)Unicode標(biāo)量是一個(gè)字符或者修飾符唯一的21-bit數(shù)字,例如U+0061LATIN SMALL LETTER A("a"),U+1F425FRONT-FACING BABY CHICK ("??")。

注意:一個(gè)Unicode標(biāo)量是在U+0000U+D7FF(包含首尾)這個(gè)范圍中的一個(gè)代碼點(diǎn)(code point),或者是U+E000U+10FFFF(包含首尾)這個(gè)范圍的一個(gè)代碼點(diǎn)。Unicode標(biāo)量不包含Unicode代理對(duì)(surrogate pair)代碼點(diǎn),代理對(duì)的代碼點(diǎn)范圍是U+D800U+DFFF(包含首尾)。

并不是所有的21-bitUnicode標(biāo)量都賦值給了一個(gè)字符,有一些標(biāo)量是保留起來(lái)給未來(lái)使用的。被賦值給字符串的Unicode標(biāo)量都有一個(gè)名字,例如上面例子中的LATIN SMALL LETTER A和FRONT-FACING BABY CHICK`。

字符串字面值中的特殊字符 (Special Characters in String Literals)

字符串字面值可以包含以下字符:

  • 轉(zhuǎn)義字符:\0(空字符)、\\(反斜杠)、\t(水平制表符)、\n(換行)、\r(回車(chē))、\"(雙引號(hào))、\'(單引號(hào))
  • 一個(gè)任意的Unicode標(biāo)量,寫(xiě)成這個(gè)樣式\u{n},n是1~8的十六進(jìn)制數(shù)字并且等于一個(gè)有效的Unicode代碼點(diǎn)
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
擴(kuò)展字形集群 (Extended Grampheme Clusters)

每個(gè)SwiftCharacter類(lèi)型實(shí)例代表著一個(gè)擴(kuò)展字形集群。一個(gè)擴(kuò)展字形集群是一系列的一個(gè)或多個(gè)Unicode標(biāo)量,并形成一個(gè)人類(lèi)能夠讀懂的字符。

例如,字母é代表這個(gè)Unicode標(biāo)量é(LATIN SMALL LETTER E WITH ACUTE, 或者 U+00E9)。然而這個(gè)字母還可以代表一對(duì)標(biāo)量:一個(gè)標(biāo)準(zhǔn)的字母e(LATIN SMALL LETTER,或者U+0065),接著是COMBINING ACUTE ACCENT標(biāo)量(U+0301)。COMBINING ACUTE ACCENT標(biāo)量被圖形化的應(yīng)用于在它前面的標(biāo)量,Unicode文本渲染系統(tǒng)把e渲染成é

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

擴(kuò)展字形集群是一個(gè)能把很多復(fù)雜腳本字符顯示為一個(gè)Character字符的一種靈活的方式。例如,韓文字母的韓文音節(jié)可以用合成或者分解序列來(lái)表示:

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

擴(kuò)展字形集群還支持封閉標(biāo)志(例如COMBINING ENCLOSING CIRCLE,或者U+20DD):

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

代表區(qū)域標(biāo)志符號(hào)的Unicode標(biāo)量還可以結(jié)成對(duì),形成一個(gè)Character字符。例如這個(gè)組合:REGIONAL INDICATOR SYMBOL LETTER U (U+1F1FA)REGIONAL INDICATOR SYMBOL LETTER S (U+1F1F8)

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

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

使用字符串的characterscount屬性來(lái)后去字符的個(gè)數(shù):

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

注意:在Swift中,使用擴(kuò)展字形集群形成的字符,在拼接和修改字符串時(shí)不一定會(huì)影響字符的個(gè)數(shù)。例如:初始化一個(gè)字符串cafe,接著在后面追加COMBINING ACUTE ACCENT (U+0301),最終得到的字符串還是只有4個(gè)字符,第四個(gè)字符是é,不是e:

var word = "cafe"
print("the number of characters in \(word) is \(word.characters.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.characters.count)")
// Prints "the number of characters in café is 4"

注意:擴(kuò)展字形集群可以有一個(gè)或多個(gè)Unicode標(biāo)尺組成,這意味著不同的字符和同一個(gè)字符的不同展示方式需要不同大小的內(nèi)存來(lái)存儲(chǔ)。所以,Swift中的字符不會(huì)在字符串的展示中占用相同的內(nèi)存量。那么,如果不遍歷一個(gè)字符串,就無(wú)法知道這個(gè)字符串的字符數(shù),從而無(wú)法確定字符的擴(kuò)展字形集群邊界。如果我們要使用特別長(zhǎng)的字符串,必須清楚地知道字符串的characters屬性必須遍歷整個(gè)字符串的全部Unicode標(biāo)量來(lái)決定字符串的所有字符。

在擁有相同字符得到情況下,Stringcharacters屬性返回的字符數(shù)不總是等于NSStringlength屬性。NSString的長(zhǎng)度是基于在字符串的UTF-16顯示之內(nèi)的16-bit代碼單元的數(shù)字,而不是基于在字符串之內(nèi)的Unicode擴(kuò)展字形集群的數(shù)字。

訪(fǎng)問(wèn)和修改字符串 (Accessing and Modifying a String)

字符串索引 (String Indices)

每個(gè)String都有一個(gè)關(guān)聯(lián)的索引類(lèi)型,String.Index,對(duì)應(yīng)著字符串里的沒(méi)一個(gè)字符的位置。

就像上面說(shuō)到的,不同的字符需要占用不用的內(nèi)存量,所以為了得到字符的位置,我們需要遍歷整個(gè)字符串的全部Unicode標(biāo)量。所以Swift的字符串不能按整數(shù)值進(jìn)行索引。

使用startIndex來(lái)訪(fǎng)問(wèn)字符串的第一個(gè)字符的位置,endIndex是字符串最后一個(gè)字符的下一個(gè)位置。所以,endIndex不是一個(gè)有效的字符串索引。如果一個(gè)字符串是空的,那么startIndexendIndex是相等的。

使用index(before:)index(after:)來(lái)訪(fǎng)問(wèn)索引;使用index(_:offsetBy:)來(lái)訪(fǎng)問(wèn)離一個(gè)給定的索引一定距離的索引。

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)
// a

下面兩行代碼將會(huì)報(bào)錯(cuò),已經(jīng)超出了字符串的范圍:

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

使用charactersindices屬性來(lái)訪(fǎng)問(wèn)字符串中每一個(gè)字符的索引:

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

注意:我們可以在遵循了Collection協(xié)議的任何類(lèi)型中使用startIndex/endIndex屬性和index(before:)/index(after:)/index(_:offsetBy:)方法,包括:StringArrayDictionarySet

插入和移除 (Inserting and Removing)

在特定的位置插入一個(gè)字符,使用insert(_:at:);在特定的位置插入另外一個(gè)字符串內(nèi)容,使用insert(contentsOf:at:)

var welcome = "hello"
welcome.insert("!", at: welcome.endIndex)
// welcome 現(xiàn)在是 hello!

welcome.insert(contentsOf: " there".characters, at: welcome.index(before: welcome.endIndex))
// welcome 現(xiàn)在是 hello there!

在特定的位置刪除一個(gè)字符,使用remove(at:),刪除一個(gè)范圍內(nèi)的字符串,使用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

注意:我們可以在遵循了RangeReplaceableCollection協(xié)議的任何類(lèi)型中使用insert(_:at:)insert(contentsOf:at:)remove(at:)removeSubrange(_:)方法,包括:StringArrayDictionarySet

字符串的比較 (Comparing Strings)

Swift提供了三種方式來(lái)進(jìn)行文本比較:1)字符串和字符相等;2)前綴相等;3)后綴相等。

字符串和字符相等 (String and Character Equality)

使用等于==和不等于!=進(jì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ò)展字形集群正則等價(jià),那么這兩個(gè)字符串或者字符相等。如果兩個(gè)字符串或者兩個(gè)字符有相同的語(yǔ)言意義和外觀,那么他們的擴(kuò)展字形集群相等,即使他們是不同的Unicode標(biāo)量合成的。例如,LATIN SMALL LETTER E WITH ACUTE (U+00E9)LATIN SMALL LETTER E (U+0065)COMBINING ACUTE ACCENT (U+0301)的組合是相等的:

// "Voulez-vous un café?" using LATIN SMALL LETTER E WITH ACUTE
let eAcuteQuestion = "Voulez-vous un caf\u{E9}?"
 
// "Voulez-vous un café?" 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"

相反,英語(yǔ)的LATIN CAPITAL LETTER A(U+0041, 或者 "A"),不等于俄語(yǔ)的CYRILLIC CAPITAL LETTER A(U+0410, 或者 "А"),他們看起來(lái)非常相似,但是語(yǔ)言意義不一樣:

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中的字符和字符串對(duì)區(qū)域不敏感。

前綴和后綴相等 (Prefix and Suffix Equality)

使用hasPrefix(_:)hasSuffix(_:)來(lái)判斷一個(gè)字符串是否有特定的前綴或者后綴。

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"
]

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"

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"

Unicode表示的字符串 (Unicode Representations of Strings)

當(dāng)一個(gè)Unicode字符串被寫(xiě)入文本文件或者其他內(nèi)存時(shí),字符串的Unicode標(biāo)量會(huì)被以一種Unicode定義的編碼形式編碼。每一種形式將字符串編碼到一個(gè)小方塊,也就是代碼單元。這些形式包括:1)UTF-8編碼形式(把一個(gè)字符串編碼為8-bit代碼單元);2)UTF-16編碼形式(把一個(gè)字符串編碼為16-bit代碼單元);3)UTF-32編碼形式(把一個(gè)字符串編碼為32-bit代碼單元)。

訪(fǎng)問(wèn)其中一種符合Unicode標(biāo)準(zhǔn)表示的字符串:

  • UTF-8代碼單元集合(使用utf8屬性)
  • UTF-16代碼單元集合(使用utf16屬性)
  • 21-bit Unicode標(biāo)量,也就是字符串的UTF-32編碼形式(使用unicodeScalars屬性訪(fǎng)問(wèn))

下面的例子將以不同的展示方式來(lái)展示這兩個(gè)字符或字符串:1)一個(gè)由Dog!!(DOUBLE EXCLAMATION MARK,或者Unicode標(biāo)量U+203C)組成的字符串;2)字符:?? (DOG FACE,或者Unicode標(biāo)量U+1F436)。

UTF-8 展示 (UTF-8 Representation)

遍歷utf8屬性來(lái)訪(fǎng)問(wèn)字符串的UTF-8展示,這個(gè)屬性是String.UTF8View類(lèi)型,是無(wú)符號(hào)8-bit (UInt8)值的集合。

UTF-8 representation
let codeUnit in dogString.utf8 {
    print("\(codeUnit) ", terminator: "")
}
print("")
// 68 111 103 226 128 188 240 159 144 182
UTF-16 展示 (UTF-16 Representation)

遍歷utf16屬性來(lái)訪(fǎng)問(wèn)字符串的UTF-16展示,這個(gè)屬性是String.UTF16View類(lèi)型,是無(wú)符號(hào)8-bit (UInt8)值的集合。

UTF-16 Representation
for codeUnit in dogString.utf16 {
    print("\(codeUnit) ", terminator: "")
}
print("")
// Prints "68 111 103 8252 55357 56374 "
Unicode標(biāo)量展示 (Unicode Scalar Representation)

遍歷unicodeScalars屬性來(lái)訪(fǎng)問(wèn)字符串的Unicode標(biāo)量展示。這個(gè)屬性是UnicodeScalarView類(lèi)型,是一個(gè)UnicodeScalar類(lèi)型的值的集合。

每個(gè)UnicodeScalar有一個(gè)value屬性,這個(gè)屬性返回標(biāo)量的21-bit值。

Unicode Scalar Representation
for scalar in dogString.unicodeScalars {
    print("\(scalar.value) ", terminator: "")
}
print("")
// Prints "68 111 103 8252 128054 "

作為查詢(xún)value屬性的另外一種方法,每個(gè)UnicodeScalar的值可以用來(lái)構(gòu)建一個(gè)新的字符串,如字符串的插值:

for scalar in dogString.unicodeScalars {
print("\(scalar)")
}
// D
// o
// g
// ?
// ??

第三部分完。下個(gè)部分:【Swift 3.1】04 - 集合類(lèi)型 (Collection Types)


如果有錯(cuò)誤的地方,歡迎指正!謝謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容