字符串字面量

字符串字面量可以用于为常量和变量提供初始值:

let someString = "Some string literal value"

多行字符串字面量

如果你需要一个字符串是跨越多行的,那就使用多行字符串字面量 — 由一对三个双引号包裹着的具有固定顺序的文本字符集:

let quotation = """
The White Rabbit put on his spectacles.  "Where shall I begin,
please your Majesty?" he asked.

"Begin at the beginning," the King said gravely, "and go on
till you come to the end; then stop."
"""

字符串字面量的特殊字符

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

  • 转义字符 \0(空字符)、\\(反斜线)、\t(水平制表符)、\n(换行符)、\r(回车符)、\"(双引号)、\'(单引号)。
  • Unicode 标量,写成 \u{n}(u 为小写),其中 n 为任意一到八位十六进制数且可用的 Unicode 位码。

下面的代码为各种特殊字符的使用示例。 wiseWords 常量包含了两个双引号。 dollarSignblackHeartsparklingHeart 常量演示了三种不同格式的 Unicode

let wiseWords = "\"Imagination is more important than knowledge\" - Einstein"
// "Imageination is more important than knowledge" - Enistein
let dollarSign = "\u{24}"             // $,Unicode 标量 U+0024
let blackHeart = "\u{2665}"           // ♥,Unicode 标量 U+2665
let sparklingHeart = "\u{1F496}"      // 💖,Unicode 标量 U+1F496

扩展字符串分隔符

