你已经了解了String是表示文本的类型。文本是一种非常常见的数据类型:人名;地址;一句话。所有这些都可能是应用程序需要处理的。所以我们需要更深入的了解字符串如何工作以及它能做什么。
这一章会加深你对字符串的理解,更确切地说是了解字符串如何在Swift中工作的。Swift是少数能够正确处理Unicode字符的语言之一,同时可预测性最大(不懂什么意思)。
Strings as collections(字符串集合)
在第2章中,你学习了字符串的基础知识,并学习了字符集和代码点。他们将映射数字定义为它所代表的字符。现在让我们进一步研究字符串类型。
将字符串概念化为字符的集合是非常容易的。因为字符串本来就是集合,像下面这样:
let string = "Matt"
for char instring {
print(char)
}
分别打印出Matt的每个字符。很简单,是吗?你还可以使用其他的操作,例如:
let stringLength = string.count
这将得到string的长度。
现在假设你想要获得字符串中的第四个字符。你可以这样做。
let fourthChar = string[3]
但是,如果你这样做,你将收到以下错误消息:
'subscript' is unavailable: cannot subscriptString with an Int,
see the documentation comment for discussion
这是为什么呢?因为他引入了一个字集群。
Grapheme clusters字集群
如你所知,字符串由Unicode字符的集合组成。到目前为止,你认为一个代码点恰好等于一个字符,反之亦然。然而,“字符”这个词是相当宽松的。
比如有两种方式可以代表某些字符。其中的一个例子就是café里的é ,这是一个带有声调的e。你可以用一个字符或两个字符来表示这个字符。
单个字符是代码点233。两个字符是一个单独的e,后面跟着一个声调字符,两个字符结合成一个字符,这是一个特殊的字符,他修改了前面的字符。
QQ20180427-174854@2x.png在第二个图中,这两个字符的组合构成了一个由Unicode标准定义的字形集群。当你想到一个字符时,你实际上可能会想到一个字形集群。字形集群由Swift类型字符表示。
组合字符的另一个例子是用来改变某些表情符号的皮肤颜色。例如:
QQ20180427-174943@2x.png在这里,竖起大拇指的表情符号后面结合了肤色字符。在支持它的平台上,包括iOS和macOS,呈现的表情是一个单拇指向上的字符,颜色是肤色。
现在让我们来看看当字符串被用作集合时,这意味着什么。思考下面的代码:
let cafeNormal = "café"
let cafeCombining = "cafe\u{0301}"
cafeNormal.count // 4
cafeCombining.count // 4
这两项都等于4。这是因为Swift将一个字符串视为一个grapheme集群的集合。聪明的读者也会注意到,这意味着找出一个字符串的长度需要一点时间,因为你需要遍历所有的字符来确定有多少个grapheme集群。你不能简单地从查看内存中字符串的大小来判断。
注意:在上面的代码中,使用Unicode速记法编写了字符的音,就是在括号中使用十六进制的代码点。你可以使用这个简写来编写任何Unicode字符。我必须在这里用它来组合字符,因为没有办法在键盘上键入这个音调字符!
但是,有一种方法可以访问字符串中的底层Unicode代码点。你可以通过字符串的unicodeScalars视图来完成此操作。视图本身也是一个集合。你可以这样做:
cafeNormal.unicodeScalars.count // 4
cafeCombining.unicodeScalars.count // 5
正如你所看到的,在这种情况下,你看到与你所期望的不一样。
你可以像这样遍历Unicode标量视图:
for codePoint incafeCombining.unicodeScalars {
print(codePoint.value)
}
这将按预期打印下列数字列表:
99
97
102
101
769
字符串索引
正如你前面看到的,为获得某个字符(意思是grapheme集群)索引到一个字符串,并不像使用整数下标那样简单。这是因为Swift想让你了解更详细,从而语法会更加冗长。
根据字符串索引进行操作,其索引为字符串。例如,你获得表示字符串开头的索引:
let firstIndex = cafeCombining.startIndex
如果你在playground上点击firstIndex,你会注意到它是string类型的。字符串的索引并不是整数。
然后,你可以使用该值来获取该索引中的字符(grapheme集群),
像这样:
let firstChar = cafeCombining[firstIndex]
在这种情况下,firstChar当然是“c”。这个值的类型是字符,一个字母集群。
类似地,你可以像这样获得最后的grapheme集群:
let lastIndex = cafeCombining.endIndex
let lastChar = cafeCombining[lastIndex]
但是如果你这样做,你会犯一个致命的错误:
fatal error: Can't form a Characterfrom an empty String
因为endIndex实际上是在字符串的末尾,获取最后一个字符
你需要这样做:
let lastIndex = cafeCombining.index(before: cafeCombining.endIndex)
let lastChar = cafeCombining[lastIndex]
先获取一个索引之前的索引,然后在该索引中获取字符。或者你也可以从第一个字符移动:
let fourthIndex = cafeCombining.index(cafeCombining.startIndex, offsetBy:
3)
let fourthChar = cafeCombining[fourthIndex]
在这种情况下第四个字符是é。
但正如你所知,这个例子中的é是由多个代码点组成的。你可以通过unicodeScalars视图,以同样的方式访问字符类型上的这些代码点。所以你可以这样做:
fourthChar.unicodeScalars.count // 2
fourthChar.unicodeScalars.forEach { codePoint in
print(codePoint.value)
}
这一次,你将使用forEach函数遍历Unicode标量视图。count是2,按照预期,循环输出:
101
769
相等的组合字符
组合字符使字符串的相等比较变得更复杂。例如,思考café́这个词,一旦使用单一é字符,和使用组合字符,如下所示:
QQ20180427-175918@2x.png这两个字符串在逻辑上是相等的。当它们在屏幕上打印时,它们使用相同的符号,看起来完全一样。但它们在电脑中以不同的方式表现出来。许多编程语言都认为这些字符串是不相等的,因为这些语言的工作方式是逐个地比较代码。然而,Swift认为这些字符串在默认情况下是相等的。让我们来看看它的作用:
let equal= cafeNormal == cafeCombining
在这种情况下,equal是真,因为这两个字符串在逻辑上是相同的。
Swift的字符串比较使用了一种称为规范化的技术。在检查相等之前,Swift将两个字符串规范化,这意味着它们被转换为使用相同的特殊字符表示。
不管用哪种方式进行规范化——使用单个字符还是使用组合字符——只要两个字符串都转换为相同的样式就行了。一旦规范化完成,Swift可以比较单个字符来检查是否相等。
相同的规范化可以计算字符串长度,前面café和组合字符串的长度就是相等的。
网友评论