字符串和子字符串如何在Swift中工作

这是我在Stack Overflow( 此处 此处 上写的两个答案的综合

以下所有示例均使用

var str = "Hello, playground" 

在Swift版本中,字符串发生了很大变化。 从Swift 4开始,当您从String中获取一些子字符串时,您将获得Substring类型而不是String 。 为什么是这样? 字符串是Swift中的值类型。 这意味着,如果您使用一个String来创建一个新的String,则必须将其复制过来。 这有利于稳定(在您不知情的情况下,没有其他人会改变它),但不利于效率。

另一方面,子字符串是对原始字符串的引用。 无需复制,因此使用效率更高。 但是,假设您从一百万个字符的字符串中得到了十个字符的子字符串。 因为子字符串引用了字符串,所以只要子字符串存在,系统就必须保留整个字符串。 因此,无论何时完成操作子字符串,都将其转换为字符串。

 let myString = String(mySubstring) 

这将只复制子字符串,旧字符串可以被垃圾回收。 子字符串(作为一种类型)意味着寿命很短。

Swift 4的另一个重大改进是(再次),Strings是Collections。 这意味着无论您对集合执行什么操作,都可以对字符串执行操作(使用下标,遍历字符,过滤器等)。

在我们进一步研究子字符串之前,了解字符串索引对于组成字符串的字符的工作方式将很有帮助。

startIndexendIndex

  • 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语句将被跳过。

您可以使用下标或许多其他方法(例如prefixsuffixsplit )从字符串中获取子字符串。 不过,您仍然需要使用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..<end
let 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索引查找。