您可以将字符串文字放在扩展分隔符中,这样字符串中的特殊字符将会被直接包含而非转义后的效果。将字符串放在引号(")中并用数字符号(#)括起来。例如,打印字符串文字 #"Line 1 \nLine 2"# 会打印换行符转义序列(\n)而不是给文字换行。

如果需要字符串文字中字符的特殊效果,请匹配转义字符(\)后面添加与起始位置个数相匹配的 # 符。 例如,如果您的字符串是 #"Line 1 \nLine 2"# 并且您想要换行,则可以使用 #"Line 1 \#nLine 2"# 来代替。 同样,###"Line1 \###nLine2"### 也可以实现换行效果。

初始化空字符串

你可以通过将一个特定字符串分配给一个变量来对其进行修改,或者分配给一个常量来保证其不会被修改:

var variableString = "Horse"
variableString += " and carriage"
// variableString 现在为 "Horse and carriage"

let constantString = "Highlander"
constantString += " and another Highlander"
// 这会报告一个编译错误(compile-time error) - 常量字符串不可以被修改。

字符串是值类型

SwiftString 类型是值类型。如果你创建了一个新的字符串,那么当其进行常量、变量赋值操作,或在函数/方法中传递时,会进行值拷贝。在前述任一情况下,都会对已有字符串值创建新副本,并对该新副本而非原始字符串进行传递或赋值操作。值类型在 结构体和枚举是值类型 中进行了详细描述。

Swift 默认拷贝字符串的行为保证了在函数/方法向你传递的字符串所属权属于你,无论该值来自于哪里。你可以确信传递的字符串不会被修改,除非你自己去修改它。

在实际编译时,Swift 编译器会优化字符串的使用,使实际的复制只发生在绝对必要的情况下,这意味着你将字符串作为值类型的同时可以获得极高的性能。

使用字符

你可通过 for-in 循环来遍历字符串,获取字符串中每一个字符的值:

for character in "Dog!🐶" {
    print(character)
}
// D
// o
// g
// !
// 🐶

另外,通过标明一个 Character 类型并用字符字面量进行赋值,可以建立一个独立的字符常量或变量:

let exclamationMark: Character = "!"

连接字符串和字符

字符串可以通过加法运算符(+)相加在一起(或称“连接”)创建一个新的字符串:

let string1 = "hello"
let string2 = " there"
var welcome = string1 + string2
// welcome 现在等于 "hello there"
var instruction = "look over"
instruction += string2
// instruction 现在等于 "look over there"
let exclamationMark: Character = "!"
welcome.append(exclamationMark)
// welcome 现在等于 "hello there!"

字符串插值

字符串插值是一种构建新字符串的方式,可以在其中包含常量、变量、字面量和表达式。字符串字面量和多行字符串字面量都可以使用字符串插值。你插入的字符串字面量的每一项都在以反斜线为前缀的圆括号中

let multiplier = 3
let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
// message 是 "3 times 2.5 is 7.5"

计算字符数量

如果想要获得一个字符串中Character值的数量,可以使用 count 属性:

let unusualMenagerie = "Koala 🐨, Snail 🐌, Penguin 🐧, Dromedary 🐪"
print("unusualMenagerie has \(unusualMenagerie.count) characters")
// 打印输出“unusualMenagerie has 40 characters”

注意
通过 count 属性返回的字符数量并不总是与包含相同字符的 NSStringlength 属性相同。NSStringlength 属性是利用 UTF-16 表示的十六位代码单元数字,而不是 Unicode 可扩展的字符群集。

访问和修改字符串

字符串索引

每一个 String 值都有一个关联的索引(index)类型,String.Index,它对应着字符串中的每一个 Character 的位置。

前面提到,不同的字符可能会占用不同数量的内存空间,所以要知道 Character 的确定位置,就必须从 String 开头遍历每一个 Unicode 标量直到结尾。因此,Swift 的字符串不能用整数(integer)做索引。

使用 startIndex 属性可以获取一个 String 的第一个 Character 的索引。使用 endIndex 属性可以获取最后一个 Character 的后一个位置的索引。因此,endIndex 属性不能作为一个字符串的有效下标。如果 String 是空串,startIndexendIndex 是相等的。

通过调用 Stringindex(before:)index(after:) 方法,可以立即得到前面或后面的一个索引。你还可以通过调用 index(_:offsetBy:) 方法来获取对应偏移量的索引,这种方式可以避免多次调用 index(before:)index(after:) 方法。

let greeting = "Guten Tag!"
greeting[greeting.startIndex]
// G
greeting[greeting.index(before: greeting.endIndex)]
// !
greeting[greeting.index(after: greeting.startIndex)]
// u
let index = greeting.index(greeting.startIndex, offsetBy: 7)
greeting[index]
// a

使用 indices 属性会创建一个包含全部索引的范围(Range),用来在一个字符串中访问单个字符。

for index in greeting.indices {
   print("\(greeting[index]) ", terminator: "")
}
// 打印输出“G u t e n   T a g ! ”

注意
你可以使用 startIndexendIndex 属性或者 index(before:)index(after:)index(_:offsetBy:) 方法在任意一个确认的并遵循 Collection 协议的类型里面,如上文所示是使用在 String 中,你也可以使用在 ArrayDictionarySet 中。

插入和删除

调用 insert(_:at:) 方法可以在一个字符串的指定索引插入一个字符,调用 insert(contentsOf:at:) 方法可以在一个字符串的指定索引插入一个段字符串。

var welcome = "hello"
welcome.insert("!", at: welcome.endIndex)
// welcome 变量现在等于 "hello!"

welcome.insert(contentsOf:" there", at: welcome.index(before: welcome.endIndex))
// welcome 变量现在等于 "hello there!"

调用 remove(at:) 方法可以在一个字符串的指定索引删除一个字符,调用 removeSubrange(_:) 方法可以在一个字符串的指定索引删除一个子字符串。

welcome.remove(at: welcome.index(before: welcome.endIndex))
// welcome 现在等于 "hello there"

let range = welcome.index(welcome.endIndex, offsetBy: -6)..<welcome.endIndex
welcome.removeSubrange(range)
// welcome 现在等于 "hello"

注意
你可以使用 insert(_:at:)、insert(contentsOf:at:)remove(at:)removeSubrange(_:) 方法在任意一个确认的并遵循 RangeReplaceableCollection 协议的类型里面,如上文所示是使用在 String 中,你也可以使用在 ArrayDictionarySet 中。

子字符串

当你从字符串中获取一个子字符串 —— 例如,使用下标或者 prefix(_:) 之类的方法 —— 就可以得到一个 Substring 的实例,而非另外一个 StringSwift 里的 Substring 绝大部分函数都跟 String 一样,意味着你可以使用同样的方式去操作 SubstringString。然而,跟 String 不同的是,你只有在短时间内需要操作字符串时,才会使用 Substring。当你需要长时间保存结果时,就把 Substring 转化为 String 的实例:

let greeting = "Hello, world!"
let index = greeting.firstIndex(of: ",") ?? greeting.endIndex
let beginning = greeting[..<index]
// beginning 的值为 "Hello"

// 把结果转化为 String 以便长期存储。
let newString = String(beginning)

就像 String,每一个 Substring 都会在内存里保存字符集。而 StringSubstring 的区别在于性能优化上,Substring 可以重用原 String 的内存空间,或者另一个 Substring 的内存空间(String 也有同样的优化,但如果两个 String 共享内存的话,它们就会相等)。这一优化意味着你在修改 StringSubstring 之前都不需要消耗性能去复制内存。就像前面说的那样,Substring 不适合长期存储 —— 因为它重用了原 String 的内存空间,原 String 的内存空间必须保留直到它的 Substring 不再被使用为止。

上面的例子,greeting 是一个 String,意味着它在内存里有一片空间保存字符集。而由于 beginninggreetingSubstring,它重用了 greeting 的内存空间。相反,newString 是一个 String —— 它是使用 Substring 创建的,拥有一片自己的内存空间。下面的图展示了他们之间的关系:

注意
StringSubstring 都遵循 StringProtocol 协议,这意味着操作字符串的函数使用 StringProtocol 会更加方便。你可以传入 StringSubstring 去调用函数。

比较字符串

Swift 提供了三种方式来比较文本值:字符串字符相等、前缀相等和后缀相等。

字符串/字符相等

字符串/字符可以用等于操作符(==)和不等于操作符(!=),详细描述在 比较运算符:

let quotation = "We're a lot alike, you and I."
let sameQuotation = "We're a lot alike, you and I."
if quotation == sameQuotation {
    print("These two strings are considered equal")
}
// 打印输出“These two strings are considered equal”

前缀/后缀相等

通过调用字符串的 hasPrefix(_:)/hasSuffix(_:) 方法来检查字符串是否拥有特定前缀/后缀,两个方法均接收一个 String 类型的参数,并返回一个布尔值。

下面的例子以一个字符串数组表示莎士比亚话剧《罗密欧与朱丽叶》中前两场的场景位置:

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

你可以调用 hasPrefix(_:) 方法来计算话剧中第一幕的场景数:

var act1SceneCount = 0
for scene in romeoAndJuliet {
    if scene.hasPrefix("Act 1 ") {
        act1SceneCount += 1
    }
}
print("There are \(act1SceneCount) scenes in Act 1")
// 打印输出“There are 5 scenes in Act 1”

相似地,你可以用 hasSuffix(_:) 方法来计算发生在不同地方的场景数:

var mansionCount = 0
var cellCount = 0
for scene in romeoAndJuliet {
    if scene.hasSuffix("Capulet's mansion") {
        mansionCount += 1
    } else if scene.hasSuffix("Friar Lawrence's cell") {
        cellCount += 1
    }
}
print("\(mansionCount) mansion scenes; \(cellCount) cell scenes")
// 打印输出“6 mansion scenes; 2 cell scenes”

注意
hasPrefix(_:)hasSuffix(_:) 方法都是在每个字符串中逐字符比较其可扩展的字符群集是否标准相等,详细描述在 字符串/字符相等。