字符串string)是字符的有序集合,如 "hello, world""albatross"。Swift 语言中的字符串由 String 类型表示,对应着 Character 类型值的集合。

Swift 的 StringCharacter 类型可高速处理文本,且兼容 Unicode 规范。创建与处理字符串的语法轻量且可读性强,与 C 语言的字符串语法相近。字符串的连接操作非常简单,只需将两个字符串用 + 运算符相加。字符串的值是否可变取决于它是常量还是变量,这点与 Swift 其他类型完全一样。

Swift 的 String 类型不仅语法简洁,还采用了高速的现代化字符串实现方案。 每个字符串由编码独立的 Unicode 字符组成,每个字符均支持以不同的 Unicode 表达形式访问。

Swift 的字符串还支持在较长的字符串中插入常量、变量、字面量以及表达式的值,该过程称为字符串内插。这使得显示、存储以及输出自定义的字符串值更为简便。

注:Swift 的 String 类型与底层 Foundation 框架的 NSString 类无缝衔接。如果你在用 Cocoa / Cocoa Touch 的 Foundation 框架,则除本章讲解的 String 功能以外,对创建的任何 String 值,均可调用到 NSString 类的全部 API。还可以将 String 值传递给任何要求输入 NSString 实例的 API 方法。

关于 String 与 Foundation / Cocoa 框架结合使用的更多信息,请见 Swift 与 Cocoa 及 Objective-C 的结合

字符串字面量

代码中可以以字符串字面量string literal)的形式嵌入预先定义的 String 值。字符串字面量是由一对双引号("")包围的文本字符的固定序列。

字符串字面量可用来为常量或变量提供初始值:

  1. let 某字符串 = "某字符串字面值"

注意,Swift 将 某字符串 这个常量推断为了 String 类型,因为它初始化时使用了字符串字面值。

字符串字面量可包含下述特殊字符:

  • 转义的特殊字符 \0(null 字符),\\ (反斜杠本身),\t(水平制表符),\n(换行符),\r(回车符),\"(双引号)以及 \'(单引号)

  • 单字节的 Unicode 标量,写作 \xnn,其中 nn 为两个十六进制数位

  • 双字节的 Unicode 标量,写作 \unnnn,其中 nnnn 为四个十六进制数位

  • 四字节的 Unicode 标量,写作 \Unnnnnnnn,其中 nnnnnnnn 为八个十六进制数位

下面的代码是书写这几种特殊字符的例子。wiseWords 常量包含两个经过转义的双引号字符。dollarSignblackHeart 以及 sparklingHeart 常量展示了 Unicode 标量字符的三种不同书写格式:

  1. let wiseWords = "\"Imagination is more important than knowledge\" - Einstein"
  2. // "Imagination is more important than knowledge" - Einstein
  3. let dollarSign = "\x24"        // $,Unicode 标量 U+0024
  4. let blackHeart = "\u2665"     // ♥,Unicode 标量 U+2665
  5. let sparklingHeart = "\U0001F496" // 💖,Unicode 标量 U+1F496

初始化一个空字符串

若要创建一个空的 String 值,作为构建较长字符串的第一步,你既可以将空字符串字面量赋值给一个变量,也可以用初始化语法初始化一个新的 String 实例:

  1. var 空字符串 = ""            // 空字符串字面量
  2. var 另一个空字符串 = String() // 初始化语法
  3. // 这两个字符串均为空,互为等同关系

要想知道 String 的值是否为空,可检查其 isEmpty 属性(布尔型):

  1. if 空字符串.isEmpty {
  2.     println("没有内容")
  3. }
  4. // 输出 "没有内容"

    字符串的可变性

要声明一个特定的 String 是否可以修改(即可变mutable),可将其赋值给一个变量(可以修改)或常量(不可修改):

  1. var 字符串变量 = "Horse"
  2. 字符串变量 += " and carriage"
  3. // 字符串变量 现在为 "Horse and carriage"
  4. let 字符串常量 = "Highlander"
  5. 字符串常量 += " and another Highlander"
  6. // 报告编译时错误 - 常量字符串不可修改

