我一直在更新我的一些旧代码和答案与Swift 3,但当我得到Swift字符串和索引子字符串的事情变得令人困惑。

具体来说,我尝试了以下几点:

let str = "Hello, playground"
let prefixRange = str.startIndex..<str.startIndex.advancedBy(5)
let prefix = str.substringWithRange(prefixRange)

第二行给出了如下错误

String类型的值没有成员substringWithRange

我看到String现在确实有以下方法:

str.substring(to: String.Index)
str.substring(from: String.Index)
str.substring(with: Range<String.Index>)

这些一开始让我很困惑,所以我开始摆弄索引和范围。这是子字符串的后续问题和答案。我在下面添加了一个答案来说明它们是如何使用的。


当前回答

我的思维很机械。以下是一些基本常识……

斯威夫特4 斯威夫特5

  let t = "abracadabra"

  let start1 = t.index(t.startIndex, offsetBy:0)
  let   end1 = t.index(t.endIndex, offsetBy:-5)
  let start2 = t.index(t.endIndex, offsetBy:-5)
  let   end2 = t.index(t.endIndex, offsetBy:0)

  let t2 = t[start1 ..< end1]
  let t3 = t[start2 ..< end2]                

  //or a shorter form 

  let t4 = t[..<end1]
  let t5 = t[start2...]

  print("\(t2) \(t3) \(t)")
  print("\(t4) \(t5) \(t)")

  // result:
  // abraca dabra abracadabra

结果是一个子字符串,这意味着它是原始字符串的一部分。要得到一个完整的独立字符串,只需使用e.g.。

    String(t3)
    String(t4)

这是我所使用的:

    let mid = t.index(t.endIndex, offsetBy:-5)
    let firstHalf = t[..<mid]
    let secondHalf = t[mid...]

其他回答

斯威夫特4

在swift 4字符串符合集合。现在应该使用下标,而不是substring。所以如果你只想从“Hello, playground”中删除单词“play”,你可以这样做:

var str = "Hello, playground"
let start = str.index(str.startIndex, offsetBy: 7)
let end = str.index(str.endIndex, offsetBy: -6)
let result = str[start..<end] // The result is of type Substring

有趣的是,这样做会给你一个Substring而不是String。这是快速和有效的,因为Substring与原始字符串共享其存储空间。然而,以这种方式共享内存也很容易导致内存泄漏。

这就是为什么一旦您想要清理原始的String,就应该将结果复制到一个新的String中。你可以使用普通的构造函数:

let newString = String(result)

你可以在[Apple文档].1中找到更多关于新的Substring类的信息

所以,如果你得到一个范围作为nsregularexexpression的结果,你可以使用以下扩展:

extension String {

    subscript(_ range: NSRange) -> String {
        let start = self.index(self.startIndex, offsetBy: range.lowerBound)
        let end = self.index(self.startIndex, offsetBy: range.upperBound)
        let subString = self[start..<end]
        return String(subString)
    }

}

我的思维很机械。以下是一些基本常识……

斯威夫特4 斯威夫特5

  let t = "abracadabra"

  let start1 = t.index(t.startIndex, offsetBy:0)
  let   end1 = t.index(t.endIndex, offsetBy:-5)
  let start2 = t.index(t.endIndex, offsetBy:-5)
  let   end2 = t.index(t.endIndex, offsetBy:0)

  let t2 = t[start1 ..< end1]
  let t3 = t[start2 ..< end2]                

  //or a shorter form 

  let t4 = t[..<end1]
  let t5 = t[start2...]

  print("\(t2) \(t3) \(t)")
  print("\(t4) \(t5) \(t)")

  // result:
  // abraca dabra abracadabra

结果是一个子字符串,这意味着它是原始字符串的一部分。要得到一个完整的独立字符串,只需使用e.g.。

    String(t3)
    String(t4)

这是我所使用的:

    let mid = t.index(t.endIndex, offsetBy:-5)
    let firstHalf = t[..<mid]
    let secondHalf = t[mid...]

我最初的反应也一样。我也对语法和对象在每个主要版本中发生如此巨大的变化感到沮丧。

然而,我从经验中意识到,我总是在努力对抗“变化”的过程中最终遭受后果,比如处理多字节字符,如果你面对的是全球受众,这是不可避免的。

因此,我决定承认并尊重苹果工程师所付出的努力,并尽我所能,理解他们在想出这种“可怕”方法时的心态。

与其创建扩展,这只是一个让你的生活更容易的解决方案(我不是说他们是错误的或昂贵的),为什么不弄清楚字符串现在是如何设计工作的。

