参考资料

字符串与字符

字符串是一系列字符的集合,例如 "hello, world" 或 "albatross"。Swift 字符串由 String 类型表示。String 的内容可以通过多种方式访问,包括作为 Character 值的集合。

字符串与字符

字符串是一系列字符的集合,例如 "hello, world""albatross"。Swift 字符串由 String 类型表示。String 的内容可以通过多种方式访问,包括作为 Character 值的集合。

Swift 的 StringCharacter 类型提供了一种快速、符合 Unicode 标准的方式来处理代码中的文本。字符串的创建和操作语法轻量且易读,使用的字符串字面量语法与 C 语言类似。字符串拼接只需使用 + 运算符将两个字符串相加即可,而字符串的可变性则通过选择常量或变量来管理,这与 Swift 中的其他值一样简单。你还可以使用字符串将常量、变量、字面量和表达式插入到更长的字符串中,这个过程称为字符串插值。这使得创建用于显示、存储和打印的自定义字符串值变得非常容易。

尽管语法简洁,Swift 的 String 类型是一个快速、现代的字符串实现。每个字符串都由与编码无关的 Unicode 字符组成,并支持以各种 Unicode 表示形式访问这些字符。

注意 Swift 的 String 类型与 Foundation 的 NSString 类进行了桥接。Foundation 还扩展了 String 以暴露 NSString 定义的方法。这意味着,如果你导入了 Foundation,就可以在 String 上访问那些 NSString 方法,而无需进行类型转换。

有关将 String 与 Foundation 和 Cocoa 一起使用的更多信息,请参阅 Bridging Between String and NSString

字符串字面量

你可以在代码中包含预定义的 String 值作为字符串字面量。字符串字面量是由双引号(")包围的一系列字符。

使用字符串字面量作为常量或变量的初始值:

let someString = "Some string literal value"

注意,Swift 会为 someString 常量推断出 String 类型,因为它是用字符串字面量值初始化的。

多行字符串字面量

如果你需要一个跨越多行的字符串,请使用多行字符串字面量——由三个双引号包围的一系列字符:

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."
"""

多行字符串字面量包含其开始和结束引号之间的所有行。字符串从开始引号(""")之后的第一行开始,到结束引号之前的那一行结束,这意味着下面的字符串都不会以换行符开始或结束:

let singleLineString = "These are the same."
let multilineString = """
These are the same.
"""

当你的源代码在多行字符串字面量中包含换行符时,该换行符也会出现在字符串的值中。如果你想使用换行符使源代码更易读,但不希望换行符成为字符串值的一部分,请在这些行的末尾写一个反斜杠(\):

let softWrappedQuotation = """
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."
"""

要创建一个以换行符开始或结束的多行字符串字面量,请将第一行或最后一行写为空行。例如:

let lineBreaks = """

This string starts with a line break.
It also ends with a line break.

"""