注:该实现方案与 Objective-C / Cocoa 的字符串可变性有所差异,后者通过选择实例所属的类(NSStringNSMutableString)来声明字符串是否可变。

字符串属于传值类型

Swift 的 String 类型是一种传值类型value type)。如果将一个 String 值传递给函数或方法,或将其赋值给一个常量或变量,则该 String 值会被复制过去。这两种情况均会为现有 String 值创建新的副本,实际传递或赋值的是其副本,而非原始实例。传值类型的说明请见 结构与枚举类型均为传值类型

注:该行为与 Cocoa 的 NSString 不同。Cocoa 的 NSString 实例在传递给函数或方法,或赋值给变量时,实际传递或赋值的是同一个 NSString引用。除非特别指定,期间不会发生复制字符串的操作。

Swift 中 String 的“默认复制”行为可确保函数或方法传递 String 值给你时,这个 String 值的确属于你,而与其调用方无关。可以肯定的是,除非你自己去修改它,你接收到的字符串绝对不会变。

在实现级别,Swift 的编译器会优化字符串的内存占用,仅在绝对需要时才会实际创建字符串的副本。因此,虽然字符串属于传值类型,你仍然总能达到最佳性能。

字符操作

Swift 的 String 类型表示 Character 值的有序集合。每个 Character 值代表单个 Unicode 字符。字符串中的各个 Character 值可以通过 for-in 循环遍历得到:

  1. for 字符 in "Dog!🐶" {
  2.     println(字符)
  3. }
  4. // D
  5. // o
  6. // g
  7. // !
  8. // 🐶

for-in 循环的说明请见 for 循环

还可以通过 Character 类型说明,用单字符的字符串字面量,单独创建 Character 常量或变量:

  1. let 日元符号: Character = "¥"

    字符数目统计

要获取字符串中字符的个数,可以调用全局函数 countElements,并将字符串作为唯一的参数传入:

  1. let 珍稀动物园 = "考拉 🐨, 蜗牛 🐌, 企鹅 🐧, 单峰骆驼 🐪"
  2. println("珍稀动物园 有 \(countElements(珍稀动物园)) 个字符")
  3. // 输出 "珍稀动物园 有 24 个字符"

注:不同的 Unicode 字符,以及同一个 Unicode 字符的不同表示,在内存中所需的存储空间可能不同。因此,要想计算出字符串的长度,必须遍历整个字符串,依次统计每一个字符。如果你在处理特别长的字符串值,要谨记,countElements 函数需要遍历整个字符串方可求出其精确的字符数目。

还需注意,countElements 返回的字符数目,与包含同样内容的 NSString 对象的 length 属性并不总是一样大。NSString 的长度根据该字符串的 UTF-16 形式的 16 位码元数得出,而非根据字符串内 Unicode 字符的个数得出。为了体现这一事实,在 Swift 语言中,NSStringlength 属性需通过 String 值的 utf16count 属性访问。

字符串与字符的连接

StringCharacter 值可以用加法运算符(+)加到一起(即连接concatenate),得到一个新的 String 值:

  1. let 字符串1 = "hello"
  2. let 字符串2 = " there"
  3. let 字符1: Character = "!"
  4. let 字符2: Character = "?"
  5. let 字符串加字符 = 字符串1 + 字符1       // 等于 "hello!"
  6. let 字符串加字符串 = 字符串1 + 字符串2   // 等于 "hello there"
  7. let 字符加字符串 = 字符1 + 字符串1       // 等于 "!hello"
  8. let 字符加字符 = 字符1 + 字符2          // 等于 "!?"

还可以用加法赋值运算符(+=)将 StringCharacter 值追加到现有 String 变量的末尾:

  1. var 说明 = "look over"
  2. 说明 += 字符串2
  3. // 说明 现在等于 "look over there"
  4. var 欢迎语 = "good morning"
  5. 欢迎语 += 字符1
  6. // 欢迎语 现在等于 "good morning!"

注:不能将 StringCharacter 追加到现有 Character 变量的末尾,因为 Character 的值只能包含一个字符。

字符串内插

