字符串是一系列字符,如“hello, world”或“albatross”。Swift字符串由字符串类型表示。字符串的内容可以通过多种方式访问,包括作为字符值的集合。
Swift的字符串和字符类型提供了一种快速的、与unicode兼容的方式来处理代码中的文本。字符串的语法创建和操纵轻便,可读性强,与字符串的语法类似于c字符串连接非常简单,只需将两个字符串+操作符,和字符串可变性由常量或变量之间选择,就像任何其他价值迅速。您还可以使用字符串将常量、变量、文字和表达式插入到更长的字符串中,这个过程称为字符串插值。这使得创建用于显示、存储和打印的自定义字符串值变得很容易。
尽管语法简单,Swift的字符串类型是一种快速、现代的字符串实现。每个字符串都由独立于编码的Unicode字符组成,并支持以各种Unicode表示访问这些字符。
Swift的字符串类型由Foundation的NSString类桥接。Foundation还扩展了String以公开NSString定义的方法。这意味着,如果你导入Foundation,你可以在String上访问那些NSString方法而不用强制转换。
String Literals 字符串文字
您可以在代码中包含预定义的字符串值作为字符串文本。字符串文字是由一对双引号(")包围的字符序列。
let someString = "Some string literal value"
注意,Swift推断someString常量的字符串类型,因为它是用字符串文字值初始化的。
Multiline 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 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.
haha
"""
打印结果如下:
"\nThis string starts with a line break.\nIt also ends with a line break.\n haha\n"
如果在多行符结束标签"""前面有n个空格,那么每行字面量开头也必须至少要有m(m>=n)个空格,不然会报错,多余的空格(m-n)将包含在字符串值中。如下:
let linesWithIndentation = """
这将没有空格开头
这将有4个空格开头
"""
print(linesWithIndentation)
打印如下:
这将没有空格开头
这将有4个空格开头
字符串文字中的特殊字符
特殊字符:
- 转义的特殊字符\0(空字符)、\(反斜杠)、\t(水平制表符)、\n(换行)、\r(回车)、"(双引号)和'(单引号)
- 任意Unicode标量值,写为\u{n},其中n是1-8位十六进制数字(Unicode在下面的Unicode中讨论)
下面的代码显示了这些特殊字符的四个示例。wiseWords常量包含两个转义的双引号。$符号、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
输入结果如下:
"Imagination is more important than knowledge" - Einstein
$
♥
💖
Initializing an Empty String 初始化空字符串
为了构建一个更长的字符串,可以先创建一个空字符串,可以将空字符串赋值给一个变量,也可以使用String的初始化方法:
var emptyString = "" // empty string literal
var anotherEmptyString = String() // initializer syntax
// these two strings are both empty, and are equivalent to each other
判断字符串是否为空字符串
if emptyString.isEmpty {
print("Nothing to see here")
}
// Prints "Nothing to see here"
String Mutability 字符串可变性
指定一个特定的字符串是可以修改的(modified 或者 mutated)是将其赋值给一个变量(var 声明),而不是赋值给一个常量(let 声明)
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
Strings Are Value Types 字符串是值类型
Swift的String类型是值类型。如果您创建一个新的字符串值,当将该字符串值传递给函数或方法时,或者当将该字符串值分配给常量或变量时,将复制该字符串值。在每种情况下,都会创建现有字符串值的新副本,并传递或分配新副本,而不是原始版本。
Swift 的默认复制字符串行为确保了函数或方法传递了一个字符串值,确定您拥有那个明确的字符串值,不管它来自哪里。您可以确信传递给您的字符串不会被修改,除非您亲自修改它。
在幕后,Swift的编译器优化了字符串的使用,因此只有在绝对必要时才会进行实际的复制。这意味着当使用字符串作为值类型时,您总是可以获得很好的性能。
Working with Characters 使用字符
你可以通过for-in循环遍历字符串来访问字符串的各个字符值:
for character in "Dog!🐶" {
print(character)
}
// D
// o
// g
// !
// 🐶
或者,您可以通过提供字符类型注解,从单字符字符串文本创建一个独立的字符常量或变量:
let exclamationMark: Character = "!"
字符串值可以通过将一组字符值作为参数传递给它的初始化器来构造:
let catCharacters: [Character] = ["C", "a", "t", "!", "🐱"]
let catString = String(catCharacters)
print(catString)
打印如下:
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"
也可以使用 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
String Interpolation 字符串插值
字符串插值是一种通过将常量、变量、文字和表达式的值包含在字符串文字中来构造新字符串值的方法。您可以在单行和多行字符串文字中使用字符串插值。使用 (name):
let multiplier = 3
let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
// message is "3 times 2.5 is 7.5"
Unicode 字符编码标准
Unicode是一种国际标准,用于在不同的书写系统中编码、表示和处理文本。它使您能够以标准化的形式表示任何语言中的几乎任何字符,并将这些字符读写到外部源(如文本文件或web页面)或从外部源(如文本文件或web页面)。Swift的字符串和字符类型完全符合unicode,如本节所述。
Unicode Scalar Values 字符编码标量值
在后台,Swift的原生字符串类型是由Unicode标量值构建的。Unicode标量值是一个独特的21-bit数字字符或修饰符,如拉丁字母小写字母a 表示为 U+0061,或 U+1F425 表示为小鸡(“🐥”)。
注意,并非所有21位Unicode标量值都分配给一个字符—有些标量保留给将来的分配或UTF-16编码中使用。分配给字符的标量值通常也有一个名称,如上面示例中的拉丁字母a和小鸡宝宝。
Extended Grapheme Clusters 扩展字符集群
Swift字符类型的每个实例都表示一个扩展的图形簇。扩展的grapheme集群是由一个或多个Unicode标量组成的序列,这些标量(组合在一起)产生一个人类可读的字符。
这是一个例子。字符é表示为单个Unicode标量é(带锐号的拉丁小字母e,或U+00E9)。然而,也可以使用字母e和'组合来表示:
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 한
还有许多如:
let enclosedEAcute: Character = "\u{E9}\u{20DD}"
// enclosedEAcute is é⃝
let regionalIndicatorForUS: Character = "\u{1F1FA}\u{1F1F8}"
// regionalIndicatorForUS is 🇺🇸
Counting Characters 字符个数
若要检索字符串中字符值的计数,请使用字符串的count属性:
let unusualMenagerie = "Koala 🐨, Snail 🐌, Penguin 🐧, Dromedary 🐪"
print("unusualMenagerie has \(unusualMenagerie.count) characters")
// Prints "unusualMenagerie has 40 characters"
注意,Swift对字符值使用扩展字符集群意味着字符串连接和修改并不总是影响字符串的字符数。
如:
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中的每个字符在字符串表示中占用的内存并不相同。因此,如果不遍历字符串以确定其扩展的grapheme集群边界,就无法计算字符串中的字符数。如果您正在处理特别长的字符串值,请注意count属性必须遍历整个字符串中的Unicode标量,以便确定该字符串的字符。
count属性返回的字符数并不总是与包含相同字符的NSString的length属性相同。NSString的长度基于字符串的UTF-16表示中16位代码单元的数量,而不是字符串中Unicode扩展的图形簇的数量。
Accessing and Modifying a String 访问和修改字符串
您可以通过字符串的方法和属性或使用下标语法访问和修改字符串。
String Indices 字符串索引
每个字符串值都有一个关联的索引类型String。索引,它对应于字符串中每个字符的位置。
如上所述,不同的字符可能需要不同数量的内存来存储,因此,为了确定哪个字符位于特定位置,必须从字符串的开始或结束遍历每个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
使用indexes属性访问字符串中单个字符的所有索引。
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等集合类型。
Inserting and Removing 插入和移除
若要在指定索引处将单个字符插入字符串,请使用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等集合类型。
Substrings 子字符串
当您从字符串获取子字符串时(例如,使用下标或类似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是一个字符串,这意味着它有一个存储组成字符串的字符的内存区域。因为begin是问候语的子字符串,所以它重用问候语使用的内存。相反,newString是字符串——当它从子字符串创建时,它有自己的存储。下图显示了这些关系:
字符串和子字符串都符合StringProtocol协议,这意味着字符串操作函数通常可以方便地接受StringProtocol值。您可以使用字符串或子字符串值调用这些函数。
Comparing Strings 比较字符串
Swift提供了三种比较文本值的方法:字符串和字符相等、前缀相等和后缀相等。string and character equality, prefix equality, and suffix equality.
String and Character Equality 字符串和字符相等性
字符串和字符的比较可以使用 == 和 != 运算符进行比较
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标量在幕后组成的。
这两个扩展的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"
相反,拉丁大写字母(U + 0041,或“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中的字符串和字符比较对语言环境不敏感。
Prefix and Suffix Equality 前缀和后缀相等
要检查字符串是否具有特定的字符串前缀或后缀,请调用字符串的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(_:)方法计算发生在Capulet 's mansion和Friar 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"
Unicode Representations of Strings 字符串的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属性访问)
下面的字符串使用不同的表达方式组成
let dogString = "Dog‼🐶"
UTF-8 Representation
通过遍历字符串的utf8属性,可以访问字符串的UTF-8表示形式。此属性的类型为String。UTF8View,它是一个无符号8位(UInt8)值的集合,每个字节在字符串的UTF-8表示:
for codeUnit in dogString.utf8 {
print("\(codeUnit) ", terminator: "")
}
print("")
// Prints "68 111 103 226 128 188 240 159 144 182 "
在上面的示例中,前三个十进制码元值(68,111,103)表示字符D、o和g,其UTF-8表示与ASCII表示相同。接下来的三个十进制代码单元值(226、128、188)是双感叹号字符的三字节UTF-8表示形式。最后四个codeUnit值(240、159、144、182)是DOG FACE字符的四字节UTF-8表示。
UTF-16 Representation
通过遍历字符串的utf16属性,可以访问字符串的UTF-16表示形式。此属性的类型为String。UTF16View,它是一个无符号16位(UInt16)值的集合,在字符串的UTF-16表示中,每个16位代码单元对应一个值:
同样,前三个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 Scalar Representation Unicode标量表示
通过遍历字符串值的unicodeScalars属性,可以访问字符串值的Unicode标量表示形式。此属性属于UnicodeScalarView类型,它是UnicodeScalar类型值的集合。
每个UnicodeScalar都有一个value属性,返回标量的21位值,用UInt32值表示:
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的十进制等效值,它表示双感叹号字符的Unicode标量U+203C。
第五个也是最后一个UnicodeScalar的值属性128054是十六进制值1F436的十进制等价物,它表示Unicode标量U+1F436用于DOG FACE字符。
除了查询它们的值属性,每个UnicodeScalar值还可以用来构造一个新的字符串值,比如字符串插值:
for scalar in dogString.unicodeScalars {
print("\(scalar) ")
}
// D
// o
// g
// ‼
// 🐶