基本概念
Strings 類似于集合,其由字形簇(Grapheme clusters)組成。
//遍歷字符
let string = "Swift"
for char in string {
print(char)
}
//字符串長度
let stringLength = string.count
/*
錯誤的訪問字符方式
涉及到字符串的不同編碼方式,以及字形合并等原因。
*/
let fourthChar = string[3] //error
字形簇 (Grapheme clusters)
字符由字形簇組成,相應的字形簇也是一個類集合。這其中有一個概念叫做合成字符,如 e?
,其邏輯上的等價字符由 e
和 ′
合成。相對應的 Unicode
碼如下:
e?
: 233
e
: 101
′
: 769
下面我們來嘗試一下,使用代碼怎么表示。
let normalE = "e?"
let combineE = "e\u{0301}"
//注意 \u{0301} 代表 `′` 十六進制換算下就明白了
normalE.count //1
combineE.count //1
/*
邏輯上 字符串 和 合成字符串 相等,他們對應于同一個字形。
Swift對于字符串的比較,使用了標準化。
在比較前,會先轉化為標準字形。
*/
let equal = normalE == combineE
/*
通過上面的方法,我們并沒有發現這兩個字符串有何不同。
但其實,系統提供了一個方式來訪問字形內部的簇族。
unicodeScalars: 碼點(codePoint)集合
*/
normalE.unicodeScalars.count //1
combineE.unicodeScalars.count //2
//碼點遍歷
for codePoint in normalE.unicodeScalars {
print(codePoint.value)
}
// 233
combineE.unicodeScalars {
print(codePoint.value)
}
// 101
// 769
通過結果,我們看到字形 和 其合成字形,并不完全等價。這一點從其構成的碼點集合,可以看出來。這種差別,在我們操作或訪問編碼層級的時候尤其要注意!!!
字符串下標索引
上面提到了字符串是一種類集合,但我們在使用下面方式訪問字符時卻出現了錯誤。
let strTitle = "Swift"
let fourthChar = strTitle[3]
/*
error: 'subscript' is unavailable:
cannot subscript String with an Int,
see the documentation comment for discussion
*/
可以看到,并不是不支持下標語法,只是不是Int型下標索引。正確的類型是 String.Index
,下面看示例。這一塊的概念有些纏繞...
獲取首個字符:
//獲取字符串起始下標
let firstIndex = strTitle.startIndex
/*
通過這個下標,我們可以讀取對應該下標的字符。
注意沒有細致到碼點層級,只是單個字符。
*/
let firstChar = strTitle[firstIndex]
// S
獲取末尾字符:
//末尾下標
let lastIndex = strTitle.endIndex
//末尾字符
let lastChar = strTitle[lastIndex]
/*
fatal error: Can't form a Character from an empty String
發生了錯誤,因為集合類型都是zero-index.
糾正如下:
*/
let lastIndex = strTitle.index(before: strTitle.endIndex)
let lastChar = strTitle[lastIndex]
// t
獲取其它位置的字符:
let secondIndex = strTitle.index(strTitle.startIndex, offsetBy: 1)
let secondChar = strTitle[secondIndex]
// w
字符串反置
字符串作為一個有序的字符集合,系統也提供了翻轉方法。
let name = "Swift"
var backwardsName = name.reversed()
/*
這里的返回值類型,ReversedCollection<String>
這背后隱藏了一個并不透明的機制,該返回值是和原字符串內存有關聯的。
*/
//生成字符串
let backwardsNameString = String(backwardsName)
//注意:如果我們改變了原字符串
backwardsName = ""
/*
會得到如下錯誤
Fatal error:
Can't form a Character from an empty String
至于為什么,歡迎討論、拍磚。
*/
字符串截取(Substrings)
在字符串使用過程中,通常會有截取的操作。那么回顧上文中提及的 String.Index
下標索引,以及區間的概念,截取操作如下:
let fullName = "Tom Green"
//注意返回值是一個可選型
let spaceIndex = fullNmae.index(of: " ")!
let firstName = fullName[fullName.startIndex..<spaceIndex] // "Tom"
/*
Open-ended range
只標明一個索引的區間
*/
let firstName = fullName[..<spaceIncexf] // "Tom"
let lastName = fullName[fullName.index(after: spaceIndex)...] // "Green"
//注意Substring 操作返回值類型是String.Sequence
//所以想生成字符串的話
let lastNameString = String(lastName)
編碼 (Encoding)
這部分內容有些生澀僅做提及,建議額外詳查資料。
Strings
是由 Unicode code points
構成的集合,這些 code points
的范圍是 0 ~ 1114111(0x10FFFFF 十六進制)。這也就是說,需要21bits 才能夠表示這個范圍。如果你只需要用到低范圍的 code points
(像拉丁字母),那么可以只用8bits 來標識每一個code point.
大多數編程語言中數值類型都是可尋址且存儲空間具有一定規則的,計算機只能處理以bit(1位_表示0 或 1)為基礎的數據,常見的像8-bits,16-bits,32-bits。
當去存儲 Strings
類型數據時,可以規定每個碼點(code point)使用32-bits來表示,如UInt32。這些UInt32稱為碼元(code unit),而這個表示過程就是 Strings 的編碼(encoding),專業的名詞被稱為UTF-32。
類似的還有UTF-8 、UTF-16,感興趣的可以詳查資料。
//系統提供了訪問 編碼層級的API
//UTF-8
let char = "\u{00bd}"
for i in char.utf8 {
//輸出二進制形式
print("\(i): \(String(i, radix: 2))")
}
res:
194: 11000010
189: 10111101
//UTF-16
for i in char.utf16 {
print("\(i) : \(String(i, radix: 2))")
}
res:
189 : 10111101