基于Swift 版本 5.1
// 一个Unicode字符串值,它是字符的集合。
struct String
字符串是一系列字符组成的集合,如“Swift”。Swift中的字符串是Unicode正确且对语言环境不敏感的,旨在提高效率。字符串类型与Objective-C类NSString连接,并提供与处理字符串的C函数的互操作性。
您可以使用字符串文字或字符串内插来创建新字符串。字符串文字是用引号括起来的一系列字符。
Swift的String类型是一种值类型,String值再传递给方法或者函数的时候,会被复制过去,还有赋值给常量或者变量的时候也一样。每一次赋值和传递,现存的String值都会被复制一次,传递走的是拷贝而不是原本。Swift编译器优化了字符串使用的资源,实际上拷贝只会再确实需要的时候才进行。
字符串插值是字符串文字,用于评估所有包含的表达式并将结果转换为字符串形式。字符串插值为您提供了一种由多个片段构建字符串的简便方法。将每个表达式括在括号内的字符串内插中,并以反斜杠为前缀。
let name = "Rosa"
let personalizedGreeting = "Welcome, \(name)!"
// personalizedGreeting == "Welcome, Rosa!"
let price = 2
let number = 3
let cookiePrice = "\(number) cookies: $\(price * number)."
// cookiePrice == "3 cookies: $6."
使用串联运算符(+)合并字符串。
let longerGreeting = greeting + " We're glad you're here!"
// longerGreeting == "Welcome! We're glad you're here!"
多行字符串文字用三个双引号(""")括起来,每个定界符在其自己的行上。缩进从多行字符串文字的每一行中去除,以匹配结束定界符的缩进。
let banner = """
a\
b
c
d
"""
print(banner)
// log
/*
ab
c
d
*/
修改和比较字符串
字符串始终具有值语义。修改字符串的副本不会影响原始字符串。
var otherGreeting = greeting
otherGreeting += " Have a nice time!"
// otherGreeting == "Welcome! Have a nice time!"
print(greeting)
// Prints "Welcome!"
使用Unicode规范表示来使用等于运算符(==)或关系运算符(如<或>=)比较字符串是否相等。结果,字符串的不同表示形式比较相等。
let cafe1 = "Cafe\u{301}"
let cafe2 = "Café"
print(cafe1 == cafe2)
// Prints "true"
Unicode标量值"\u{301}"修改前面的字符以包含重音符号,因此"e\u{301}"具有与单个Unicode标量值相同的规范表示形式"é"。
基本的字符串操作对语言环境设置不敏感,可确保字符串比较和其他操作始终具有单个稳定的结果,从而允许将字符串用作Dictionary实例中的键或用于其他目的。
访问字符串元素
字符串是扩展的字素簇的集合,这些簇近似于人类可读的字符。许多单独的字符(例如“é”,“김”和“🇮🇳”)可以由多个Unicode标量值组成。这些标量值由Unicode的边界算法组合成扩展的字形簇,由Swift Character类型表示。字符串的每个元素都由一个Character实例表示。
例如,要检索较长字符串的第一个单词,可以搜索一个空格,然后从该字符串的前缀创建一个子字符串,直到该点为止:
let name = "Marie Curie"
let firstSpace = name.firstIndex(of: " ") ?? name.endIndex
let firstName = name[..<firstSpace]
// firstName == "Marie"
该常数是实例,表示一个字符串的子串,同时共享原始字符串的存储类型,一种类型。子字符串与字符串具有相同的接口。
print("\(name)'s first name has \(firstName.count) letters.")
// Prints "Marie Curie's first name has 5 letters."
访问字符串的Unicode表示形式
如果你需要在不同的Unicode编码,字符串的使用一个编码访问字符串的内容,或性质。每个属性都以一系列代码单元的形式提供对字符串视图的访问,每个代码单元均以不同的Unicode编码进行编码。unicodeScalars、utf16、utf8
为了演示每个字符串可用的不同视图,以下示例使用此String实例:
let cafe = "Cafe\u{301} du 🌍"
print(cafe)
// Prints "Café du 🌍"
该cafe字符串是九个字符显示的字符串时可见的集合。
print(cafe.count)
// Prints "9"
print(Array(cafe))
// Prints "["C", "a", "f", "é", " ", "d", "u", " ", "🌍"]"
Unicode标量视图
字符串的unicodeScalars属性是Unicode标量值的集合,21位代码是Unicode的基本单位。每个标量值都用Unicode表示。标量实例,相当于一个UTF-32代码单元。
print(cafe.unicodeScalars.count)
// Prints "10"
print(Array(cafe.unicodeScalars))
// Prints "["C", "a", "f", "e", "\u{0301}", " ", "d", "u", " ", "\u{0001F30D}"]"
print(cafe.unicodeScalars.map { $0.value })
// Prints "[67, 97, 102, 101, 769, 32, 100, 117, 32, 127757]"
unicodeScalars视图的元素包含cafe字符串中的每个Unicode标量值。特别是,因为cafe是使用“é”字符的分解形式声明的,所以unicodeScalars包含字母“e”(101)和重音字符“’”(769)的标量值。
UTF-16视图
字符串的utf16属性是UTF-16编码单元的集合,这是字符串的Unicode标量值的16位编码形式。每个代码单元存储为UInt16实例。
print(cafe.utf16.count)
// Prints "11"
print(Array(cafe.utf16))
// Prints "[67, 97, 102, 101, 769, 32, 100, 117, 32, 55356, 57101]"
utf16视图的元素是UTF-16编码时字符串的代码单元。这些元素与通过索引的NSString api访问的元素相匹配。
let nscafe = cafe as NSString
print(nscafe.length)
// Prints "11"
print(nscafe.character(at: 3))
// Prints "101"
UTF-8视图
字符串的utf8属性是UTF-8代码单元的集合,这是字符串的Unicode标量值的8位编码形式。每个代码单元存储为一个UInt8实例。
print(cafe.utf8.count)
// Prints "14"
print(Array(cafe.utf8))
// Prints "[67, 97, 102, 101, 204, 129, 32, 100, 117, 32, 240, 159, 140, 141]"
utf8视图的元素是UTF-8编码时字符串的代码单元。此表示与将字符串实例传递给C api时使用的表示相匹配。
let cLength = strlen(cafe)
print(cLength)
// Prints "14"
测量字符串长度
当您需要知道一个字符串的长度时,您必须首先考虑将该长度用于什么。您是在度量将显示在屏幕上的字符数,还是在度量特定编码中字符串所需的存储空间?当通过不同的视图测量时,一个字符串的长度可能会有很大的差异。
例如,像大写字母A这样的ASCII字符由它的四个视图中的每个元素表示。A的Unicode标量值是65,它足够小,可以同时容纳UTF-16和UTF-8中的单个代码单元。
let capitalA = "A"
print(capitalA.count)
// Prints "1"
print(capitalA.unicodeScalars.count)
// Prints "1"
print(capitalA.utf16.count)
// Prints "1"
print(capitalA.utf8.count)
// Prints "1"
另一方面,表情符号标志字符由一对Unicode标量值(如"\u{1F1F5}"和)构成"\u{1F1F7}"。这些标量值中的每一个都太大,以致无法放入单个UTF-16或UTF-8代码单元中。结果,字符串的每个视图"🇵🇷"报告的长度都不同。
let flag = "🇵🇷"
print(flag.count)
// Prints "1"
print(flag.unicodeScalars.count)
// Prints "2"
print(flag.utf16.count)
// Prints "4"
print(flag.utf8.count)
// Prints "8"
要检查字符串是否为空,请使用其isEmpty属性,而不是将其中一个视图的长度与0进行比较。与isEmpty不同,计算视图的count属性需要遍历字符串的元素。
访问字符串视图元素
要查找字符串的各个元素,请为任务使用适当的视图。例如,要检索较长字符串的第一个单词,您可以搜索该字符串的空格,然后从该字符串的前缀到该点创建一个新字符串。
let name = "Marie Curie"
let firstSpace = name.firstIndex(of: " ") ?? name.endIndex
let firstName = name[..<firstSpace]
print(firstName)
// Prints "Marie"
字符串及其视图共享索引,因此可以使用相同的firstSpace索引访问名称字符串的UTF-8视图。
print(Array(name.utf8[..<firstSpace]))
// Prints "[77, 97, 114, 105, 101]"
注意,一个视图的索引在另一个视图中可能没有确切的对应位置。例如,上面声明的标记字符串包含一个字符,但是当编码为UTF-8时,它由8个代码单元组成。下面的代码为标记中的第一个和第二个位置创建常量。use utf8视图。使用这些索引访问utf8视图将生成第一个和第二个代码UTF-8单元。
let firstCodeUnit = flag.startIndex
let secondCodeUnit = flag.utf8.index(after: firstCodeUnit)
// flag.utf8[firstCodeUnit] == 240
// flag.utf8[secondCodeUnit] == 159
但是,当用来访问标记字符串本身的元素时,secondCodeUnit索引并不对应于特定字符的位置。不是只访问特定的UTF-8代码单元,而是将该索引视为字符在索引编码偏移处的位置。在secondCodeUnit的情况下,该字符仍然是标记本身。
如果需要验证一个字符串视图中的索引是否与另一个视图中的确切位置相对应,请使用索引的samePosition(in:)方法或init(_:within:)初始化器。
if let exactIndex = secondCodeUnit.samePosition(in: flag) {
print(flag[exactIndex])
} else {
print("No exact match for this position.")
}
// Prints "No exact match for this position."
性能优化
尽管Swift中的字符串具有值语义,但字符串使用写时复制策略将数据存储在缓冲区中。然后,这个缓冲区可以由字符串的不同副本共享。一个字符串的数据只有在多个字符串实例使用同一个缓冲区时,才会在发生变化时惰性地复制。因此,任何顺序的第一个突变操作可能会花费O(n)时间和空间。
当字符串的连续存储被填满时,必须分配一个新的缓冲区,并且必须将数据移动到新的存储中。字符串缓冲区使用指数增长策略,当对多个附加操作求平均值时,该策略使附加到字符串成为常数时间操作。
字符串和NSString之间的桥接
任何字符串实例都可以使用类型转换操作符(as)桥接到NSString,而来自Objective-C的任何字符串实例都可以使用NSString实例作为其存储。因为NSString的任意子类都可以成为一个字符串实例,所以当一个字符串实例由NSString存储支持时,不能保证它的表示形式或效率。因为NSString是不可变的,它就像存储被一个拷贝共享一样。任何序列中的第一个操作都会导致元素被复制到唯一的、连续的存储中,这可能会消耗O(n)时间和空间,其中n是字符串的编码表示长度(或者更多,如果底层NSString具有不寻常的性能特征)。
有关本讨论中使用的Unicode术语的更多信息,请参见Unicode.org词汇表。特别是,本文讨论了扩展的grapheme集群、Unicode标量值和规范等价。
网友评论