基本概念
Strings 类似于集合,其由字形簇(Grapheme clusters)组成。
//遍历字符
let string = "Swift"
for char in string {
print(char)
}
//字符串长度
let stringLength = string.count
/*
错误的访问字符方式
涉及到字符串的不同编码方式,以及字形合并等原因。
*/
let fourthChar = string[3] //error
字形簇 (Grapheme clusters)
字符由字形簇组成,相应的字形簇也是一个类集合。这其中有一个概念叫做合成字符,如 é
,其逻辑上的等价字符由 e
和 ′
合成。相对应的 Unicode
码如下:
é
: 233
e
: 101
′
: 769
下面我们来尝试一下,使用代码怎么表示。
let normalE = "é"
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