我已经在Swift 5中阅读了有关String
和 Unicode 的文档,但无法理解为什么我们无法从Character
获得String
为:
let someString = "????"
let oneCharacter = someString[2] // Error
为什么我们应该使用更复杂的方法来获取Character
?
let strawberryIndex = someString.index(someString.startIndex, offsetBy: 2) // String.Index type
someString[strawberryIndex] // Character(?)
使用String.Index类型有什么意义?
答案 0 :(得分:2)
首先,您不能将Int用作字符串的索引。该接口需要String.Index。
为什么?我们使用的是Unicode,而不是ASCII。 Swift字符串的单位是一个字符,即“字形簇”。一个字符可以包含多个Unicode代码点,每个Unicode代码点可以包含1到4个字节。
现在,假设您有10兆字节的字符串,并进行了搜索以找到子字符串“ Wysteria”。您是否要返回字符串以哪个字符开头?如果它是字符123,456,那么要再次找到相同的字符串,我们必须从字符串的开头开始,然后分析123,456个字符以找到该子字符串。那是疯狂的低效。
相反,我们得到一个String.Index,它使Swift可以快速定位该子字符串。它很可能是字节偏移,因此可以非常快速地对其进行访问。
现在在该字节偏移量上添加“ 1”是无意义的,因为您不知道第一个字符有多长时间。 (Unicode很有可能具有另一个等于ASCII'W'的字符)。因此,您需要调用一个返回下一个字符的索引的函数。
您可以编写代码以返回字符串中的第二个字符。要返回百万分之一的角色需要花费大量时间。 Swift不允许您执行效率极低的事情。
答案 1 :(得分:2)
由于多个原因,Swift抽象了字符串索引。据我所知,主要目的是使人们 stop 认为自己只是整数。在表面之下,它们是,但是它们的行为与人们最初的期望背道而驰。
我们对String编码的期望通常以英语为中心。 ASCII通常是人们开始学习的第一个字符编码,并且通常以某种借口说它在某种程度上是最受欢迎的或最标准的,等等。
问题是,大多数用户不是美国人。他们是西欧人,他们的拉丁字母需要很多不同的口音,或者东欧人想要西里尔字母,或者中文用户具有很多不同的字符(over 74,000!,他们需要能够书写)。从来都不打算成为对所有语言进行编码的国际标准。美国标准协会创建了ASCII以对与美国市场相关的字符进行编码。其他国家也根据自己的需要进行了字符编码。
在与计算机的国际通信变得更加流行之前,已经使区域字符编码起作用。这些零散的字符编码不能互操作,从而导致各种乱码文本和用户混乱。需要有一个新的标准来统一它们,并允许在世界范围内进行标准化编码。
因此,Unicode被发明为统治一切的一环。单个代码表包含所有语言的所有字符,并为将来的扩展留有足够的空间。
在ASCII中,可能有127个字符。字符串中的每个字符都被编码为单个8位字节。这意味着对于一个n
字符串,您正好有n
个字节。像任何数组下标一样,下标以获得第i
个字符是简单的指针算法问题。
address_of_element_i =基本地址+(size_of_each_element * i)
size_of_each_element
仅是1(字节),这进一步减少到base_address + i
。这确实非常快,而且有效。
许多字符(大多数?)编程语言的标准库中,这种每字符1个字节的ASCII质量使API设计知道了字符串类型。即使ASCII是“默认”编码的错误选择(已经有几十年的历史了),但到Unicode普遍存在时,损害已经造成了。
用户认为是字符的字符在Unicode中称为“扩展字素簇”。它们是基本字符,可以选择后面跟任意数量的连续字符。假设许多语言都是基于“ 1字符等于1字节”这一假设来实现的。
字符认为字节是在Unicode世界中被破坏。不是“哦,它已经足够好了,我们在扩展到国际市场时会担心它”,但绝对是完全不可行的。大多数用户不会说英语。英文用户使用表情符号。从ASCII建立的假设不再起作用。以Python 2.7为例,它可以正常工作:
>>> s = "Hello, World!"
>>> print(s)
Hello, World!
>>> print(s[7])
W
这不是:
>>> s = "????"
>>> print(s)
????
>>> print([2])
[2]
>>> print(s[2])
�
在Python 3中,引入了一项重大更改:索引现在表示代码点,而不是字节。因此,现在上面的代码“按预期”工作,打印?
。但这还不够。多代码点代码仍被破坏,例如:
>>> s = "A????Z"
>>> print(s[0])
A
>>> print(s[1])
?
>>> print(s[2]) # Zero width joiner
>>> print(s[3])
?
>>> print(s[4])
>>> print(s[5])
?
>>> print(s[6])
>>> print(s[7])
?
>>> print(s[8])
Z
>>> print(s[9]) # Last index
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: string index out of range
Swift可以轻松处理此问题:
1> let s = "A????Z"
s: String = "A????Z"
2> s[s.index(s.startIndex, offsetBy: +0)]
$R0: Character = "A"
3> s[s.index(s.startIndex, offsetBy: +1)]
$R1: Character = "????"
4> s[s.index(s.startIndex, offsetBy: +2)]
$R2: Character = "Z"
按字符下标在Unicode中很慢。从头开始,您被迫沿字符串移动,在行进时应用打破音素的规则,直到达到所需的计数为止。这是一个O(n)
进程,与ASCII情况下的O(1)
不同。
如果此代码隐藏在下标运算符后面,则代码如下:
for i in 0..<str.count {
print(str[i])
}
可能看起来就像O(str.count)
(毕竟“只有一个for循环”,对吗?!),但实际上是O(str.count^2)
,因为每个{{1} }操作隐藏了字符串的线性遍历,这种遍历是一遍又一遍。
Swift的String API试图迫使人们远离直接索引,而转向不涉及手动索引的替代模式,例如:
str[i]
/ String.prefix
用于切掉字符串的开头或结尾以获取切片String.suffix
转换字符串中的所有字符Swift的String API尚未完全完成。有很多改善人体工程学的愿望/意图。
但是,人们习惯于编写的许多字符串处理代码完全是错误的。他们可能从未注意到,因为他们从未尝试过将其用于外语或表情符号。 String试图默认情况下是正确的,并且很难犯国际化错误。
答案 2 :(得分:1)
Apple不允许用整数下标字符串。
查看: Get nth character of a string in Swift programming language
答案 3 :(得分:1)
从其他人(和How does String.Index work in Swift)提供的链接/信息中可以看出,这与性能有关。
RandomAccessCollection确保“可以在O(1)时间内移动索引任何距离并测量索引之间的距离”。字符串不能做到这一点。
您可以执行此操作,它可以工作,但是会破坏合同。
extension RandomAccessCollection {
subscript(position: Int) -> Element {
self[index(startIndex, offsetBy: position)]
}
}
extension Substring: RandomAccessCollection { }
extension String: RandomAccessCollection { }
"????"[2] // "?"
但是,我建议这样!
public extension Collection {
/// - Complexity: O(`position`)
subscript(startIndexOffsetBy position: Int) -> Element {
self[index(startIndex, offsetBy: position)]
}
}
"????"[startIndexOffsetBy: 2]