多行字符串可以缩进以匹配周围的代码。结束引号(""")之前的空白告诉 Swift 在所有其他行之前忽略多少空白。但是,如果你在某一行的开头写了额外的空白(超出结束引号之前的空白),那些空白被包含在内。

multilineStringWhitespace

在上面的例子中,尽管整个多行字符串字面量都有缩进,但字符串中的第一行和最后一行不会以任何空白开始。中间的行比结束引号有更多的缩进,所以它以额外的四个空格缩进开始。

字符串字面量中的特殊字符

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

  • 转义特殊字符 \0(空字符)、\\(反斜杠)、\t(水平制表符)、\n(换行符)、\r(回车符)、\"(双引号)和 \'(单引号)
  • 任意 Unicode 标量值,写作 \u{n},其中 n 是一个 1-8 位的十六进制数(Unicode 在下面的 Unicode 中讨论)

下面的代码展示了这些特殊字符的四个例子。wiseWords 常量包含两个转义的双引号。dollarSignblackHeartsparklingHeart 常量演示了 Unicode 标量格式:

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

因为多行字符串字面量使用三个双引号而不是一个,所以你可以在多行字符串字面量中包含双引号(")而无需转义。要在多行字符串中包含文本 """,请至少转义其中一个引号。例如:

let threeDoubleQuotationMarks = """
Escaping the first quotation mark \"""
Escaping all three quotation marks \"\"\"
"""

扩展字符串分隔符

你可以将字符串字面量放在扩展分隔符中,以在字符串中包含特殊字符而不触发它们的效果。将字符串放在引号(")内,并用井号(#)包围。例如,打印字符串字面量 #"Line 1\nLine 2"# 会打印换行转义序列(\n)而不是将字符串打印在两行上。

如果你需要字符串字面量中某个字符的特殊效果,请在转义字符(\)之后匹配字符串中井号的数量。例如,如果你的字符串是 #"Line 1\nLine 2"# 而你想换行,可以使用 #"Line 1\#nLine 2"# 代替。类似地,###"Line1\###nLine2"### 也会换行。

使用扩展分隔符创建的字符串字面量也可以是多行字符串字面量。你可以使用扩展分隔符在多行字符串中包含文本 """,覆盖结束字面量的默认行为。例如:

let threeMoreDoubleQuotationMarks = #"""
Here are three more double quotes: """
"""#

初始化空字符串

要创建一个空的 String 值作为构建更长字符串的起点,可以将空字符串字面量赋值给变量,或者使用初始化语法初始化一个新的 String 实例:

var emptyString = ""               // empty string literal
var anotherEmptyString = String()  // initializer syntax
// these two strings are both empty, and are equivalent to each other

通过检查 String 值的布尔 isEmpty 属性来判断它是否为空:

if emptyString.isEmpty {
    print("Nothing to see here")
}
// Prints "Nothing to see here".

字符串可变性

你可以通过将特定的 String 赋值给变量(在这种情况下它可以被修改)或常量(在这种情况下它不能被修改)来指示它是否可以被修改(或可变):

var variableString = "Horse"
variableString += " and carriage"
// variableString is now "Horse and carriage"

let constantString = "Highlander"
constantString += " and another Highlander"
// this reports a compile-time error - a constant string cannot be modified

注意 这种方法与 Objective-C 和 Cocoa 中的字符串可变性不同,在那里你通过在两个类(NSStringNSMutableString)之间选择来指示字符串是否可以被修改。

字符串是值类型

Swift 的 String 类型是值类型。如果你创建一个新的 String 值,当它被传递给函数或方法,或者被赋值给常量或变量时,该 String 值会被复制。在每种情况下,都会创建现有 String 值的新副本,并且传递或赋值的是新副本,而不是原始版本。值类型在结构体和枚举是值类型中描述。

Swift 的默认复制 String 行为确保当函数或方法向你传递 String 值时,你清楚地拥有那个确切的 String 值,无论它来自哪里。你可以确信你收到的字符串不会被修改,除非你自己修改它。

在幕后,Swift 的编译器优化了字符串的使用,使得只有在绝对必要时才进行实际复制。这意味着当你将字符串作为值类型使用时,总是能获得出色的性能。

使用字符

你可以通过使用 for-in 循环遍历字符串来访问 String 的各个 Character 值:

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

for-in 循环在 For-In 循环中描述。

或者,你可以通过提供 Character 类型注解,从单字符字符串字面量创建一个独立的 Character 常量或变量:

let exclamationMark: Character = "!"

通过将 Character 值数组作为参数传递给其初始化器,可以构造 String 值:

let catCharacters: [Character] = ["C", "a", "t", "!", "🐱"]
let catString = String(catCharacters)
print(catString)
// Prints "Cat!🐱".

拼接字符串和字符

String 值可以使用加法运算符(+)相加(或拼接)以创建新的 String 值:

let string1 = "hello"
let string2 = " there"
var welcome = string1 + string2
// welcome now equals "hello there"

你也可以使用加法赋值运算符(+=)将 String 值附加到现有的 String 变量:

var instruction = "look over"
instruction += string2
// instruction now equals "look over there"

你可以使用 String 类型的 append() 方法将 Character 值附加到 String 变量:

let exclamationMark: Character = "!"
welcome.append(exclamationMark)
// welcome now equals "hello there!"

注意 你不能将 StringCharacter 附加到现有的 Character 变量,因为 Character 值必须只包含一个字符。

如果你使用多行字符串字面量来构建更长字符串的各行,你希望字符串中的每一行都以换行符结束,包括最后一行。例如:

let badStart = """
    one
    two
    """
let end = """
    three
    """
print(badStart + end)
// Prints two lines:
// one
// twothree

let goodStart = """
    one
    two

    """
print(goodStart + end)
// Prints three lines:
// one
// two
// three

在上面的代码中,将 badStartend 拼接会产生一个两行的字符串,这不是期望的结果。因为 badStart 的最后一行没有以换行符结束,所以该行与 end 的第一行合并了。相反,goodStart 的两行都以换行符结束,所以当它与 end 组合时,结果如预期的那样有三行。

字符串插值

字符串插值是一种通过在字符串字面量中包含常量、变量、字面量和表达式的值来构造新 String 值的方法。你可以在单行和多行字符串字面量中使用字符串插值。你插入到字符串字面量中的每个项目都用一对括号包裹,并以反斜杠(\)为前缀:

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

在上面的例子中,multiplier 的值被插入到字符串字面量中作为 \(multiplier)。当计算字符串插值以创建实际字符串时,这个占位符被替换为 multiplier 的实际值。

multiplier 的值也是字符串后面更大表达式的一部分。这个表达式计算 Double(multiplier) * 2.5 的值并将结果(7.5)插入到字符串中。在这种情况下,当表达式包含在字符串字面量中时,它被写作 \(Double(multiplier) * 2.5)

你可以使用扩展字符串分隔符来创建包含原本会被视为字符串插值的字符的字符串。例如:

print(#"Write an interpolated string in Swift using \(multiplier)."#)
// Prints "Write an interpolated string in Swift using \(multiplier)."

要在使用扩展分隔符的字符串中使用字符串插值,请使反斜杠后面的井号数量与字符串开头和结尾的井号数量匹配。例如:

print(#"6 times 7 is \#(6 * 7)."#)
// Prints "6 times 7 is 42."

注意 你在插值字符串的括号内编写的表达式不能包含未转义的反斜杠(\)、回车符或换行符。但是,它们可以包含其他字符串字面量。

Unicode

Unicode 是一个用于在不同书写系统中编码、表示和处理文本的国际标准。它使你能够以标准化的形式表示几乎任何语言的任何字符,并从外部源(如文本文件或网页)读取和写入这些字符。如本节所述,Swift 的 StringCharacter 类型完全符合 Unicode 标准。

Unicode 标量值

在幕后,Swift 的原生 String 类型是从 Unicode 标量值构建的。Unicode 标量值是一个字符或修饰符的唯一 21 位数字,例如 U+0061 表示 LATIN SMALL LETTER A"a"),或 U+1F425 表示 FRONT-FACING BABY CHICK"🐥")。

请注意,并非所有 21 位 Unicode 标量值都分配给了字符——有些标量保留用于将来分配或用于 UTF-16 编码。已分配给字符的标量值通常也有一个名称,如上面例子中的 LATIN SMALL LETTER AFRONT-FACING BABY CHICK

扩展字形簇

Swift 的每个 Character 类型实例都代表一个扩展字形簇。扩展字形簇是一个或多个 Unicode 标量的序列,它们(组合起来)产生单个人类可读的字符。

这里有一个例子。字母 é 可以表示为单个 Unicode 标量 éLATIN SMALL LETTER E WITH ACUTE,或 U+00E9)。但是,同一个字母也可以表示为一标量——一个标准字母 eLATIN SMALL LETTER E,或 U+0065),后跟 COMBINING ACUTE ACCENT 标量(U+0301)。COMBINING ACUTE ACCENT 标量在图形上应用于它前面的标量,当由 Unicode 感知的文本渲染系统渲染时,将 e 变成 é

