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

字符串和字符 (Strings and Characters)

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


字符串字面值 (String Literals)

使用字符串字面值來初始化一個常量或者變量:

let someString = "Some string literal value"

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

初始化一個空字符串

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

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

通過檢查字符串的布爾類型屬性isEmpty來判斷一個字符串是否為空:

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

字符串的可變性 (String Mutability)

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

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

let constatString = "Highlander"
constantString += "and another Highlander"
// 會產(chǎn)生一個編譯錯誤,因為constantString是一個常量,不能被改變

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

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

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

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

與字符交互 (Working with Characters)

使用for-in循環(huán)遍歷字符串的characters屬性來訪問單個字符:

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

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

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

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

使用加號運算符(+)來拼接兩個字符串:

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

使用加法賦值運算符來拼接一個已經(jīng)存在字符串:

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

使用String類型的append()方法來拼接:

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

注意:不能添加一個String或者Character到一個已經(jīng)存在的Character變量,因為Character類型的值只能包含一個字符。

字符串插值 (String Interpolation)

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

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

Unicode

Unicode是在不同系統(tǒng)中編碼、展示和處理文本的國際標(biāo)準(zhǔn)??梢宰屛覀円試H化的形式在不同的語言中展示幾乎任何字符,從外部文件(例如text文件或網(wǎng)頁)讀取那些字符或者把那些字符寫到外部文件。Swift的StringCharacter類型是完全兼容Unicode的。

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

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

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

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

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

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

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

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

例如,字母é代表這個Unicode標(biāo)量é(LATIN SMALL LETTER E WITH ACUTE, 或者 U+00E9)。然而這個字母還可以代表一對標(biāo)量:一個標(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 é

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

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

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

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

代表區(qū)域標(biāo)志符號的Unicode標(biāo)量還可以結(jié)成對,形成一個Character字符。例如這個組合: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

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

使用字符串的characterscount屬性來后去字符的個數(shù):

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

注意:在Swift中,使用擴展字形集群形成的字符,在拼接和修改字符串時不一定會影響字符的個數(shù)。例如:初始化一個字符串cafe,接著在后面追加COMBINING ACUTE ACCENT (U+0301),最終得到的字符串還是只有4個字符,第四個字符是é,不是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"

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

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

訪問和修改字符串 (Accessing and Modifying a String)

字符串索引 (String Indices)

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

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

使用startIndex來訪問字符串的第一個字符的位置,endIndex是字符串最后一個字符的下一個位置。所以,endIndex不是一個有效的字符串索引。如果一個字符串是空的,那么startIndexendIndex是相等的。

使用index(before:)index(after:)來訪問索引;使用index(_:offsetBy:)來訪問離一個給定的索引一定距離的索引。

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

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

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

使用charactersindices屬性來訪問字符串中每一個字符的索引:

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

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

插入和移除 (Inserting and Removing)

在特定的位置插入一個字符,使用insert(_:at:);在特定的位置插入另外一個字符串內(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!

在特定的位置刪除一個字符,使用remove(at:),刪除一個范圍內(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é)議的任何類型中使用insert(_:at:)insert(contentsOf:at:)、remove(at:)removeSubrange(_:)方法,包括:String、Array、Dictionary、Set。

字符串的比較 (Comparing Strings)

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

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

使用等于==和不等于!=進行比較:

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"

如果兩個字符串或者兩個字符的擴展字形集群正則等價,那么這兩個字符串或者字符相等。如果兩個字符串或者兩個字符有相同的語言意義和外觀,那么他們的擴展字形集群相等,即使他們是不同的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"

相反,英語的LATIN CAPITAL LETTER A(U+0041, 或者 "A"),不等于俄語的CYRILLIC CAPITAL LETTER 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ū)域不敏感。

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

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

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

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

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

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

UTF-8 展示 (UTF-8 Representation)

遍歷utf8屬性來訪問字符串的UTF-8展示,這個屬性是String.UTF8View類型,是無符號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屬性來訪問字符串的UTF-16展示,這個屬性是String.UTF16View類型,是無符號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屬性來訪問字符串的Unicode標(biāo)量展示。這個屬性是UnicodeScalarView類型,是一個UnicodeScalar類型的值的集合。

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

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

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

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

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


如果有錯誤的地方,歡迎指正!謝謝!

最后編輯于
?著作權(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)容