为 Swift 4 更新
Swift 范围比 NSRange 更复杂,而且在 Swift 3 中也没有变得更容易。如果您想尝试了解其中一些复杂性背后的原因,请阅读 this 和 this。我将向您展示如何创建它们以及何时可以使用它们。
封闭范围:a...b
这个range operator 创建一个包含元素a 和 元素b 的Swift 范围,即使b 是一个类型的最大可能值(例如Int.max) .有两种不同类型的封闭范围:ClosedRange 和 CountableClosedRange。
1。 ClosedRange
Swift 中所有范围的元素都是可比较的(即它们符合 Comparable 协议)。这允许您访问集合中范围内的元素。这是一个例子:
let myRange: ClosedRange = 1...3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c", "d"]
但是,ClosedRange 是不可数的(即,它不符合序列协议)。这意味着您不能使用 for 循环遍历元素。为此,您需要CountableClosedRange。
2。 CountableClosedRange
这与上一个类似,只是现在范围也可以迭代。
let myRange: CountableClosedRange = 1...3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c", "d"]
for index in myRange {
print(myArray[index])
}
半开范围:a..<b
此范围运算符包含元素a,但不包含元素b。和上面一样,有两种不同类型的半开范围:Range 和 CountableRange。
1。 Range
与ClosedRange 一样,您可以使用Range 访问集合的元素。示例:
let myRange: Range = 1..<3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]
同样,您不能迭代 Range,因为它只是可比较的,而不是可跨步的。
2。 CountableRange
CountableRange 允许迭代。
let myRange: CountableRange = 1..<3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]
for index in myRange {
print(myArray[index])
}
NSRange
你可以(必须)在 Swift 中有时仍然使用 NSRange(例如,在制作 attributed strings 时),所以知道如何制作是很有帮助的。
let myNSRange = NSRange(location: 3, length: 2)
注意这是位置和长度,而不是开始索引和结束索引。此处的示例与 Swift 范围 3..<5 的含义相似。但是,由于类型不同,它们是不可互换的。
字符串范围
... 和 ..< 范围运算符是创建范围的简写方式。例如:
let myRange = 1..<3
创建相同范围的常用方法是
let myRange = CountableRange<Int>(uncheckedBounds: (lower: 1, upper: 3)) // 1..<3
可以看到这里的索引类型是Int。不过,这不适用于String,因为字符串是由字符组成的,而且并非所有字符的大小都相同。 (阅读this 了解更多信息。)例如,像? 这样的表情符号比字母“b”占用更多空间。
NSRange 的问题
尝试使用 NSRange 和带有表情符号的 NSString 进行试验,您就会明白我的意思了。头痛。
let myNSRange = NSRange(location: 1, length: 3)
let myNSString: NSString = "abcde"
myNSString.substring(with: myNSRange) // "bcd"
let myNSString2: NSString = "a?cde"
myNSString2.substring(with: myNSRange) // "?c" Where is the "d"!?
笑脸需要两个 UTF-16 编码单元来存储,所以它给出了不包括“d”的意外结果。
快速解决方案
因此,对于 Swift 字符串,您使用 Range<String.Index>,而不是 Range<Int>。字符串索引是根据特定字符串计算的,以便它知道是否有任何表情符号或扩展字素簇。
例子
var myString = "abcde"
let start = myString.index(myString.startIndex, offsetBy: 1)
let end = myString.index(myString.startIndex, offsetBy: 4)
let myRange = start..<end
myString[myRange] // "bcd"
myString = "a?cde"
let start2 = myString.index(myString.startIndex, offsetBy: 1)
let end2 = myString.index(myString.startIndex, offsetBy: 4)
let myRange2 = start2..<end2
myString[myRange2] // "?cd"
单边范围:a... 和 ...b 和 ..<b
在 Swift 4 中,事情被简化了一点。只要可以推断出范围的起点或终点,您就可以不使用它。
内部
您可以使用单边整数范围来迭代集合。以下是来自documentation 的一些示例。
// iterate from index 2 to the end of the array
for name in names[2...] {
print(name)
}
// iterate from the beginning of the array to index 2
for name in names[...2] {
print(name)
}
// iterate from the beginning of the array up to but not including index 2
for name in names[..<2] {
print(name)
}
// the range from negative infinity to 5. You can't iterate forward
// over this because the starting point in unknown.
let range = ...5
range.contains(7) // false
range.contains(4) // true
range.contains(-1) // true
// You can iterate over this but it will be an infinate loop
// so you have to break out at some point.
let range = 5...
字符串
这也适用于字符串范围。如果您在一端使用str.startIndex 或str.endIndex 创建范围,则可以将其关闭。编译器会推断出来。
给定
var str = "Hello, playground"
let index = str.index(str.startIndex, offsetBy: 5)
let myRange = ..<index // Hello
您可以使用...从索引转到str.endIndex
var str = "Hello, playground"
let index = str.index(str.endIndex, offsetBy: -10)
let myRange = index... // playground
另见:
备注
- 您不能使用由一个字符串在不同字符串上创建的范围。
- 如您所见,字符串范围在 Swift 中是一个难题,但它们确实可以更好地处理表情符号和其他 Unicode 标量。
进一步研究