在这两种情况下,字母 é 都表示为代表扩展字形簇的单个 Swift Character 值。在第一种情况下,簇包含单个标量;在第二种情况下,它是两个标量的簇:

let eAcute: Character = "\u{E9}"                         // é
let combinedEAcute: Character = "\u{65}\u{301}"          // e followed by ́
// eAcute is é, combinedEAcute is é

扩展字形簇是将许多复杂脚本字符表示为单个 Character 值的灵活方式。例如,韩语字母表中的韩文音节可以表示为预组合或分解序列。这两种表示在 Swift 中都符合单个 Character 值的条件:

let precomposed: Character = "\u{D55C}"                  // 한
let decomposed: Character = "\u{1112}\u{1161}\u{11AB}"   // ᄒ, ᅡ, ᆫ
// precomposed is 한, decomposed is 한

扩展字形簇使包围标记的标量(如 COMBINING ENCLOSING CIRCLE,或 U+20DD)能够将其他 Unicode 标量包围起来,作为单个 Character 值的一部分:

let enclosedEAcute: Character = "\u{E9}\u{20DD}"
// enclosedEAcute is é⃝

区域指示符号的 Unicode 标量可以成对组合以形成单个 Character 值,例如 REGIONAL INDICATOR SYMBOL LETTER UU+1F1FA)和 REGIONAL INDICATOR SYMBOL LETTER SU+1F1F8)的组合:

