Swift 4官方文档中文版: Strings and Characters

Swift学习有问必答群 : 313838956 ( mac版QQ有权限要求, 入群只能通过手机版 QQ申请). 本群由Guards翻译组创建并维护
入群须知:

0.0 重申: mac版QQ有权限要求, 入群只能通过手机版 QQ申请.
0.1 群主晚上每天20点--21点定时回答Swift相关的问题.
0.2 群主会在看到问题后, 第一时间回复
0.3 拒绝长时间潜水, 拒绝讨论和Swift , iOS 无关的问题



该文章翻译自Apple官方文档: The Swift 4 Programming Language

Guards翻译组 正在翻译Swift 4的全套文档, 这是该文档第三章节《Strings and Characters》原文链接: Strings and Characters

翻译 、校对 :Seahub (简书) August (简书 )

译者心声

我们会不定期的更新翻译文章, Guards翻译组下周内会发布 Collection Types 章节中文版. 如感兴趣,可以关注我们的简书

我们是一群热爱翻译并且热爱 Swift 的人, 希望通过自己的努力让不熟悉英语的程序员也能学习到国外的高质量的文章. 如发现文章中翻译错误之处, 烦请跟我们联系, 我们第一时间更正.

本篇包含内容:

  • 字符串字面量
  • 初始化空字符串
  • 字符串可变性
  • 字符串值类型
  • 字符与字符串拼接
  • 字符串插值
  • 访问和修改字符串
  • 子串
  • 字符串比较
  • 字符串的 Unicode 表示方式

字符串和字符 (Strings and Characters)

诸如 "hello,world" 或者 "albatross" 的字符串是一系列字符的集合 。Swift字符串由String类型表示。String的内容可以有很多种构成方式,其中一种是由Character集合构成。

SwiftStringCharacter类型提供了快速和兼容 Unicode 的方式来操作我们代码中的文本。字符串创建和操作的语法是轻量级和易读的,具有与 C 语言类似的的语法。字符串连接只需简单地使用 + 运算符来连接两个字符串。并且,我们可以通过选择声明常量或变量来管理字符串可变性,就像Swift中的任何其他类型一样。在字符串插值的过程中,我们还可以将常量、变量、文字和表达式插入更长的字符串。这样可以轻松创建用于显示,存储和打印的自定义字符串值。

尽管语法简单,SwiftString类型是一种快速,现代的字符串实现。 每个字符串由独立于编码的Unicode字符组成,并支持在不同的 Unicode 表示集中获取这些字符。

NOTE:
Swift 的 String 类型与 Foundation NSString 类可以进行无缝桥接。Foundation 也可以对 String 进行了扩展,并暴露了在 NSString 中定义的方法。这意味着,如果我们导入了 Foundation 框架,那么我们在 String 中可以无需转换成 NSString,并调用这些 NSString 的方法。
更多关于在 Foundation 和 Cocoa 中使用 String 的资料请查阅 Using Swift with Cocoa and Objective-C (Swift 4)

字符串字面量(String Literals)

我们可以在代码中包含一段预定义的字符串值作为字符串字面量。字符串字面量是由双引号 ( "" ) 包裹着的具有固定顺序的文本字符集。

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

let someString = "Some string literal value

注意因为someString常量通过字符串字面量进行初始化,所以Swift 会推断该常量为String类型。

NOTE:

更多关于在字符串字面量中使用特殊字符的信息,请查阅 Special Characters in String Literals

如果我们需要创建多行字符串,需要使用多行字符串字面量语法。多行字符串字面量是由三个双引号包含的字符:

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 threeDoubleQuotes = """
Escaping the first quote \"""
Escaping all three quotes \"\"\"
"""

有关使用反斜杠转义特殊字符的更多资料,请查阅 Special Characters in String Literals

在其多行形式中,字符串字面量包括其开头和结尾引号之间的所有行。 字符串从开头引号(""")之后的第一行开始,并在结束引号(""")之前的行结束,这意味着多行字符串不以换行符开始或结束。 因此以下两个字符串相同:

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

要使用换行符开头或结尾的多行字符串文字,请写一条空白行作为第一行或最后一行。 例如:

"""
 
This string starts with a line feed.
It also ends with a line feed.
 
"""

可以缩进多行字符串以匹配周围的代码。 右引号(""")之前的空格告诉 Swift 在所有其他行之前要忽略的空格。例如,即使下面的函数中的多行字符串文字缩进,实际字符串中的行也不会开始于任何空白。

func generateQuotation() -> 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."
        """
    return quotation
}
print(quotation == generateQuotation())
// Prints "true"

