Swift Learning (5) - Strings and Characters (Fully Ver.)

Swift Learning (5) - Strings and Characters (Fully Ver.)

August 13, 2021·Jensen
Jensen

image

字符串是一系列字符的集合,例如“Hello, world”,“albatross”。Swift的字符串通过String类型来表示。而String内容的访问方式有多种,例如以Character值的集合。

SwiftStringCharacter类型提供了一种快速且兼容Unicode的方式来处理代码中的文本内容。创建和操作字符串的语法与C语言中字符串操作相似,轻量并且易读。通过+符号就可以非常简单的实现两个字符串的拼接操作。与Swift中其他值一样,能否更改字符串的值,取决于其被定义为常量还是变量。

开发者可以在已有字符串中插入常量、变量、字面量和表达式从而形成更长的字符串,这一过程也被称为字符串差值。尤其是在为显示、存储和打印创建自定义字符串值时,字符串插值操作尤其有用。

尽管语法简易,但Swift中的String类型的实现却很快速和现代化。每一个字符串都是由编码无关的Unicode字符组成,并支持访问字符的多种Unicode表现形式。

注意

Swift的String类型与Foundation NSString类进行了无缝桥接。Foundation还对String进行扩展使其可以访问NSString类型中定义的方法。这意味着调用那些NSString的方法无需进行任何类型转换。


字符串字面量

开发者可以在代码里使用一段预定义的字符串值作为字符串字面量。字符串字面量是由一对双引号包裹着的具有固定顺序的字符集。

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

let someString = "Some string literal value"  // 字符串字面量初始化String类型常量

注意,Swift之所以推断someString常量为字符串类型,是因为它使用了字面量方式进行初始化。

多行字符字面量

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

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 til you come to the end; then stop."
    """
print(quotation)
---
output: 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 til you come to the end; then stop."

一个多行字符串字面量包含了所有的在开启和关闭引号"""中的行。这个字符从开启引号"""之后的第一行开始,到关闭引号"""之前为止。这就意味着字符串开启引号之后或者结束引号之前都没有换行符号。

下例中两个字符串其实是一样的,虽然第二个使用了多行字符串的形式。

let singleLineString = "These are the same."
let multiLineString = """
These are the same.
"""
print("单行字符串:\(singleLineString)")
print("多行字符串:\(multiLineString)")
---
output: 单行字符串:These are the same.
多行字符串: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."
"""
print("使用了换行符的quotation:\(softWrappedQuotation)")
---
output: 使用了换行符的quotationThe 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.

"""
print(lineBreaks)
---
output: 
This string starts with a line break.
It also ends with a line break.

一个多行字符串字面量能够缩进来匹配周围的代码。关闭引号"""之前的空白字符串告诉Swift编译器其他各行多少空白字符串需要忽略。然而,如果在某行的前面写的空白字符串超出了关闭引号之前的空白字符串,则超出部分将被包含在多行字符串字面量中。

let linesWithIndentation = """
    This line doesn't begin with whitespace.
        This line begins with four spaces.
    This line doesn't begin with whitespace.
    """  // 关闭引号前的空白字符串有4个空格
print(linesWithIndentation)
---
output: This line doesn't begin with whitespace.
    This line begins with four spaces.
This line doesn't begin with whitespace.

上面的例子中,尽管整个多行字符串字面量都是缩进的(源代码缩进),第一行和最后一行没有以空白字符串开始(实际的变量值)。中间一行的缩进用的空白字符串(源代码缩进)比关闭引号之前的空白字符串还要多,所以行首有4个空格。

字符串字面量的特殊字符

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

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

下面的代码为各种特殊字符的使用示例。

let wiseWords = "\"Imagination is more important than knowledge.\" - Einstein"
let dollarSign = "\u{24}"  // $,Unicode标量 U+0024
let blackHeart = "\u{2665}"  // ♥,Unicode标量 U+2665
let sparklingHeart = "\u{1F496}"  // 💖,Unicode标量 U+1F496
print(wiseWords)
print(dollarSign, terminator: " ")
print(blackHeart, terminator: " ")
print(sparklingHeart)
---
output: "Imagination is more important than knowledge." - Einstein
$  💖

由于多行字符串字面量使用了三个双引号而不是一个,所以可以在多行字符串字面量里直接使用双引号"而不必加上转义符\。要在多行字符串字面量中使用"""的话,就需要使用至少一个转义符(在多行字符串字面量中可以使用"““转义三个双引号,也可使用"""转义)。

let threeDoubleQuotes = """
    Escaping the first quote \"""
    Escaping all three quotes \"\"\"
    """
