Swift 001-字符串 官方文档 概述

基于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标量值和规范等价。

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

推荐阅读更多精彩内容