例如,我在Swift 2.2上有这样的代码:

let rString = cString.substringToIndex(2)
let gString = (cString.substringFromIndex(2) as NSString).substringToIndex(2)
let bString = (cString.substringFromIndex(4) as NSString).substringToIndex(2)

在放弃尝试使用相同的方法工作后,例如使用Substrings,我终于理解了将字符串作为双向集合的概念,为此我最终得到了这个版本的相同代码:

let rString = String(cString.characters.prefix(2))
cString = String(cString.characters.dropFirst(2))
let gString = String(cString.characters.prefix(2))
cString = String(cString.characters.dropFirst(2))
let bString = String(cString.characters.prefix(2))

我希望这有助于……

我对Swift的字符串访问模型感到非常沮丧:所有东西都必须是索引。我想要的只是使用Int访问字符串的第I个字符,而不是笨拙的索引和推进(这恰好随着每个主要版本的发布而改变)。所以我对String做了一个扩展:

extension String {
    func index(from: Int) -> Index {
        return self.index(startIndex, offsetBy: from)
    }

    func substring(from: Int) -> String {
        let fromIndex = index(from: from)
        return String(self[fromIndex...])
    }

    func substring(to: Int) -> String {
        let toIndex = index(from: to)
        return String(self[..<toIndex])
    }

    func substring(with r: Range<Int>) -> String {
        let startIndex = index(from: r.lowerBound)
        let endIndex = index(from: r.upperBound)
        return String(self[startIndex..<endIndex])
    }
}

let str = "Hello, playground"
print(str.substring(from: 7))         // playground
print(str.substring(to: 5))           // Hello
print(str.substring(with: 7..<11))    // play

下面的所有示例都使用

var str = "Hello, playground"

斯威夫特4

在Swift 4中,字符串进行了相当大的修改。当你从String中获取子字符串时,你得到的是substring类型而不是String类型。为什么会这样?字符串是Swift中的值类型。这意味着如果你使用一个字符串来创建一个新的字符串,那么它必须被复制。这有利于稳定性(没有人会在你不知情的情况下改变它),但不利于效率。

另一方面,Substring是返回到它所来自的原始String的引用。下面是文档中的一张图片说明了这一点。

不需要复制,所以使用起来更有效率。但是,假设您从100万个字符字符串中获得了10个字符的子字符串。因为Substring引用了String,只要Substring存在,系统就必须保留整个String。因此,当你完成对Substring的操作时,将其转换为String。

let myString = String(mySubstring)

这将只复制子字符串,保留旧字符串的内存可以被回收。子字符串(作为一种类型)意味着生命周期很短。

Swift 4的另一个重大改进是字符串是集合(再次)。这意味着你可以对Collection做什么,也可以对String做什么(使用下标、遍历字符、过滤器等)。

下面的例子展示了如何在Swift中获取子字符串。

获得子字符串

您可以通过使用下标或许多其他方法(例如,前缀、后缀、split)从字符串中获得子字符串。您仍然需要使用String。索引,而不是范围的Int索引。(如果你需要帮助,请参阅我的另一个答案。)

字符串的开头

你可以使用下标(注意Swift 4的单边范围):

let index = str.index(str.startIndex, offsetBy: 5)
let mySubstring = str[..<index] // Hello

或前缀:

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

或后缀:

let index = str.index(str.endIndex, offsetBy: -10)
let mySubstring = str.suffix(from: index) // playground

或者更简单:

let mySubstring = str.suffix(10) // playground

注意,当使用后缀(from: index)时,我必须使用-10从末尾开始计数。当只使用后缀(x)时,这是不必要的,它只接受字符串的最后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

将子字符串转换为字符串

不要忘记,当你准备保存你的子字符串时,你应该把它转换成一个字符串,这样旧的字符串的内存就可以被清理。

let myString = String(mySubstring)

使用Int索引扩展?

在阅读Airspeed Velocity和Ole Begemann的文章《Strings in Swift 3》后,我犹豫是否要使用基于Int的索引扩展。虽然在Swift 4中,字符串是集合,但Swift团队故意没有使用Int索引。它仍然是String.Index。这与Swift字符由不同数量的Unicode码点组成有关。实际索引必须为每个字符串唯一地计算。

我不得不说,我希望Swift团队能找到一种方法来抽象String。未来的指数。但在那之前,我选择使用他们的API。它帮助我记住String操作不仅仅是简单的Int索引查找。