print(threeDoubleQuotes)
---
output: Escaping the first quote """
Escaping all three quotes """

拓展字符串分隔符

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

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

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

let threeMoreDoubleQuotationMarks = #"""
Here are three more double quotes: """
Good! Good!
"""#
print(threeMoreDoubleQuotationMarks)
---
output: Here are three more double quotes: """
Good! Good!

初始化空字符串

要创建一个空字符串作为初始值,可以将空的字符串字面量赋值给变量,也可以初始化一个新的String实例。

var emptyString = ""  // 空字符串字面量
var anotherEmptyString = String()  // 初始化方法
// 两个字符串均为空并等价

可以通过检查Bool类型的’isEmpty’属性来判断该字符串是否为空。

if emptyString.isEmpty {
    print("Nothing to see here.")  // 打印输出“Nothing to see here.”
}
---
output: Nothing to see here.

字符串可变性

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

var variableString = "Horse"
variableString += " and carriage"  // variableString现在为“Horse and carriage”
print(variableString)

let constantString = "Highlander"
//constantString += " and another Hignlander"  // 若取消这行注释,编译器会报错:Left side of mutating operator isn't mutable: 'constantString' is a 'let' constant
print(constantString)
---
output: Horse and carriage
Highlander

注意

在Objective-C和Cocoa中,需要通过选择两个不同的类(NSString和NSMutableString)来指定字符串是否可以被修改。


字符串是值类型

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

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

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


使用字符

可以使用for-in循环来遍历字符串,获取字符串中的每一个字符的值。

for character in "Dog!🐶" {
    print(character)
}
// 另外,通过标明一个`Character`类型并用字符字面量进行赋值,可以建立一个独立的字符常量或变量。
let exclamationMark: Character = "!"
// 字符串可以通过传递一个值类型为`Character`的数组作为自变量来初始化。
let catCharacters: [Character] = ["c", "a", "t", "!", "🐱"]
let catString = String(catCharacters)
print(catString)  // 打印"cat!🐱"
---
output: D
o
g
!
🐶
cat!🐱

连接字符串和字符

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

let string1 = "hello"
let string2 = " jensen"
var welcome = string1 + string2  // welcome现在等于“hello jensen”
print(welcome)
---
output: hello jensen

当然也可通过加法赋值运算符+=将一个字符串添加到一个已经存在的字符串变量上。

var instruction = "look over"
instruction += string2  // instruction现在等于“look over jensen”
print(instruction)
---
output: look over jensen

可以使用append()方法将一个字符附加到一个字符串变量的尾部。

let questionMark: Character = "?"
welcome.append(questionMark)  // welcome现在等于“hello jensen?”
print(welcome)
---
output: hello jensen?

注意

不能将一个字符串或者字符添加到一个已经存在的字符变量上,因为字符变量只能包含一个字符。

如果需要使用多行字符串字面量来拼接字符串,并且需要字符串每一行都以换行符结尾,包括最后一行。

let badStart = """
    one
    two
    """
let end = """
    three
    """
print(badStart + end)  // 打印两行:one\ntwothree

let goodStart = """
    one
    two
    
    """
print(goodStart + end)  // 打印三行:one\ntwo\nthree
---
output: one
twothree
one
two
three

上面的例子中,把badStartend拼接起来的字符串非我们想要的结果,因为badStart最后一行没有换行符,会和end的第一行结合到一起。相反的,goodStart每一行都以换行符为结尾,所以和end拼接的字符串总共有三行。


字符串插值

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

let multiplier = 3
let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"  // message是“3 times 2.5 is 7.5”
print(message)
---
output: 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)."#)  // 打印“Write an interpolated string in Swift using \(multiplier).”
---
output: Write an interpolated string in Swift using \(multiplier).

如果要值使用扩展字符串分隔符的字符串中使用字符串插值,需要在反斜杠后面添加与开头和结尾数量相同的扩展字符串分隔符。

print(#"6 times 7 is \#(6 * 7)"#)  // 打印“6 times 7 is 42”
---
output: 6 times 7 is 42

注意

插值字符串中写在括号中的表达式不能包含非转义反斜杠\,并且不能包含回车或者换行符。不过,插值字符串可以包含其他字面量。


Unicode

Unicode是一个用于在不同书写系统中对文本进行编码、表示和处理的国际标准。它使开发者可以用标准格式来表示来自任意语言的几乎所有的字符,并能够对文本文件或网页这样的外部资源中的字符进行读写操作。SwiftStringCharacter类型是完全兼容Unicode标准的。

Unicode标量

SwiftString类型是基于Unicode标量而建立的。Unicode标量是对应字符或者修饰符的唯一的21位数字,例如U+0061表示小写的拉丁字母(LATIN SMALL LETTER A)(“a”),U+1F425表示小鸡表情(FRONT-FACING BABY CHICK)(“🐤”)。(UTF-32编码的最大长度是4字节,四字节模版:11110XXX 10XXXXXX 10XXXXXX 10XXXXXX,因此有21位数字)

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

可扩展的字形群集

每一个SwiftCharacter类型代表一个可扩展的字形群。而一个可扩展的字形群构成了人类可读的单个字符,它由一个或多个(当组合时)Unicode标量的序列组成。

举个例子,字母é可以用单一的Unicode标量é(LATIN SMALL LETTER E WITH ACUTE,或者U+00E9)来表示。然而一个标准的字母e(LATIN SMALL LETTER E,或者U+0065)加上一个急促重音(COMBINING ACUTE ACCENT)的标量(U+0301),这样一对标量就表示了同样地字母é。这个急促重音的标量形象地将e转换成了é

在这两种情况中,字母é代表了一个单一的SwiftCharacter值,同时代表了一个可扩展的字形群。在第一种情况,这个字形群包含一个单一的标量;而在第二种情况,它是包含两个标量的字形群。

let eAcute: Character = "\u{E9}"  // é 代表一个单一的Swift的Character值,同时代表了一个可扩展的字形群,该字形群包含一个单一的标量
let combinedEAcute = "\u{65}\u{301}"  // é 代表一个可扩展的字形群,该字形群包含两个标量
print("eAcute is \(eAcute) and combinedEAcute is \(combinedEAcute)")
---
output: eAcute is é and combinedEAcute is é

可扩展的字形集是一个将许多复杂的脚本字符表示为单个字符值的灵活方式。例如,来自朝鲜字母表的韩语音节能表示为组合或分解的有序排列。在Swift都会表示位同一个单一的Character值。

let precomposed: Character = "\u{D55C}"  // 한
let decomposed: Character = "\u{1112}\u{1161}\u{11AB}"  // ᄒ, ᅡ, ᆫ
print("precomposed的值是\(precomposed),decomposed的值是\(decomposed)")
---
output: precomposed的值是한,decomposed的值是한

可扩展的字符群集可以使包围记号(例如COMBINING ENCLOSING CIRCLE或者U+20DD)的标量包围其他Unicode标量,作为一个单一的Character值。

let enclosedEAcute: Character = "\u{E9}\u{20DD}"  // enclosedEAcute是é⃝
print(enclosedEAcute)
---
output: é⃝

地域性指示符号的Unicode标量可以组合成一个单一的Character值,例如REGIONAL INDICATOR SYMBOL LETTER H(U+1F1ED)REGIONAL INDICATOR SYMBOL LETTER K(U+1F1F0)

let regionalIndicatorForHK: Character = "\u{1F1ED}\u{1F1F0}"  //HK
print(regionalIndicatorForHK)  //  regionalIndicatorForHK是🇭🇰
---
output: 🇭🇰

计算字符数量

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

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

注意在Swift中,使用可扩展的字符群集作为Character值来连接或改变字符串时,并不一定会更改字符串的字符数量。

例如,如果用四个字符的单词cafe初始化一个新的字符串,然后添加一个COMBINING ACUTE ACCENT (U+0301)作为字符串的结尾。最终这个字符串的字符数量仍然是4,因为这四个字符现在是café,长度依然是4。

var word = "cafe"
print("The number of characters in \(word) is \(word.count).")  // 打印输出“The number of characters in cafe is 4.”

word += "\u{301}"
print("The number of characters in \(word) is \(word.count).")  // 打印输出”The number of characters in café is 4.“
---
output: The number of characters in cafe is 4.
The number of characters in café is 4.

注意

可扩展的字形群可以由多个Unicode标量组成。这意味着不同的字符以及相同字符的不同表示方式可能需要不同数量的内存空间来存储。所以Swift中的字符在一个字符串中并不一定占用相同的内存空间。因此在没有获取字符串的可扩展的字符群的范围时候,就不能计算出字符串的字符数量。如果正在处理一个长字符串,需要注意count属性必须遍历全部的Unicode标量,来确定字符串的字符数量。

另外需要注意的是通过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:)方法。

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

let greeting = "Jensen Jon!"
print("greeting's first character is \(greeting[greeting.startIndex]).")  // J
print("The character before greeting's endIndex is \(greeting[greeting.index(before: greeting.endIndex)])")  // !
print("The character after greeting's startIndex is \(greeting[greeting.index(after: greeting.startIndex)])")  // e
print("The character with a offset of 7 from greeting's startIndex is \(greeting[greeting.index(greeting.startIndex, offsetBy: 7)])")  // J
---
output: greeting's first character is J.
The character before greeting's endIndex is !
The character after greeting's startIndex is e
The character with a offset of 7 from greeting's startIndex is J

试图获取越界索引对应的Character,将引发一个运行时的错误。

//greeting[greeting.endIndex]  // 若取消注释,运行时系统会报错:Fatal error: String index is out of bounds
//greeting.index(after: greeting.endIndex)  // 若取消注释,运行时系统会报错:Fatal error: String index is out of bounds

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

for index in greeting.indices {
    print("\(greeting[index])", terminator: " ")  // 打印输出“J e n s e n   J o n !”
}
print("")
---
output: J e n s e n   J o n ! 

注意

可以使用startIndex和endIndex属性或者index(before:)、index(after:)和index(_:offsetBy:)方法在任意一个确定的并遵循Collection协议的类型里面。除了上面所示的String,也可以使用在Array、Dictionary和Set中。

插入和删除

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

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

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

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

friendlyGreeting.remove(at: friendlyGreeting.index(before: friendlyGreeting.endIndex))  // friendlyGreeting变量现在等于“hello there”

let range = friendlyGreeting.index(friendlyGreeting.endIndex, offsetBy: -6)..<friendlyGreeting.endIndex
friendlyGreeting.removeSubrange(range)  // friendlyGreeting变量现在等于“hello”
print(friendlyGreeting)
---
output: hello

注意

可以使用insert(:at:)、insert(contentsOf:at:)、remove(at:)和removeSubrange(:)方法在任意一个确定的并遵循RangeReplaceableCollection协议的类型里面/除了上面用到的String,也可以使用在Array、Dictionary和Set中。


子字符串

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

let niceGreeting = "Hello, Jensen!"
let index = niceGreeting.firstIndex(of: ",") ?? niceGreeting.endIndex
let beginning = greeting[..<index]  // beginning的值是“Hello”
// 把结果转化为String以便长期存储。
let newString = String(beginning)
print("newString is \(newString).")
---
output: newString is Jense.

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

上述例子中,niceGreeting是一个String,意味着它在内存里有一片空间保存字符集。而由于beginningniceGreetingSubstring,它重用了niceGreeting的内存空间。相反,newString是一个String,其是使用Substring创建的,拥有一片自己的内存空间。

String substring

注意

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


比较字符串

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

字符串/字符相等

字符串/字符可以用等于操作符==和不等于操作符!=

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

如果两个字符串(或者两个字符)的可扩展的字形群集是标准相等,那就认为它们是相等的。只要可扩展的字形群集有同样的语言意义和外观则认为它们标准相等,即使它们是由不同的Unicode标量构成。

例如, LATIN SMALL LETTER E WITH ACUTE (U+00E9)就是标准相等于LATIN SMALL LETTER E (U+0065)后面加上COMBINING ACUTE ACCENT (U+0301)。这两个字符群集都是表示字符é的有效方式,所以它们被认为是标准相等的。

let eAcuteQuestion = "Voluez-vous un caf\u{E9}?"
let combinedEAcuteQuestion = "Voluez-vous un caf\u{65}\u{301}?"

if eAcuteQuestion == combinedEAcuteQuestion {
    print("These two strings are considered equal.")  // 打印输出“These two strings are considered equal.”
}
---
output: These two strings are considered equal.

相反,英语中的LATIN CAPITAL LETTER A (U+0041)不等于俄语中的CYRILLIC CAPITIAL LETTER A (U+0410)。两个字符看着是一样的,但却有不同的语言意义。

let latinCapitialLetterA: Character = "\u{41}"
let cyrillicCapitalLetterA: Character = "\u{410}"

if latinCapitialLetterA != cyrillicCapitalLetterA {
    print("These two characters are not equivalent.")  // 打印输出“These two characters are not equivalent.”
}
---
output: These two characters are not equivalent.

注意

在Swift中,字符串和字符并不区分地域(not locale-sensitive)。

前缀/后缀相等

通过调用字符串的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.")
---
output: 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")
---
output: 6 mansion scenes; 2 cell scenes

注意

hasPrefix(:)和hasSuffix(:)方法都是在每个字符串中逐字符比较其可扩展的字符群集是否标准相等。


字符串的Unicode表示形式

当一个Unicode字符串被写进文本文件或者其他存储时,字符串中的Unicode标量会用Unicode定义的几种编码格式(encoding forms)编码。每个字符串中的小块编码都被称为代码单元(code units)。这些包括UTF-8编码格式(编码字符串为8位的代码单元),UTF-16编码格式(编码字符串为16位的编码单元),以及UTF-32编码格式(编码字符串为32位的编码单元)。

Swift提供了几种不同的方式来访问字符串的Unicode表示形式。可以利用for-in来对字符串进行遍历,从而以Unicode可扩展的字符群集的方式访问每一个Character值。

另外,能够以其他三种Unicode兼容的方式访问字符串的值:

  • UTF-8代码单元集合(利用字符串的utf8属性进行访问)
  • UTF-16代码单元集合(利用字符串的utf16属性进行访问)
  • 21位的Unicode标量值集合,也就是字符串的UTF-32编码格式(利用字符串的unicodeScalars属性进行访问)

下面由Dog,!!(DOUBLE EXCLAMATION MARK, Unicode标量 U+203C)🐶(DOG FACE,Unicode标量 U+1F436)组成的字符串中的每一个字符代表着一种不同的表示。

let dogString = "Dog‼🐶"

UTF-8表示

可以通过遍历Stringutf8属性来访问它的UTF-8表示。其为String.UTF8View类型的属性,UTF8View是无符号8位(UInt8)值的集合,每一个UInt8值都是一个字符的UTF-8表示:

CharacterD
U+0044
o
U+006F
g
U+0067

U+203C
🐶
U+1F436
UTF-8
Code Unit
68111103226128188240159144182
Position0123456789
for codeUnit in dogString.utf8 {
    print("\(codeUnit) ", terminator: "")
}
print("")
---
output: 68 111 103 226 128 188 240 159 144 182 

上面的例子中,前三个十进制codeUnit值(68、111、103)代表了字符Dog,它们的UTF-8表示与ASCII表示相同。接下来的三个十进制codeUnit值(226、128、188)是DOUBLE EXCLAMATION MARK的3字节UTF-8表示。最后的四个codeUnit值(240、159、144、182)是DOG FACE的4字节UTF-8表示。

UTF-16表示

可以通过遍历Stringutf16属性来访问它的UTF-16表示。其为String.UTF16View类型属性,UTF16View是无符号16位(UInt16)值的集合,每一个UInt16都是一个字符的UTF-16表示:

CharacterD
U+0044
o
U+006F
g
U+0067

U+203C
🐶
U+1F436
UTF-16
Code Unit
6811110382525535756374
Position012345
for codeUnit in dogString.utf16 {
    print("\(codeUnit) ", terminator: "")
}
print("")
---
output: 68 111 103 8252 55357 56374

同样,前三个codeUnit值(68、111、103)代表了字符Dog,它们的UTF-16代码单元和UTF-8完全相同(因为这些Unicode标量表示ASCII字符)。

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

第五和第六个codeUnit值(55357和56374)是DOG FACE字符的UTF-16表示。第一个值为U+D83D(十进制值为55357),第二个值为U+DC36(十进制值为 56374)。

Unicode标量表示

可以通过遍历String值的unicodeScalars属性来访问它的Unicode标量表示。其为UnicodeScalarView类型的属性,UnicodeScalarViewUnicodeScalar类型的值的集合。

每一个unicodeScalar拥有一个value属性,可以返回对应的21位数值,用UInt32来表示:

CharacterD
U+0044
o
U+006F
g
U+0067

U+203C
🐶
U+1F436
Unicode Scalar
Code Unit
681111038252128054
Position01234
for scalar in dogString.unicodeScalars {
    print("\(scalar.value) ", terminator: "")
}
print("")
---
output: 68 111 103 8252 128054 

前三个UnicodeScalar值(68、111、103)的value属性仍然代表字符Dog

第四个codeUnit值(8252)仍然是一个等于十六进制203C的十进制值。这个代表了DOUBLE EXCLAMATION MARK字符的Unicode标量U+203C

第五个UnicodeScalar值的value属性,128054,是一个十六进制1F436的十进制表示。其等同于DOG FACEUnicode标量U+1F436

作为查询它们的value属性的一种替代方法,每个UnicodeScalar值也可以用来构建一个新的String值,比如在字符串插值中使用:

for scalar in dogString.unicodeScalars {
    print("\(scalar) ")
}
---
output: D 
o 
g 
 
🐶 
Last updated on