let regionalIndicatorForUS: Character = "\u{1F1FA}\u{1F1F8}"
// regionalIndicatorForUS is 🇺🇸

字符计数

要获取字符串中 Character 值的计数,请使用字符串的 count 属性:

let unusualMenagerie = "Koala 🐨, Snail 🐌, Penguin 🐧, Dromedary 🐪"
print("unusualMenagerie has \(unusualMenagerie.count) characters")
// Prints "unusualMenagerie has 40 characters".

请注意,Swift 对 Character 值使用扩展字形簇意味着字符串的拼接和修改可能并不总是影响字符串的字符计数。

例如,如果你用四个字符的单词 cafe 初始化一个新字符串,然后在字符串末尾附加一个 COMBINING ACUTE ACCENTU+0301),结果字符串的字符计数仍然是 4,第四个字符是 é,而不是 e

var word = "cafe"
print("the number of characters in \(word) is \(word.count)")
// Prints "the number of characters in cafe is 4".

word += "\u{301}"    // COMBINING ACUTE ACCENT, U+0301

print("the number of characters in \(word) is \(word.count)")
// Prints "the number of characters in café is 4".

注意 扩展字形簇可以由多个 Unicode 标量组成。这意味着不同的字符——以及同一字符的不同表示——可能需要不同的内存量来存储。因此,Swift 中的字符在字符串表示中不会各自占用相同的内存量。结果是,如果不遍历字符串以确定其扩展字形簇边界,就无法计算字符串中的字符数。如果你正在处理特别长的字符串值,请注意 count 属性必须遍历整个字符串中的 Unicode 标量才能确定该字符串的字符。

count 属性返回的字符计数并不总是与包含相同字符的 NSStringlength 属性相同。NSString 的长度基于字符串 UTF-16 表示中 16 位代码单元的数量,而不是字符串中 Unicode 扩展字形簇的数量。

访问和修改字符串

你可以通过字符串的方法和属性,或使用下标语法来访问和修改字符串。

字符串索引

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

如上所述,不同的字符可能需要不同的内存量来存储,因此为了确定哪个 Character 在特定位置,你必须从该 String 的开头或结尾遍历每个 Unicode 标量。因此,Swift 字符串不能用整数值进行索引。

使用 startIndex 属性访问 String 第一个 Character 的位置。endIndex 属性是 String 中最后一个字符之后的位置。因此,endIndex 属性不是字符串下标的有效参数。如果 String 为空,则 startIndexendIndex 相等。

你可以使用 Stringindex(before:)index(after:) 方法访问给定索引之前和之后的索引。要访问距离给定索引较远的索引,可以使用 index(_:offsetBy:) 方法,而不是多次调用这些方法之一。

你可以使用下标语法访问特定 String 索引处的 Character

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

尝试访问字符串范围之外的索引或字符串范围之外索引处的 Character 将触发运行时错误。

greeting[greeting.endIndex] // Error
greeting.index(after: greeting.endIndex) // Error

使用 indices 属性访问字符串中所有单个字符的索引。

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

注意 你可以在任何符合 Collection 协议的类型上使用 startIndexendIndex 属性以及 index(before:)index(after:)index(_:offsetBy:) 方法。这包括这里展示的 String,以及集合类型如 ArrayDictionarySet

插入和删除

要在字符串的指定索引处插入单个字符,请使用 insert(_:at:) 方法,要在指定索引处插入另一个字符串的内容,请使用 insert(contentsOf:at:) 方法。

var welcome = "hello"
welcome.insert("!", at: welcome.endIndex)
// welcome now equals "hello!"

welcome.insert(contentsOf: " there", at: welcome.index(before: welcome.endIndex))
// welcome now equals "hello there!"

要从字符串的指定索引处删除单个字符,请使用 remove(at:) 方法,要删除指定范围内的子字符串,请使用 removeSubrange(_:) 方法:

welcome.remove(at: welcome.index(before: welcome.endIndex))
// welcome now equals "hello there"

let range = welcome.index(welcome.endIndex, offsetBy: -6)..<welcome.endIndex
welcome.removeSubrange(range)
// welcome now equals "hello"

