字符串和子字符串如何在Swift中工作
这是我在Stack Overflow( 此处 和 此处 ) 上写的两个答案的综合 。
以下所有示例均使用
var str = "Hello, playground"
在Swift版本中,字符串发生了很大变化。 从Swift 4开始,当您从String中获取一些子字符串时,您将获得Substring
类型而不是String
。 为什么是这样? 字符串是Swift中的值类型。 这意味着,如果您使用一个String来创建一个新的String,则必须将其复制过来。 这有利于稳定(在您不知情的情况下,没有其他人会改变它),但不利于效率。
另一方面,子字符串是对原始字符串的引用。 无需复制,因此使用效率更高。 但是,假设您从一百万个字符的字符串中得到了十个字符的子字符串。 因为子字符串引用了字符串,所以只要子字符串存在,系统就必须保留整个字符串。 因此,无论何时完成操作子字符串,都将其转换为字符串。
let myString = String(mySubstring)
这将只复制子字符串,旧字符串可以被垃圾回收。 子字符串(作为一种类型)意味着寿命很短。
Swift 4的另一个重大改进是(再次),Strings是Collections。 这意味着无论您对集合执行什么操作,都可以对字符串执行操作(使用下标,遍历字符,过滤器等)。
在我们进一步研究子字符串之前,了解字符串索引对于组成字符串的字符的工作方式将很有帮助。
startIndex
和endIndex
-
startIndex
是第一个字符的索引 -
endIndex
是最后一个字符之后的索引。
例
var str = "Hello, playground"
// character
str[str.startIndex] // H
str[str.endIndex] // error: after last character// character
str[str.startIndex] // H
str[str.endIndex] // error: after last character// range
let range = str.startIndex..<str.endIndex
str[range] // "Hello, playground"
使用Swift 4的单边范围,范围可以简化为以下形式之一。
let range = str.startIndex...
let range = ..<str.endIndex
为了清楚起见,我将在以下示例中使用完整格式,但是为了便于阅读,您可能希望在代码中使用单边范围。
after
如在: index(after: String.Index)
-
after
是指在给定索引之后的字符索引。
例子
// character
let index = str.index(after: str.startIndex)
str[index] // "e"// range
let range = str.index(after: str.startIndex)..<str.endIndex
str[range] // "ello, playground"
before
如在: index(before: String.Index)
-
before
指的是字符在给定索引之前的索引。
例子
// character
let index = str.index(before: str.endIndex)
str[index] // d// range
let range = str.startIndex..<str.index(before: str.endIndex)
str[range] // Hello, playgroun
offsetBy
如在: index(String.Index, offsetBy: String.IndexDistance)
-
offsetBy
值可以为正也可以为负,并从给定索引开始。 尽管它的类型为String.IndexDistance
,但您可以给它一个Int
。
例子
// character
let index = str.index(str.startIndex, offsetBy: 7)
str[index] // p// range
let start = str.index(str.startIndex, offsetBy: 7)
let end = str.index(str.endIndex, offsetBy: -6)
let range = start..<end
str[range] // play
limitedBy
如在: index(String.Index, offsetBy: String.IndexDistance, limitedBy: String.Index)
-
limitedBy
对于确保偏移量不会导致索引超出范围很有用。 它是一个边界索引。 由于偏移量有可能超过限制,因此此方法返回Optional。 如果索引超出范围,则返回nil
。
例
// character
if let index = str.index(str.startIndex, offsetBy: 7, limitedBy: str.endIndex) {
str[index] // p
}
如果偏移量是77
而不是7
,则if
语句将被跳过。
您可以使用下标或许多其他方法(例如prefix
, suffix
, split
)从字符串中获取子字符串。 不过,您仍然需要使用String.Index
而不是Int
索引作为范围。
字符串的开头
您可以使用下标(请注意Swift 4的单面范围):
let index = str.index(str.startIndex, offsetBy: 5)
let mySubstring = str[..<index] // Hello
或prefix
:
let index = str.index(str.startIndex, offsetBy: 5)
let mySubstring = str.prefix(upTo: index) // Hello
甚至更简单:
let mySubstring = str.prefix(5) // Hello
字符串结尾
使用下标:
let index = str.index(str.endIndex, offsetBy: -10)
let mySubstring = str[index...] // playground
或suffix
:
let index = str.index(str.endIndex, offsetBy: -10)
let mySubstring = str.suffix(from: index) // playground
甚至更简单:
let mySubstring = str.suffix(10) // playground
请注意,使用suffix(from: index)
我必须使用-10
从末尾倒数。 仅使用suffix(x)
(仅接受String的最后x
字符suffix(x)
,这不是必需的。
字符串范围
同样,我们在这里仅使用下标。
let start = str.index(str.startIndex, offsetBy: 7)
let end = str.index(str.endIndex, offsetBy: -6)
let range = start..<endlet mySubstring = str[range] // play
将Substring
String
转换为String
别忘了,当您准备保存子字符串时,应将其转换为String
以便可以清理旧字符串的内存。
let myString = String(mySubstring)
对字符串使用Int
索引会容易得多 。 实际上,通过使用基于Int
的扩展,可以隐藏String索引的复杂性。 但是,在阅读了Airspeed Velocity和Ole Begemann撰写的文章Swift 3中的字符串后,我犹豫了。 另外,Swift团队故意没有使用Int
索引。 它仍然是String.Index
。 原因是Swift中的Characters在引擎盖下的长度并不相同。 一个Swift字符可能由一个,两个甚至更多个Unicode代码点组成。 因此,每个唯一的String必须计算其Characters的索引。
我必须说,我希望Swift团队将来能找到一种抽象String.Index
的方法。 但是直到他们我选择使用他们的API。 它可以帮助我记住String操作不只是简单的Int
索引查找。