Swift5.x-字符串和字符(中文文档)

引言

继续学习Swift文档,从上一章节:基本的操作,我们学习了Swift上基本的一些运算符操作,基本上跟C、OC上的运算符一样;不过,在Swift上有一些特有的运算符,可以简化代码,如??操作符、...操作符、..<操作符和单边值域,不熟悉的朋友可以到上一章节去看这部分内容(在7、8、9三个小节)。现在,我们学习Swift的字符串和字符相关的内容。由于篇幅较长,这里分篇来记录,接下来,开始吧!

这一章节内容比较简单,已经熟悉的朋友可以跳过,进入下一章节:集合类型

字符串和字符

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

Swift的字符串和字符类型提供了一种快速、兼容unicode的方式来处理代码中的文本。字符串的语法创建和操纵轻便,可读性强,与字符串的语法类似于c字符串连接非常简单,只需将两个字符串+操作符,和字符串可变性由常量或变量之间选择,就像任何其他价值迅速。还可以使用字符串将常量、变量、文本和表达式插入到更长的字符串中,这个过程称为字符串插值。这使得为显示、存储和打印创建自定义字符串值变得很容易。

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

Swift的字符串类型通过Foundation的NSString类进行连接。Foundation还扩展了String以公开NSString定义的方法。这意味着,如果你导入Foundation,你可以在字符串上访问那些NSString方法而不需要强制转换。

有关在Foundation和Cocoa中使用String的更多信息,请参见String和NSString之间架桥

1 字符串

