Swift 字符串和字符

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

Swift的String和Character类型提供了一种快速、兼容Unicode的方法来处理代码中的文本。字符串创建和操作的语法,字符串文字语法类似于C,是轻量级和易读性的。字符串连接就像用+运算符组合两个字符串一样简单,字符串可变性是通过在常量或变量之间选择来管理的,就像Swift中的任何其他值一样。还可以使用字符串将常量、变量、文本和表达式插入较长的字符串中,这一过程称为字符串插值。这使得为显示、存储和打印来创建自定义字符串值变得容易。

尽管语法如此简单,但Swift的String类型是一种快速、现代的字符串实现。每个字符串都由编码独立的Unicode字符组成,并支持在各种Unicode表示中访问这些字符。

注意
Swift的String类型与Foundation中的NSString类相关联。Foundation还扩展了String以公开NSString定义的方法。这意味着,如果导入Foundation,则可以访问字符串上的那些NSString方法,而无需强制转换。
有关在Foundation和Cocoa中使用String的更多信息,请参阅String和NSString之间的桥接

字符串文字

可以将预定义的字符串值作为字符串文字包含在代码中。字符串文字是由双引号(")包围的字符序列。
使用字符串文字作为常量或变量的初始值:

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,在所有其他行之前忽略哪些空格。但是,如果我们在一行的开头加上右引号之前的空格,则会包含该空格。


截屏2021-02-03 下午5.14.47.png

在上面的示例中,即使整个多行字符串文本缩进,字符串中的第一行和最后一行也不会以任何空格开头。中间的行比右引号有更多空格的缩进,所以它从额外的四个空格缩进开始。

字符串文字中的特殊字符

字符串文字可以包括以下特殊字符:

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

下面的代码显示了这些特殊字符中的四个示例。wiseWords常量包含两个转义双引号。dollarSign、blackHeart和SparkingHeart常量演示了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: """
"""#

初始化空字符串

创建空字符串值来作为生成较长字符串的起点,请将空字符串文字赋给变量,或使用初始化方法初始化新字符串实例:

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

通过检查字符串的Boolean isEmpty属性来确定字符串值是否为空:

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

字符串易变性

我们可以通过将特定字符串赋给变量(在这种情况下可以修改)或常量(在这种情况下不能修改)来表示是否可以修改(或改变)一个特定的字符串:

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中的字符串修改,因为它们是在两个类(NSString和NSMutableString)之间进行选择,以指示字符串是否可以变异。

字符串是值类型

Swift的字符串类型是值类型。如果我们创建了一个新的字符串值,当它被传递给函数或方法时,或者当它被赋给常量或变量时,该字符串值就会被复制。在每种情况下,都会创建现有字符串值的新副本,并传递或分配新副本,而不是原始版本。值类型在结构和枚举是值类型中描述。
Swift的这种默认复制字符串行为,可以确保当一个函数或方法传递给我们一个字符串值时,不管它来自何处,我们都可以明确地拥有该字符串值。我们可以确信,传递给我们的字符串不会被修改,除非我们自己修改它。
Swift的编译器在背后优化了字符串的使用,使得真正的复制只在绝对必要的时候发生。这意味着我们在将字符串作为值类型使用时总是可以获得很好的性能。

使用字符

通过使用for-in循环对字符串进行迭代,可以访问字符串的各个字符值:

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

for-in循环在for-in循环中进行了描述。
或者,可以通过提供字符类型注释,从单个字符串文字创建独立的字符常量或变量:

let exclamationMark: Character = "!"

可以通过将字符值数组作为参数传递给其初始值设定项来构造字符串值:

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

连接字符串和字符

可以使用加法运算符(+)将字符串值相加(或串联)以创建新的字符串值:

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"

我们可以使用String类型的append() 方法将字符值追加到字符串变量:

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

注意
不能将字符串或字符追加到现有字符变量,因为字符值只能包含单个字符。

如果使用多行字符串文字来构建较长的字符串,则字符串中的每一行都会以换行符结束,包括最后一行。例如:

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

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

字符串插值

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

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的值也是字符串后面较大表达式的一部分。此表达式计算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的字符串和字符类型完全符合Unicode,如本节所述。

Unicode标量值

在后台,Swift的原生字符串类型是从Unicode标量值构建的。Unicode标量值是字符或修饰符的唯一21位数字,例如U+0061表示拉丁文小写字母A(“a”),或U+1F425表示前向小鸡(“🐥").
请注意,并非所有21位Unicode标量值都分配给一个字符,有些标量是为将来的分配或UTF-16编码中使用而保留的。已分配给字符的标量值通常也有一个名称,例如上面示例中的拉丁文小写字母a和面向前方的雏鸡。

扩展的图形集簇

Swift字符类型的每个实例都表示一个扩展的grapheme集群。扩展的grapheme集群是由一个或多个Unicode标量组成的序列,当这些标量组合在一起时,会产生一个人类可读的字符。
下面是一个例子。字母é可以表示为单个Unicode标量é(带锐音符的拉丁文小写字母E,或U+00E9)。但是,同一个字母也可以表示为一对标量——一个标准字母e(拉丁文小写字母e,或U+0065),后跟组合的锐音符标量(U+0301)。组合的锐音符标量以图形方式应用于它前面的标量,当它由支持Unicode的文本呈现系统呈现时,将e转换为é。
在这两种情况下,字母é都表示为单个Swift字符值,该值表示扩展的grapheme集群。在第一种情况下,集群包含一个标量;在第二种情况下,集群包含两个标量:

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

扩展的grapheme集群是一种灵活的方法,可以将许多复杂的脚本字符表示为单个字符值。例如,韩国语字母表中的朝鲜文音节可以表示为预合成或分解的序列。在Swift中,这两种表示都可以作为单个字符值:

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

扩展的grapheme集群允许用于封闭标记的标量(例如组合封闭圆或U+20DD)将其他Unicode标量封闭为单个字符值的一部分:

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

区域指示器符号的Unicode标量可以成对组合以形成单个字符值,例如区域指示器符号字母U(U+1F1FA)域指示器符号字母S(U+1F1F8)的组合:

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

计数字符

要检索字符串中字符值的计数,请使用该字符串的count属性:

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

请注意,Swift对字符值使用扩展的grapheme集群意味着,字符串连接和修改可能并不总是会影响字符串的字符计数。

例如,如果使用四个字符的单词cafe来初始化一个新字符串,然后在字符串末尾附加一个组合锐音符(U+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"

注意
扩展的grapheme集群可以由多个Unicode标量组成。这意味着不同的字符和同一字符的不同表示,可能需要不同数量的内存来存储。因此,Swift中的字符在字符串表示中占用的内存量并不相同。因此,如果不遍历字符串来确定其扩展的grapheme集群边界的话,那么就无法计算出字符串中的字符数。如果我们使用的是特别长的字符串值,请注意count属性必须迭代整个字符串中的Unicode标量,以便确定该字符串的字符个数。
count属性返回的字符计数,并不总是与包含相同字符的NSString的length属性相同。NSString的长度基于字符串的UTF-16表示中16位编码单元的数量,而不是字符串中Unicode扩展的grapheme集群的数量。

访问和修改字符串

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

字符串索引

每个字符串值都有一个关联的索引类型,字符串索引,对应于字符串中每个字符的位置。
如上所述,不同的字符可能需要不同数量的内存来存储,因此为了确定哪个字符位于特定位置,必须从字符串的开始或结束处迭代每个Unicode标量。因此,Swift字符串不能被整数值索引(不是很明白为什么)
使用startIndex属性访问字符串第一个字符的位置。endIndex属性是字符串中最后一个字符之后的位置。因此,endIndex属性不是字符串下标的有效参数。如果字符串为空,则startIndex和endIndex相等。
使用String的index(before:)index(after:)方法访问给定索引之前和之后的索引。要访问距离给定索引较远的索引,可以使用index(_:offsetBy:)方法,而不是多次调用其中一个方法。
可以使用下标语法访问特定字符串索引处的字符。

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

注意
我们可以对遵守Collection协议的任何类型,都可以使用startIndex和endIndex属性以及index(before:),index(after:)index(_:offsetBy:) 方法。这包括字符串,以及集合类型,如数组、字典和集合。

插入和删除

若要在指定索引处的字符串中插入单个字符,请使用 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(_:)方法。这包括字符串,以及集合类型,如数组、字典和集合。

子字符串

例如,使用下标或prefix(_:)之类的方法从字符串获取子字符串时,结果就是表示子字符串的实例,而不是另一个字符串。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是一个字符串,这意味着它有一个存储组成字符串的字符的内存区域。因为开头是greeting的子字符串,所以它重用了greeting使用的内存。相反,newString是一个由子字符串创建的字符串,它有自己的存储空间。下图显示了这些关系:

截屏2021-02-03 下午5.33.54.png

注意
字符串和子字符串都遵守StringProtocol协议,这意味着字符串操作函数通常可以方便地接受StringProtocol值。可以使用字符串或子字符串值调用此类函数。

比较字符串

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"

如果两个字符串值(或两个字符值)的扩展grapheme簇在规范上是等价的,则认为它们相等。如果扩展的字组簇具有相同的语言含义和外观,则它们在规范上是等价的,即使它们是由不同的Unicode标量在幕后组成的。

例如,带锐音符(U+00E9)的拉丁文小写字母E在规范上等同于拉丁文小写字母E(U+0065)后跟组合锐音符(U+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"

相反,英语中使用的拉丁文大写字母A(U+0041,或“A”)与俄语中使用的西里尔文大写字母A(U+0410,或“А”)并不相等。这些字符在视觉上相似,但语言意义不同:

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

注意
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(_:)方法在每个字符串中的扩展grapheme集群之间执行逐字符的规范等价比较,如字符串和字符相等中所述。

字符串的Unicode表示

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

Swift提供了几种不同的方法来访问字符串的Unicode表示。可以使用for-in语句对字符串进行迭代,以将其单个字符值作为Unicode扩展的grapheme集群进行访问。在“使用字符”中介绍了此过程。

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

  • 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,它是无符号8位(UInt8)值的集合,字符串的UTF-8表示形式中的每个字节对应一个值:

截屏2021-02-03 下午5.40.08.png

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

在上面的示例中,前三个十进制codeUnit值(68111103)表示字符D、o和g,其UTF-8表示形式与其ASCII表示形式相同。接下来的三个十进制codeUnit值(226、128、188)是双感叹号字符的三字节UTF-8表示。最后四个codeUnit值(240、159、144、182)是狗脸字符的四字节UTF-8表示。

UTF-16表示法

通过迭代字符串的utf16属性,可以访问字符串的UTF-16表示形式。此属性的类型为String.UTF16View,它是无符号16位(UInt16)值的集合,在字符串的UTF-16表示形式中,每个16位编码单元对应一个值:


截屏2021-02-03 下午5.45.07.png
for codeUnit in dogString.utf16 {
    print("\(codeUnit) ", terminator: "")
}
print("")
// Prints "68 111 103 8252 55357 56374 "

同样,前三个codeUnit值(68、111、103)表示字符D、o和g,它们的UTF-16代码单元的值与字符串的UTF-8表示中的值相同(因为这些Unicode标量表示ASCII字符)。
第四个codeUnit值(8252)是十六进制值203C的十进制等价值,它表示双感叹号字符的Unicode标量U+203C。在UTF-16中,这个字符可以表示为单个编码单元。
第五和第六个codeUnit值(55357和56374)是狗脸字符的UTF-16的表示对。这些值是高位表示U+D83D(十进制值55357)和低位表示U+DC36(十进制值56374)。

Unicode标量表示法

我们可以通过迭代字符串值的 unicodeScalars 属性来访问字符串值的Unicode标量表示。此属性的类型为UnicodeScalarView,它是UnicodeScalar类型的值的集合。
每个UnicodeScalar都有一个value属性,返回标量的21位值,用UInt32值表示:


截屏2021-02-03 下午5.47.58.png
for scalar in dogString.unicodeScalars {
    print("\(scalar.value) ", terminator: "")
}
print("")
// Prints "68 111 103 8252 128054 "

前三个UnicodeScalar值(68, 111, 103)的value属性,还是表示字符D、o和g。
第四个codeUnit值(8252)也是十六进制值203C的十进制等价值,它表示双感叹号字符的Unicode标量U+203C。
第五个也是最后一个UnicodeScalar 128054的value属性是十六进制值1F436的十进制等价值,它表示狗脸字符的Unicode标量U+1F436。
作为查询其值属性的替代方法,每个UnicodeScalar值也可用于构造新的字符串值,例如使用字符串插值:

for scalar in dogString.unicodeScalars {
    print("\(scalar) ")
}
// D
// o
// g
// ‼
// 🐶
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容