如果您在 上实现下标String
,您可能需要首先考虑标准库选择不这样做的原因。
当你打电话时self.startIndex.advancedBy(index)
,你实际上是在写这样的东西:
var i = self.startIndex
while i < index { i = i.successor() }
这是因为String.CharacterView.Index
不是随机访问索引类型。请参阅 上的文档advancedBy
。字符串索引不是随机访问的,因为Character
字符串中的每个索引都可以是字符串底层存储中的任意数量的字节——你不能像使用 C 字符串那样通过跳转到存储中来获取字符n 。n * characterSize
因此,如果要使用您的下标运算符来遍历字符串中的字符:
for i in 0..<string.characters.count {
doSomethingWith(string[i])
}
...你会有一个看起来像在线性时间内运行的循环,因为它看起来就像一个数组迭代——每次通过循环应该花费相同的时间,因为每个循环都只是递增i
并使用一个常量——时间访问得到string[i]
,对不对?没有。第advancedBy
一次通过循环的调用调用successor
一次,下一次调用它两次,依此类推...如果您的字符串有n 个字符,则最后一次通过循环调用successor
n次(即使生成的结果在前一个在调用successor
n-1次时通过循环)。换句话说,你刚刚做了一个 O(n 2) 操作看起来像 O(n) 操作,给使用您的代码的其他人留下了性能成本炸弹。
这是一个完全支持 Unicode 的字符串库的价格。
无论如何,要回答您的实际问题 - 下标和域检查有两种思想流派:
有一个可选的返回类型:func subscript(index: Index) -> Element?
当客户端无法在不执行与查找相同的工作的情况下检查索引是否有效时,这是有道理的——例如对于字典,找出给定键的值是否与找出给定键的值相同。键的值是。
要求索引有效,否则产生致命错误。
通常情况下,您的 API 的客户端可以并且应该在访问下标之前检查有效性。这就是 Swift 数组所做的事情,因为数组知道它们的计数,并且您不需要查看数组来查看索引是否有效。
对此的规范测试是precondition
:例如
func subscript(index: Index) -> Element {
precondition(isValid(index), "index must be valid")
// ... do lookup ...
}
(这里,isValid
是一些特定于您的类的用于验证索引的操作——例如,确保它 > 0 并且 < 计数。)
在几乎任何用例中,在索引错误的情况下返回“真实”值都不符合 Swift 的习惯,返回哨兵值也不合适——将带内值与哨兵分开是 Swift 具有 Optionals 的原因。
其中哪一个更适合您的用例是......好吧,因为您的用例存在问题,所以它有点洗牌。如果您precondition
的索引 < 计数,您仍然会产生 O(n) 成本来检查它(因为 aString
必须检查其内容以确定哪些字节序列构成每个字符,然后才能知道它有多少个字符)。如果您将返回类型设为可选,并在调用advancedBy
or后返回 nil count
,您仍然会产生 O(n) 成本。