但是,如果你在右引号(""")前的行首加入空格,那么这些空格将会被包含进多行字符串。

1

初始化空字符串(Initializing an Empty String)

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

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

判断一个字符串是否为空,通过检查它的isEmpty属性:

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

字符串可变性(String Mutability)

我们需要通过定义一个变量来表明一个特定的字符串能被修改,或者定义一个常量来表明它不能被修改。

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

NOTE:
这种方法不同于Objective-CCocoa中的可变字符串,在Objective-CCocoa中,我们需要选择不同的类(NSStringNSMutablString)来表明字符串是否能被修改。

字符串值类型(Strings Are Value Types)

SwiftString类型是一个值类型。 如果我们创建一个新的String,该字符串在传递给函数或方法时将会被复制,或者当它被分配给常量或变量时也会被复制。 在所有的情况下,将创建现有String的新副本,并且新副本将被传递或分配,而不是原始版本。 值类型在Structures and Enumerations Are Value Types中有所描述。

Swiftcopy-by-default String行为确保当函数或方法传递一个String值时,无论它来自哪里,我们都将拥有这个真实可靠的String值。可以确保我们传递的字符串不会被修改,除非我们自己修改。

Swift的编译器的内部,它优化了字符串性能,使得只有在必要时才对字符串进行实际复制。 这意味着在使用字符串作为值类型时,总是会获得很好的性能表现。

使用字符串(Working with Characters)

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

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

For-In Loops中描述了for-in循环

或者,我们也可以从仅含有一个字符的字符串中,通过指明字符类型来创建一个独立的字符常量或变量。

let exclamationMark: Character = "!"

String值可以通过将一个Character值数组作为参数传递给它的初始化器:

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

字符与字符串拼接(Concatenating Strings and Characters)

字符串值可以通过+运算符加到一起,以此形成新的字符串:

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

我们也可以通过 +=运算符给一个已存在的字符串拼接新字符串值:

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

我们也可以通过append()方法来将一个字符拼接至一个字符串变量上:

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

NOTE:
我们不能将字符串或字符拼接到现有的字符变量,因为字符值必须只包含一个字符。

字符串插值(String Interpolation)

字符串插值是一种通过把它们的值包含在字符串字面量中,由常量、变量、字符串字面量和表达式混合构造出一个新的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的值也是字符串中表达式的一部分。 这个表达式计算出Double(multiplier)* 2.5的值,并将结果(7.5)插入到字符串中。 在这种情况下,当表达式被包含在字符串字面量中时,表达式需要被改写为 \ (Double(multiplier) * 2.5)

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

Unicode

Unicode是一种在不同书写系统中编码、表示和处理文本的国际标准。 它使我们以标准化的形式表示任何语言的任何字符,并允许我们将这些字符从外部来源(例如文本文件或网页)读入或写出至外部来源。 Swift的字符串和字符类型完全符合Unicode标准,这正如本节所述。

Unicode标量

在内部,Swift的原生字符串类型被编译成Unicode标量。Unicode标量是由独立唯一21位数字组成,例如LATIN SMALL LETTER A(“a”)U+0061或用于FRONT-FACING BABY CHICK(“🐥”)U+1F425

NOTE:
Unicode标量是从U+0000U+D7FF或从U+E000U+10FFFF在内的任何Unicode字符。 Unicode标量不包括从U+D800U+DFFF范围内的Unicode代理对字符。

注意不是所有的21位的Unicode标量都有对应的字符,有些标量是为未来扩展而保留的。已分配的标量通常还有一个名称,比如上面提到的LATIN SMALL LETTER AFRONT-FACING BABY CHICK

字符串中的特殊字符(Special Character in String Literals)

字符串可以包含下列特殊字符:

  • 转义的特殊字符 \0(空字符),\\(反斜杠),\t(水平制表符),\n(换行),\r(回车),\"(双引号)和 \'(单引号)
  • 一个任意的Unicode标量,写为 \u {n},其中 n 是一个1-8位十六进制数字,其值等于有效的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
扩展字形集合(Extended Grapheme Clusters)

Swift的每一个字符实例都代表着一个扩展字形集合。扩展字形集合是一个或多个可产生单个人类可读字符的Unicode标量。

在下面这个例子中,字母é可以表示为单个Unicode标量éLATIN SMALL LETTER E WITH ACUTE,或U+00E9)。 但是,相同的字母也可以表示为一对标量 —— 标准字母e(拉丁小写E或拉丁字母U+0065),后跟组合ACUTE ACCENT标量(U+0301)。 组合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 é

扩展字形集合是将许多复杂脚本字符表示为单个字符值的灵活方式。 例如,来自韩文字母表的韩文字母可以表示为预先设置的或分解的序列。 这两种表述都符合Swift中的单一字符值:

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

扩展字形集合包含标记的标量(如COMBINING ENCLOSING CIRCLEU+20DD)可以将其他Unicode标量作为单个字符值的一部分:

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

用作区域指示符的Unicode标量可以成对组合以形成单个字符值,例如区域指示符符号U(REGIONAL INDICATOR SYMBOL LETTER U)(U+1F1FA)和区域指示符号表S(REGIONAL INDICATOR SYMBOL LETTER S) (U+1F1F8)的组合:

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

字符计数(Counting Characters)

使用Stringcount属性来得到字符串中字符的个数:

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

注意,Swift对于Character值使用扩展字形集合意味着字符串连接和修改可能不会影响字符串的字符数。
例如,如果您使用四个字母的单词咖啡馆初始化一个新的字符串,然后将一个组合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

NOTE:
扩展的图形集合可以由多个Unicode标量组成。这意味着不同的字符和相同字符的不同表示可能需要不同的存储量来存储。因此,Swift中的字符不会在字符串表示中占用相同的内存量。因此,需在迭代字符串的情况下计算字符串中的字符数,以确定其扩展的字形集群边界。如果您使用特别长的字符串值,请注意,获取count属性必须遍历整个字符串中的Unicode标量,以确定该字符串的字符。

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

访问和修改字符串(Accessing and Modifying a String)

我们可以通过字符串的方法和属性或通过下标语法来访问和修改字符串。

字符串索引(String Indices)

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

如上所述,不同的字符可能由不同大小的内存来存储,因此为了确定字符所在的位置,必须从该字符串的开始或结尾对每个Unicode标量进行遍历。因此,Swift字符串不能被整数值索引。

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

您可以使用String的方法index(before:)index(after:)访问给定索引之前和之后的索引。要访问离给定索引有一定距离的索引,可以使用(_:offsetBy :)方法,而不是多次调用其中一个方法。

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

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

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

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 ! 

NOTE:
对于符合Collection协议的任何类型,我们可以使用的startIndex和endIndex属性以及index(before:),index(after:)和index(_:offsetBy :)方法。 其中,符合协议的类型包括String,如此处所示,以及集合类型,如Array、Dictionary和Set。

插入和删除(Inserting and Removing)

我们可以使用insert(_:at:)方法来插入一个字符到指定索引值处,使用inser(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”

NOTE:
对于符合RangeReplaceableCollection协议的任何类型,我们可以使用insert(_:at:),insert(contentsOf:at :),remove(at :)和removeSubrange(_ :)方法。 其中,符合协议的类型包括String,如此处所示,以及集合类型,如Array、Dictionary和Set。

子串(Substrings)

当我们使用下标或像 prefix(_:) 之类的方法,从一个字符串获得一个子串时,我们获得的是子串(SubStrubg)的实例,而非其他字符串的实例。在 Swift 语言中,子串拥有大部分字符串拥有的方法,这意味着我们可以把子串当作字符串一样使用。与字符串不一样的是,在我们对字符串进行操作时,我们仅仅在很短的一段时间内使用子串。当我们需要长时间保留子串时,我们需要将实例从子串类型转化成字符串类型,如下所示:

let greeting  = "Hello, world!"
let index     = greeting.index(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 是一个字符串,当它从子串创建后,它拥有自己的内存区域,下图显示了它们之间的关系:

image

NOTE:

无论是字符串String,还是子串Substring,它们都遵循 StringProtocol 协议。如果我们在编写与字符串数据相关的代码,可以选择以遵循 StringProtocol 协议的对象作为参数,通过这样的方法,我们就可以传递 String 类型 / Substring 类型的参数

字符串比较

Swift 提供了三种比较文本值的方式:字符串与字符比较、字符串前缀比较,字符串后缀比较

字符串与字符比较

字符串与字符使用==!= 运算符进行相等比较,在章节比较操作符(Comparison Operators) 中有阐述。

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"

如果两个字符串或两个字符值的扩展字符集规范相同,那么它们将被视作相等。如果它们含有相同的意义与表现形式,那么即使组成它们的 Unicode 标量元素不一样,它们的扩展字符集规范也会被视作相等。

举个例子,LATIN SMALL LETTER E WITH ACUTEU+00E9)在规范上等同于LATIN SMALL LETTER E (U+0065)。 这两种都是使用扩展字符集表示字符 é 的有效方法,所以他们被视作在规范上相等。

// "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 A (U+0041, or "A")与俄语中的CYRILLIC CAPITAL LETTER A (U+0410, or "А")是不相等的。虽然它们两者十分相似,但是它们没有相同的语义:

let latinCapitalLetterA: Character = "\u{41}"
 
let cyrillicCapitalLetterA: Character = "\u{0410}"
 
if latinCapitalLetterA != cyrillicCapitalLetterA {
    print("These two characters are not equivalent.")
}
// Prints "These two characters are not equivalent."

NOTE:

在 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"
]

你可以对 romeoAndJuliet 数组使用 hasPrefix(\_:) 方法,从而计算出其中包含的场景1 的个数:

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(\_:) 方法计算出现 Capulet’s mansionFriar Lawrence's cell 的场景个数:

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"

NOTE:

hasPrefix(\_:)hasSuffix(\_:) 方法在每个字符串中根据扩展字符集,逐个字符执行规范相等比较,关于这一点,可以查阅 String and Character Equality

字符串的 Unicode 表示方式

当一个 Unicode 字符串被写入到一个文本文件或其他存储空间时,在字符串中的Unicode 标量将会其编码成Unicode 定义的编码形式中的一种。每种形式都会将字符串编码成更小块的代码单元。其中包含了把字符串编码成 8 位代码单元的 UTF-8 编码形式,以及能够将字符串编码成 16 位代码单元的 UTF-16 编码形式,和将字符串编码成 32 位代码单元的 UTF-32 代码形式。

Swift 提供了几种方法供我们来获取字符串的表示形式。我们可以使用 for-in 语句以 Unicode 扩展编码集的形式获取每个单独的字符串值。这个操作过程与 Working with Characters 描述的一致。

另外,我们也可以以另外三种形式来获取字符串的值:

  • UTF-8 代码单元的集合(使用字符串的 utf8 属性获取)
  • UTF-16 代码单元的集合(使用字符串的 utf16 属性获取)
  • 21 位 Unicode 标量值的集合 ,这等同于获取这个字符串的 UTF-32 编码形式(使用字符串的 unicodeScalars 属性)

下列例子展示了字符串的不同表示形式。下述字符串由 D、o、g、!! 构成(注意此处是双感叹号,或由 Unicode 标量 U+203C 构成),以及一个 🐶 的字符构成(此处 🐶 的字符代表一只小狗,由 Unicode 标量 U+1F436 构成)。

let dogString = "Dog‼🐶"
UTF-8 表示形式

你可以遍历字符串的 utf8 属性来访问字符串的 UTF-8 表示形式。这个属性的类型是 String.UTF8View,它是一个八位无符号整数类型的集合(UInt8)。这个字符串的每一个字节的 UTF-8 表示形式如下:

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

在上面的例子中,前三位十进制代码单元 (68111103) 代表字符 D、o、g,它们的表示形式与 ASCII 码的表示形式一致。排在中间的三位十进制代码单元 (226128188) 是一个三位 UTF-8 代表的双感叹号字符。最后四位代码单元 (240159144182) 是一个代表DOG FACE的四位 UTF-8 表示形式。

UTF-16 表示形式

你可以遍历字符串的 utf16 属性来访问字符串的 UTF-16 表示形式。这个属性的类型是 String.UTF16View,它是一个十六位无符号整数类型集合(UInt16)。这个字符串的每一个字节的 UTF-16 表示形式如下:

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

同样,前三个代码单元 (68111103) 代表字符 D、o、g,它们的表示形式与 UTF-8 的表示形式一致(这是因为这些 Unicode 标量代表着 ASCII 码字符)。

第四个代码单元 (822) 是一个十进制数,它的十六进制格式是 203C,代表着 Unicode 标量 U+203 ,即双感叹号字符,这个字符在 UTF-16 可以被表示成一个单独的代码单元。

第五个和第六个代码单元 (5535756374)都是DOG FACEUTF-16 表示形式。这些值是一个 U+D83D(十进制 55357)的高代理码点和 U+DC36(十进制 56374)的低代理码点。

Unicode 标量表示形式

你可以通过遍历字符串的unicodeScalars 属性字符集,来获取字符串的 Unicode 标量 表示形式。这个属性的类型是 String.ScalarView,它是一个 UnicodeScalar 类型。

每个 Unicode 标量都有一个属性用来返回 21 位标量值,它们由一个 UInt32 值表示:

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

Unicode 标量编码的前三个代码单元 (68111103) 依然代表字符 Dog

第四个代码单元(8282)是一个十进制数,它十六进制格式是 203C,它代表着 Unicode 标量 U+200C,即双感叹号字符。

最后一个 Unicode 标量128054是一个十进制数,它十六进制格式是 1F436,代表着 Unicode 标量 U+1F436 ,即DOG FACE

除了可以访问 Unicode 标量的值,每个 Unicode 标量值都可以用于构造一个新的字符串,正如同字符串插值:

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

推荐阅读更多精彩内容