字符串内插string interpolation)指,将常量、变量、字面量以及表达式的值插入字符串字面量中,并根据这些值构造新的 String 值。字符串字面量中插入的每一项均需用一对括号包围,并在左括号前加上反斜杠:

  1. let 乘数 = 3
  2. let 消息 = "\(乘数) 乘以 2.5 得 \(Double(乘数) * 2.5)"
  3. // 消息 为 "3 乘以 2.5 得 7.5"

在上例中, 乘数 的值以 \(乘数) 的形式插入字符串字面量中。在根据字符串内插式求出实际字符串的过程中,该占位符会被 乘数 的实际值替换。

在同一个字符串中,后面一个较长的表达式也用到了 乘数 的值。该表达式会计算 Double(乘数) * 2.5 的值,并将结果(7.5)插入最终字符串。为了做到这点,该表达式以 \(Double(乘数) * 2.5) 的形式嵌入在字符串字面量中。

注:字符串内插式中,括号里面的表达式不能包含未转义的双引号(")或反斜杠(\),也不能包含回车或换行符。

字符串比较

Swift 提供了三种比较 String 值的方法:字符串相等、前缀匹配、后缀匹配。

字符串相等

如果两个 String 值所包含的字符及其顺序完全相同,则认为两者相等:

  1. let quotation = "We're a lot alike, you and I."
  2. let sameQuotation = "We're a lot alike, you and I."
  3. if quotation == sameQuotation {
  4.     println("这两个字符串符合相等的定义")
  5. }
  6. // 输出 "这两个字符串符合相等的定义"

前缀/后缀匹配

要检查某字符串是否以指定的字符串开头或结尾,可调用该字符串的 hasPrefixhasSuffix 方法,这两个方法均接受单个类型为 String 的参数,并返回布尔值。两个方法均对基本字符串与指定的前缀/后缀字符串按字符逐一比较。

下例为字符串构成的数组,内容为莎士比亚戏剧《罗密欧与朱丽叶》(Romeo and Juliet)前两幕 各场景的地点说明:

  1. let romeoAndJuliet = [
  2.     "Act 1 Scene 1: Verona, A public place",
  3.     "Act 1 Scene 2: Capulet's mansion",
  4.     "Act 1 Scene 3: A room in Capulet's mansion",
  5.     "Act 1 Scene 4: A street outside Capulet's mansion",
  6.     "Act 1 Scene 5: The Great Hall in Capulet's mansion",
  7.     "Act 2 Scene 1: Outside Capulet's mansion",
  8.     "Act 2 Scene 2: Capulet's orchard",
  9.     "Act 2 Scene 3: Outside Friar Lawrence's cell",
  10.     "Act 2 Scene 4: A street in Verona",
  11.     "Act 2 Scene 5: Capulet's mansion",
  12.     "Act 2 Scene 6: Friar Lawrence's cell"
  13. ]

可以对 romeoAndJuliet 数组中元素使用 hasPrefix 方法,来统计该剧第一幕(Act 1)的场景数目:

  1. var act1SceneCount = 0
  2. for scene in romeoAndJuliet {
  3.     if scene.hasPrefix("Act 1 ") {
  4.         ++act1SceneCount
  5.     }
  6. }
  7. println("第一幕中有 \(act1SceneCount) 个场景")
  8. // 输出 "第一幕中有 5 个场景"

类似地,可以用 hasSuffix 方法来统计发生在凯普莱特家(Capulet’s mansion)以及劳伦斯神父的寺院(Friar Lawrence’s cell)及其相关的场景数目:

  1. var mansionCount = 0
  2. var cellCount = 0
  3. for scene in romeoAndJuliet {
  4.     if scene.hasSuffix("Capulet's mansion") {
  5.         ++mansionCount
  6.     } else if scene.hasSuffix("Friar Lawrence's cell") {
  7.         ++cellCount
  8.     }
  9. }
  10. println("\(mansionCount) 个凯普莱特家相关场景;\(cellCount) 个寺院相关场景")
  11. // 输出 "6 个凯普莱特家相关场景;2 个寺院相关场景"