注意 你可以在任何符合 RangeReplaceableCollection 协议的类型上使用 insert(_:at:)insert(contentsOf:at:)remove(at:)removeSubrange(_:) 方法。这包括这里展示的 String,以及集合类型如 ArrayDictionarySet

子字符串

当你从字符串中获取子字符串时——例如,使用下标或像 prefix(_:) 这样的方法——结果是 Substring 的实例,而不是另一个字符串。Swift 中的子字符串具有与字符串几乎相同的方法,这意味着你可以像处理字符串一样处理子字符串。但是,与字符串不同的是,你只在对字符串执行操作的短时间内使用子字符串。当你准备好将结果存储更长时间时,你需要将子字符串转换为 String 的实例。例如:

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

// Convert the result to a String for long-term storage.
let newString = String(beginning)

与字符串一样,每个子字符串都有一个存储组成该子字符串的字符的内存区域。字符串和子字符串之间的区别在于,作为性能优化,子字符串可以重用用于存储原始字符串的部分内存,或用于存储另一个子字符串的部分内存。(字符串有类似的优化,但如果两个字符串共享内存,它们是相等的。)这种性能优化意味着在你修改字符串或子字符串之前,你不必支付复制内存的性能成本。如上所述,子字符串不适合长期存储——因为它们重用原始字符串的存储,所以只要使用其任何子字符串,就必须将整个原始字符串保留在内存中。

在上面的例子中,greeting 是一个字符串,这意味着它有一个存储组成该字符串的字符的内存区域。因为 beginninggreeting 的子字符串,它重用了 greeting 使用的内存。相反,newString 是一个字符串——当它从子字符串创建时,它有自己的存储。下图显示了这些关系:

stringSubstring

注意 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")
}
// Prints "These two strings are considered equal".

如果两个 String 值(或两个 Character 值)的扩展字形簇是规范等价的,则它们被视为相等。如果扩展字形簇具有相同的语言意义和外观,即使它们在幕后由不同的 Unicode 标量组成,它们也是规范等价的。

例如,LATIN SMALL LETTER E WITH ACUTEU+00E9)与 LATIN SMALL LETTER EU+0065)后跟 COMBINING ACUTE ACCENTU+0301)是规范等价的。这两个扩展字形簇都是表示字符 é 的有效方式,因此它们被视为规范等价:

// "Voulez-vous un café?" using LATIN SMALL LETTER E WITH ACUTE
let eAcuteQuestion = "Voulez-vous un caf\u{E9}?"

// "Voulez-vous un café?" using LATIN SMALL LETTER E and COMBINING ACUTE ACCENT
let combinedEAcuteQuestion = "Voulez-vous un caf\u{65}\u{301}?"

if eAcuteQuestion == combinedEAcuteQuestion {
    print("These two strings are considered equal")
}
// Prints "These two strings are considered equal".

相反,英语中使用的 LATIN CAPITAL LETTER AU+0041,或 "A")与俄语中使用的 CYRILLIC CAPITAL LETTER AU+0410,或 "А"等价。这些字符在视觉上相似,但没有相同的语言意义:

let latinCapitalLetterA: Character = "\u{41}"

let cyrillicCapitalLetterA: Character = "\u{0410}"

if latinCapitalLetterA != cyrillicCapitalLetterA {
    print("These two characters aren't equivalent.")
}
// Prints "These two characters aren't equivalent."

注意 Swift 中的字符串和字符比较不是区域敏感的。

前缀和后缀相等

要检查字符串是否具有特定的字符串前缀或后缀,请调用字符串的 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(_:) 方法与 romeoAndJuliet 数组一起计算剧中第一幕的场景数:

var act1SceneCount = 0
for scene in romeoAndJuliet {
    if scene.hasPrefix("Act 1 ") {
        act1SceneCount += 1
    }
}
print("There are \(act1SceneCount) scenes in Act 1")
// Prints "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")
// Prints "6 mansion scenes; 2 cell scenes".

注意 hasPrefix(_:)hasSuffix(_:) 方法对每个字符串中的扩展字形簇执行逐字符的规范等价比较,如字符串和字符相等中所述。

字符串的 Unicode 表示

当 Unicode 字符串写入文本文件或其他存储时,该字符串中的 Unicode 标量以几种 Unicode 定义的编码形式之一进行编码。每种形式将字符串编码为称为代码单元的小块。这些包括 UTF-8 编码形式(将字符串编码为 8 位代码单元)、UTF-16 编码形式(将字符串编码为 16 位代码单元)和 UTF-32 编码形式(将字符串编码为 32 位代码单元)。