可以在代码中以字符串文本的形式包含预定义的字符串值。字符串文字是由双引号(")包围的字符序列。

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

let someString = "Some string literal value"

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

1.1 多行字符串

如果你需要一个跨越几行的字符串,使用一个多行字符串文本-由三个双引号包围的字符序列:

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在所有其他行之前忽略哪些空白。但是,如果除了在结束引号之前还在一行的开始处添加空格,那么该空格就包含在内。


image.png

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

1.2 字符串中的特殊字符

字符串中可包含以下特殊的字符:

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

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

1.3 长字符串分隔符

可以在扩展分隔符中放置字符串文字,以便在字符串中包含特殊字符而不调用其效果。您将字符串放在引号(")中,并用数字符号(#)包围它。例如,打印字符串文本#" line1 \nLine 2"#将打印换行转义序列(\n),而不是跨两行打印字符串。

如果需要字符串文本中的字符的特殊效果,则匹配转义字符()后面的字符串中的数字符号数量。例如,如果您的字符串是#"Line 1\nLine 2"#,并且您希望换行,您可以使用#"Line 1#nLine 2"#来代替。类似地,###"Line1###nLine2"###也会中断该行。

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

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

2 初始化空字符串

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

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"

3 字符串可变性

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

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)之间进行选择,以指示字符串是否可以被改变。

4 字符串是值类型

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

Swift的copy-by-default字符串行为确保了当一个函数或方法传递给你一个字符串值时,不管它来自哪里,你都清楚地拥有这个字符串值。您可以确信,您传递的字符串不会被修改,除非您自己修改它。

在幕后,Swift的编译器优化了字符串的使用,这样实际的复制只在绝对必要的时候才会发生。这意味着当您使用字符串作为值类型时,总是能够获得良好的性能。

5 使用字符

你可以通过for-in循环遍历字符串来访问单个字符的值:

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

for-in循环参见For-In Loops

或者,您可以通过提供字符类型注释,从单字符字符串文本创建独立的字符常量或变量:

let exclamationMark: Character = "!"

字符串值可以通过将一个字符值数组作为参数传递给它的初始化器来构造:

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

6 连接字符串和字符

可以使用加法运算符(+)将字符串值相加(或连接),从而创建一个新的字符串值:

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

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

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

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合并时,结果如预期的一样有三行。

7 字符串插值

字符串插值是一种通过将常量、变量、文本和表达式的值包含在字符串文本中构造新字符串值的方法。可以在单行和多行字符串文本中使用字符串插值。你插入到字符串文字中的每一项都被包裹在一对括号中,加上一个反斜杠():

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(乘法器)* 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."

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

8 Unicode

Unicode是一种国际标准,用于在不同的书写系统中编码、表示和处理文本。它使您能够以标准化的形式表示来自任何语言的几乎所有字符,并且能够从外部源(如文本文件或web页面)读写这些字符。Swift的字符串和字符类型完全兼容unicode,如本节所述。

8.1 Unicode标量值

在后台,Swift的本地字符串类型是由Unicode标量值构建的。Unicode标量值是一个独特的21-bit数字字符或修饰符,如拉丁字母小写字母 A(“a”):U+0061 ,或前置小鸡(“🐥”):U+1F425。

注意,并不是所有的21位Unicode标量值都被分配给字符—有些标量被保留以供将来分配或在UTF-16编码中使用。分配给字符的标量值通常也有一个名称,例如上面示例中的拉丁小写字母a和前面的CHICK。

8.2 扩展字形集群

Swift字符类型的每个实例都代表一个扩展的字母簇。扩展的grapheme集群是由一个或多个Unicode标量组成的序列,(在组合时)生成一个可读的字符。

这是一个例子。字母e可以表示为单个Unicode标量é(带锐角的拉丁小写字母e,或U+00E9)。但是,同一个字母也可以表示为一对标量—标准字母e(拉丁小写字母e,或U+0065),后面是合并的重音标量(U+0301)。组合的重音标量以图形方式应用于它前面的标量,在支持unicode的文本呈现系统呈现时将e转换为é。

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

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 🇺🇸

9 计算字符

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

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

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

举个例子,如果你初始化一个新的字符串与四字咖啡馆,然后添加一个结合急性口音(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扩展字符群的数量。

10 访问和修改字符串

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

10.1 字符串索引

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

如上所述,不同的字符需要存储不同的内存,因此,为了确定哪个字符位于特定位置,必须从该字符串的开始或结束遍历每个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 ! "

注意
你可以在任何符合集合协议的类型上使用startIndex和endIndex属性以及index(before:), index(after:)和index(_:offsetBy:)方法。这包括字符串(如这里所示)以及数组、字典和Set等集合类型。

10.2 插入和删除

使用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(:)方法。这包括字符串(如这里所示)以及数组、字典和Set等集合类型。

10.3 子字符串

当您从字符串中获取子字符串时——例如,使用下标或类似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是一个字符串,这意味着它有一个存储组成字符串的字符的内存区域。因为“开始”是“问候语”的一个子串,它重复了问候语所使用的记忆。相反,newString是一个字符串——当它从子字符串创建时,它有自己的存储空间。下图显示了这些关系:


image.png

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

11 比较字符串

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

11.1 字符串和字符相等

字符串和字符是否相等用“equal to”操作符(==)和“not equal to”操作符(!=)检查,如比较操作符中所述:

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集群在规范上是等效的,则认为它们是相等的。如果扩展的grapheme集群具有相同的语言意义和外观,那么它们在标准上是等价的,即使它们是由不同的Unicode标量在幕后组成的。

例如,拉丁字母E加上锐音(U+00E9)在标准上等同于拉丁字母E (U+0065)后面加上锐音(U+0301)。这两种扩展的grapheme集群都是表示字符é的有效方式,因此它们在规范上被认为是等价的:

// "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中的字符串和字符比较对语言环境不敏感。

11.2 前缀和后缀相等

要检查一个字符串是否有特定的字符串前缀或后缀,可以调用该字符串的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集群之间逐字符执行标准等价性比较,如字符串和字符相等性中所述。

12 字符串的Unicode表示

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

Swift提供了几种不同的方式来访问字符串的Unicode表示。您可以使用for-in语句遍历该字符串,以访问作为Unicode扩展字符群的单个字符值。这个过程在字符处理中有描述。

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

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

下面每一个例子展示了一个不同的表示下面的字符串,这是由人物的D, o, g,‼(双感叹号,或者Unicode标量U+203C),和🐶字符(狗脸,或者Unicode标量U+1F436):

let dogString = "Dog‼🐶"

12.1 UTF-8 表示

通过迭代字符串的utf8属性,可以访问字符串的UTF-8表示形式。此属性的类型为String。UTF8View,它是一个无符号8位(UInt8)值的集合,对应字符串UTF-8表示的每个字节:


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

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

12.2 UTF-16表示

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


image.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的十进制等价物,203C表示双感叹号字符的Unicode标量U+203C。这个字符可以用UTF-16表示为单个代码单元。

第五个和第六个codeUnit值(55357和56374)是狗脸字符的UTF-16代理项对表示。这些值分别是U+D83D的高代理值(十进制值55357)和U+DC36的低代理值(十进制值56374)。

12.3 Unicode标量表示

通过迭代字符串的unicodeScalars属性,可以访问字符串值的Unicode标量表示形式。此属性的类型是UnicodeScalarView,它是UnicodeScalar类型的值的集合。

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


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

前三个UnicodeScalar值(68、111、103)的值属性再次表示字符D、o和g。

第四个codeUnit值(8252)也是十六进制值203C的十进制等价物,203C表示Unicode标量U+203C,表示双感叹号字符。

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

除了查询它们的值属性,每个UnicodeScalar值也可以用来构造一个新的字符串值,例如字符串插值:

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

总结

这一章节讲的主要是字符串和字符相关的内容,包括字符串的初始化,多行字符串如何表示,字符串的可变性,字符串是值类型,如何使用字符,连接字符串和字符的方法,字符串差值的用法,Unicode,以及字符串的属性的用法。Swift上字符串的索引使用和OC上的还是有很大差别的,在文章里也有相应的案例,大家最好自己敲代码操作一下,这样才能加深印象。好了,这一块内容也比较简单,已经掌握的朋友可以跳过去看下一章节。

最后,喜欢这篇文章的朋友可以给个star,以鼓励我更加积极地去做这件事,嘿嘿~

上一章节:基本的操作
下一章节:集合类型

参考文档:Swift - Strings and Characters

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

推荐阅读更多精彩内容