我正在为String创建扩展,我正在尝试确定下标运算符的正确/预期/良好行为。目前,我有这个:
// Will crash on 0 length strings
subscript(kIndex: Int) -> Character {
var index = kIndex
index = index < 0 ? 0 : index
index = index >= self.length ? self.length-1 : index
let i = self.startIndex.advancedBy(index)
return self.characters[i]
}
这会导致字符串范围之外的所有值都被限制在字符串的边缘。虽然这可以减少将错误索引传递给下标的崩溃,但这并不是正确的做法。我无法从下标中抛出异常,如果索引超出范围,则不检查下标会导致BAD_INSTRUCTION
错误。我能想到的唯一其他选择是返回一个可选项,但这看起来很尴尬。权衡选项,我所拥有的似乎是最合理的,但我认为任何使用它的人都不会期望错误的索引返回有效的结果。
所以,我的问题是:下标运算符的“标准”预期行为是什么,并且从可接受/适当的无效索引返回有效元素?感谢。
答案 0 :(得分:1)
如果您要在String
上实施下标,您可能需要先考虑标准库选择的原因。
当你致电self.startIndex.advancedBy(index)
时,你实际上是在写这样的东西:
var i = self.startIndex
while i < index { i = i.successor() }
这是因为String.CharacterView.Index
不是随机访问索引类型。请参阅advancedBy
上的文档。字符串索引不是随机访问,因为字符串中的每个Character
可能是字符串底层存储中的任意数量的字节 - 您不能通过跳转{{1来获取字符 n 像C字符串一样进入存储。
因此,如果要使用下标运算符迭代字符串中的字符:
n * characterSize
...你有一个看起来的循环,就像它在线性时间运行一样,因为它看起来就像一个数组迭代 - 每次循环都应该花费相同的时间,因为每个只增加for i in 0..<string.characters.count {
doSomethingWith(string[i])
}
并使用常量访问来获取i
,对吧?不。首次通过循环的string[i]
调用会调用advancedBy
一次,下一次调用它,依此类推......如果您的字符串有 n 个字符,则最后一次调用通过循环调用successor
n 次(即使它生成了在调用successor
n-1时在前一次循环中使用的结果/ em>次)。换句话说,你刚刚做了一个看起来像O(n)操作的O(n 2 )操作,为其他人使用你的代码留下了性能成本炸弹。
这是完全支持Unicode的字符串库的价格。
无论如何,要回答你的实际问题 - 下标和域名检查有两种思路:
有一个可选的返回类型:successor
当客户端没有合理的方法来检查索引是否有效而没有执行与查找相同的工作时,这是有道理的 - 例如对于字典,找出 if 给定键的值与找出键的值
要求索引有效,否则会发生致命错误。
通常情况下,API的客户端可以并且应该在访问下标之前检查其有效性。这就是Swift数组所做的事情,因为数组知道它们的计数,你不需要查看数组来查看索引是否有效。
对此的规范测试是func subscript(index: Index) -> Element?
:例如
precondition
(此处,func subscript(index: Index) -> Element {
precondition(isValid(index), "index must be valid")
// ... do lookup ...
}
是您的类专用于验证索引的一些操作 - 例如确保它的&gt; 0和&lt; count。)
在几乎任何一个用例中,不是惯用的Swift在错误索引的情况下返回“真实”值,也不适合返回一个sentinel值 - 将带内值与哨兵分开是Swift的原因有选择权。
其中哪一个更适合您的用例......好吧,因为您的使用案例存在问题,这是一种洗漱。如果您isValid
该索引&lt; count,你仍然需要花费O(n)成本才能检查(因为precondition
必须检查其内容,以确定在知道每个字符有多少字符之前构成每个字符的字节序列。如果您将返回类型设为可选,并在致电String
或advancedBy
后返回nil,则仍然会产生O(n)费用。