Swift 提供了几种不同的方式来访问字符串的 Unicode 表示。你可以使用 for-in 语句遍历字符串,以 Unicode 扩展字形簇的形式访问其各个 Character 值。这个过程在使用字符中描述。

或者,以其他三种符合 Unicode 的表示形式之一访问 String 值:

  • UTF-8 代码单元的集合(通过字符串的 utf8 属性访问)
  • UTF-16 代码单元的集合(通过字符串的 utf16 属性访问)
  • 21 位 Unicode 标量值的集合,相当于字符串的 UTF-32 编码形式(通过字符串的 unicodeScalars 属性访问)

下面的每个例子展示了以下字符串的不同表示,该字符串由字符 DogDOUBLE EXCLAMATION MARK,或 Unicode 标量 U+203C)和 🐶 字符(DOG FACE,或 Unicode 标量 U+1F436)组成:

let dogString = "Dog‼🐶"

UTF-8 表示

你可以通过遍历字符串的 utf8 属性来访问 String 的 UTF-8 表示。该属性的类型是 String.UTF8View,它是无符号 8 位(UInt8)值的集合,字符串 UTF-8 表示中的每个字节对应一个值:

UTF8

for codeUnit in dogString.utf8 {
    print("\(codeUnit) ", terminator: "")
}
print("")
// Prints "68 111 103 226 128 188 240 159 144 182 ".

在上面的例子中,前三个十进制 codeUnit 值(68111103)表示字符 Dog,它们的 UTF-8 表示与 ASCII 表示相同。接下来的三个十进制 codeUnit 值(226128188)是 DOUBLE EXCLAMATION MARK 字符的三字节 UTF-8 表示。最后四个 codeUnit 值(240159144182)是 DOG FACE 字符的四字节 UTF-8 表示。

UTF-16 表示

你可以通过遍历字符串的 utf16 属性来访问 String 的 UTF-16 表示。该属性的类型是 String.UTF16View,它是无符号 16 位(UInt16)值的集合,字符串 UTF-16 表示中的每个 16 位代码单元对应一个值:

UTF16

for codeUnit in dogString.utf16 {
    print("\(codeUnit) ", terminator: "")
}
print("")
// Prints "68 111 103 8252 55357 56374 ".

同样,前三个 codeUnit 值(68111103)表示字符 Dog,它们的 UTF-16 代码单元与字符串 UTF-8 表示中的值相同(因为这些 Unicode 标量表示 ASCII 字符)。

第四个 codeUnit 值(8252)是十六进制值 203C 的十进制等价物,它表示 DOUBLE EXCLAMATION MARK 字符的 Unicode 标量 U+203C。这个字符可以在 UTF-16 中表示为单个代码单元。

第五和第六个 codeUnit 值(5535756374)是 DOG FACE 字符的 UTF-16 代理对表示。这些值是高位代理值 U+D83D(十进制值 55357)和低位代理值 U+DC36(十进制值 56374)。

Unicode 标量表示

你可以通过遍历字符串的 unicodeScalars 属性来访问 String 值的 Unicode 标量表示。该属性的类型是 UnicodeScalarView,它是 UnicodeScalar 类型值的集合。

每个 UnicodeScalar 都有一个 value 属性,返回标量的 21 位值,表示在 UInt32 值中:

UnicodeScalar

for scalar in dogString.unicodeScalars {
    print("\(scalar.value) ", terminator: "")
}
print("")
// Prints "68 111 103 8252 128054 ".

前三个 UnicodeScalar 值(68111103)的 value 属性再次表示字符 Dog

第四个 codeUnit 值(8252)同样是十六进制值 203C 的十进制等价物,它表示 DOUBLE EXCLAMATION MARK 字符的 Unicode 标量 U+203C

第五个也是最后一个 UnicodeScalarvalue 属性 128054 是十六进制值 1F436 的十进制等价物,它表示 DOG FACE 字符的 Unicode 标量 U+1F436

作为查询其 value 属性的替代方法,每个 UnicodeScalar 值也可以用于构造新的 String 值,例如使用字符串插值:

for scalar in dogString.unicodeScalars {
    print("\(scalar) ")
}
// D
// o
// g
// ‼
// 🐶