iOS一点点 - Swift 标准库中的 String

2016-01-03 13:12

参考资料

Swift Standard Library Reference 主体为对该链接 String 部分理解基础上的翻译,但有较大改动且加入更多个人见解与扩展内容

Unicode and you by BetterExplained BetterExplained对于Unicode的解释,随手找的

:imp::dog: ~ emoji unicode characters ~ :fire::angel:

所有我不能确定标准与否的术语翻译,均会在第一次出现时于括号内标注原文。

可能稍显啰嗦,我是希望能够借助尽量 细致、直观、全面、有理有据 的讲解,来提升自己的理解,相信也能帮到其他人。我很喜欢读这种态度的文章,也希望自己的文章能有这种水平,希望你也喜欢。

正文

Swift 标准库提供了 String 文本类型,适用 Unicode 文本。本文内容就是,如何对它进行定位(index)和切分(slice)。

先看下面的例子

let str = "Héllo, :us:laygr:open_mouth:und!"

let badRange = 4...12
// str[badRange]
// 取消上行的注释将会看到一个错误 “Subscript is unavailable: cannot subscript String with a range of Int”

不能用 Int 定义的 Range 范围来取子串,为毛? 字符串的第 n 个位置存第 n 个字 这样的逻辑有什么问题?为了理解 Swift 这样设计的目的,下面要简单扯下字符集。

C语言的字符串是这样的

01000001 01000010 01000011 01000100
A B C D

一个字节存一个字符,第 n 个字节存第 n 个字符,没问题。但是 Unicode 可以表示的字符很多,一个字节表示不完。于是要用更多字节表示一个字符,但 ASCII 中 ABCD 这些字符只要一个字节就够了,在这里也要统一用多个字节就会造成浪费。因此有了变长编码如 UTF-8 ,一些字符用一字节表示,另一些用多个字节。如字符串 "A:us:" ,utf-8的表示如下

01000001 11110000 …这里省略6个字节,呵呵呵… 10111000
A :us:

一个有趣的细节:

UTF-8 的 “A” 和 前面 ASCII 的 “A” 编码一致,都是 65 。实际上不止是 “A” ,UTF-8 是兼容 ASCII 的,所有 ASCII 内的字符的在 UTF-8 和 ASCII 中的表示都一样,也即都是占一个字节

另一个有趣的细节。。。:

UTF-8 一个字符使用的最多是 4 个字节而不是 8 个,“:us:” 符号其实是由两个地区标记符(regional indicator symbol letter)“u” 和 “s” 拼起来的,所以才用了 8 字节。

这里的地区标记符 “u” 和 “s” 不是英文字母,是专门用来拼装国家、地区标记的特殊字符。从它们占用了 4 个字节而不是 ASCII 的 1 字节这里也可以看出来区别

表示 “A” 只用一个字节,表示 “:us:” 却用了足足八个字节。这就破坏了上表中字节和字符一一对应的关系,数据结构中的第 n 位和字符串的第 n 个字符之间的对应关系没了。

前面我们说过 Swift 标准库提供的 String 用的是 Unicode ,现在再回去看前面那句报错 Subscript is unavailable: cannot subscript String with a range of Int 就知道了。不能用 Int 指定的范围来定位、切分字符串的原因就是因为,由于使用了变长编码,导致 String 的数据结构的第 n 个元素,不是我们要的第 n 个字符。(姑且先这么说吧)

String 中要定位、切分字符串,需要使用 String.Index 对象提供的一系列方法,它们会确保操作以字符为单位进行,不会出现让你把一个多字节字符砍成两半的问题:

// successor() 下一个字符
str[str.startIndex]                            // "H"
str[str.startIndex.successor()]                // "é"
str[str.startIndex.successor().successor()]    // "l"

// predecessor() 上一个字符
str[str.endIndex.predecessor()]                // "!"
str[str.endIndex.predecessor().predecessor()]  // "d"

// advancedBy(Int) 与按给定次数执行前两个方法效果相同
str[str.startIndex.advancedBy(7)]              // 与执行七次 successor() 效果相同 ":us:"
str[str.endIndex.advancedBy(-7)]               // 与执行七次 predecessor() 效果相同 "g"

// 此处创建的 Range 是 Range<Index> 类型,而非 Range<Int>
let range = str.startIndex.advancedBy(4)...str.startIndex.advancedBy(12)
str[range]                                     // "o, :us:laygr"

上面是相关的常用用法。

至此,这部分的学习就暂时告一段落了,关于 String 的其它内容,以后遇到再谈。