Swift 字符串是由 String
类型来表示。 String
的内容可以用多种方式读取,包括作为一个 Character
值的集合。
注意
Swift 的字符串类型与 Foundation 的 NSString
类型进行了无缝桥接。 Foundation 也可以对 String
进行扩展,暴露在 NSString
中定义的方法。 这就意味着,你可以不用进行类型转换,就能在 String
中调用 NSString
的这些方法。
更多关于在 Foundation 和 Cocoa 中使用 String
的信息,查看 Bridging Between String and NSString 。
初始化一个空字符串
创建一个空 String
有两种方式,给一个变量赋值一个空字符串或者使用下面的语法初始化一个 String
实例对象:1
2
3var emptyString = "" // 空字符串
var anotherEmptyString = String() // 初始化语法
//这是两个空字符串,他们等价
可以通过检查 String
的布尔类型的属性 isEmpty
来判断该字符串的值是否为空:1
2
3
4if emptyString.isEmpty {
print("Nothing to see here")
}
// 打印 "Nothing to see here"
字符串是值类型
Swift 中的 String
类型是一种 值类型
。如果你创建了一个新的 String
值, String
值在传递给方法或者函数时会被 拷贝,在给常量或者变量赋值的时候也是一样。在任何情况下,都会对现存的 String
值创建新拷贝,并对新拷贝进行传递或赋值操作。值类型在 结构体和枚举是值类型 中有详细描述。
Swift 默认 String
拷贝的行为是为了保证在函数或方法中传递的是 String
值,不管该值是从哪里来,你都绝对拥有这个 String 值。你可以确定你传递的这个字符串不会被修改,除非你自己去修改它。
另一方面,Swift 编译器优化了字符串的使用,实际拷贝只会在需要的时候才进行。这意味着你把字符串当做值类型的同时也能够得到很棒的性能。
使用字符
你可以使用 for-in
循环来遍历 String
中每个的 Character
的值:1
2
3for character in "Dog!🐶" {
print(character)
}
你可以使用 Character
类型声明,并赋值一个单字符值创建一个独立的字符常量或变量:1
let exclamationMark: Character = "!"
String
的值可以使用一个 Character
值类型的数组作为变量来进行初始化:1
2
3
4let catCharacters: [Character] = ["C", "a", "t", "!", "🐱"]
let catString = String(catCharacters)
print(catString)
// 输出 "Cat!🐱"
字符串和字符的拼接
可以使用加号( +
)将 String
的值加(或 拼接 )在一起创造出一个新的值
你可以使用加等于赋值符号( +=
)将一个 String
的值追加到一个已经存在的 String
变量中
你可以使用 String
的 append()
方法将一个 Character
的值追加到一个 String
变量中
注意
你不能将字符串 String
或字符 Character
拼接到 Character
变量中,因为 Character 的值只能包含单个字符。
字符计数
在一个字符串中使用 count 属性去计算 Character 类型值个数
注意,Swift 对 Character 类型值使用了拓展字母集,意味着字符串的拼接和修改不一定会持续影响字符串字符个数。
例如,你初始化一个拥有四个字符的字符串 cafe
,然后再追加一个 COMBINING ACUTE ACCENT
(U+0301
) 字符在末尾 ,最终形成的字符串还是拥有四个字符,并且最后一个字符是 é
,而不是 e
:1
2
3
4
5
6
7
8var word = "cafe"
print("the number of characters in \(word) is \(word.count)")
// 打印 "the number of characters in cafe is 4"
word += "\u{301}" // 拼接重音符,U+0301
print("the number of characters in \(word) is \(word.count)")
// 打印 "the number of characters in café is 4"
注意
拓展字母集可以由多个不同的 Unicode 标量组成,这就意味着相同字符和相同字符的不同表示需要占据不同的内存空间去存储,因此,在字符串的各种表示中 Swift 字符占据的内存并不一样。造成的结果就是,字符串的字符数量并不能通过遍历该字符串去计算,并用于确定该字符串的拓展字符集边界。如果你正在处理特别长的字符串,要意识到为了确定该字符串的字符个数, count
属性必须要遍历完整个字符串中的全部 Unicode 标量。
count
属性返回的字符个数不会一直都与包含相同字符的 NSString
的 length
属性返回的字符个数相同。 NSString
的长度是基于 UTF-16 表示的字符串所占据的 16 位代码单元的个数决定,而不是字符串中的拓展字母集个数决定。
访问和修改字符串
你可以通过字符串的方法和属性来访问和修改它,或者通过下标语法。
字符串索引
每个 String
值都有一个关联的 索引类型, String.Index
,对应着字符串中每个 Character
的位置。
正如上面提到的,不同的字符可能需要不同大小的内存存储,所以为了确定每个 Character
的具体位置,你必须从 String
的开头遍历每个 Unicode
标量到结束。因此,Swift 字符串不能使用整型值索引。
使用 startIndex
属性可以访问 String
的第一个 Character
的位置。使用 endIndex
属性可以访问 String
的最后一个 Character
的位置。因此, endIndex
属性并不是字符串下标的有效参数。如果 String
是空串, startIndex
和 endIndex
就是相等的。
你可以通过使用 String
的 index(before:)
和 index(after:)
方法,访问给定索引的前一个和后一个索引。要访问离给定索引偏移较多的索引,你可以使用 index(_:offsetBy:)
方法,避免多次调用 index(before:)
和 index(after:)
方法。
使用 indices
属性会创建一个包含全部索引的范围,用来在一个字符串中访问单个字符。1
2
3
4for index in greeting.indices {
print("\(greeting[index]) ", terminator: "")
}
// 打印 "G u t e n T a g ! "
注意
你可以在任意一个遵循 Collection
协议的类型里面,使用 startIndex
和 endIndex
属性或者 index(before:)
, index(after:)
和 index(_:offsetBy:)
方法。如上文所示是使用在 String
中,你也可以使用在 Array
、Dictionary
和 Set
中。
插入和删除
在一个字符串指定位置插入单个字符,使用 insert(:at:)
方法,而要插入另一个字符串的内容时,使用 insert(contentsOf:at:)
方法。
删除一个字符串指定位置的单个字符,用 remove(at:)
方法,而要删除指定范围的子字符串时,用 removeSubrange(_:)
注意
你可以在任何遵循 RangeReplaceableCollection 协议的类型上使用 insert(_:at:)
, insert(contentsOf:at:)
,remove(at:)
,和 removeSubrange(_:)
方法。除了这里说到的 String
,还包括 Array
,Dictionary
,和 Set
等集合类型。
子字符串
当你从字符串中获取一个子字符串 —— 例如使用下标或者 prefix(_:)
之类的方法 —— 就可以得到一个 Substring
的 实例 ,而非另外一个 String
。Swift 里的 Substring
的绝大部分函数都跟 String
一样,意味着你可以使用同样的方式去操作 Substring
和 String
。然而,跟 String
不同的是,你只有在短时间内需要操作字符串时,才会使用 Substring
。当你需要长时间保存结果时,就把 Substring
转化为 String
的实例:1
2
3
4
5
6
7let greeting = "Hello, world!"
let index = greeting.firstIndex(of: ",") ?? greeting.endIndex
let beginning = greeting[..<index]
// beginning 的值是 "Hello"
// 把结果转化为 String 以便长期存储。
let newString = String(beginning)
就像 String
,每一个 Substring
都会在内存里保存字符集。而 String
和 SubString
的区别在于性能优化上,Substring
可以重用原 String
的内存空间,或者另一个 Substring
的内存空间(String
也有同样的优化,但如果两个 String
共享内存的话,它们就会相等)。这一优化意味着你在修改 String
和 Substring
之前都不需要消耗性能在内存复制。就像前面说的那样,Substring
不适合长期存储 —— 因为它重用了原 String
的内存空间,原 String
的内存空间必须保留直到它的 Substring
不再被使用为止。
上面的例子, greeting
是一个 String
,意味着它在内存里有一片空间保存字符集。而由于 beginning
是 greeting
的 Substring
,它重用了 greeting
的内存空间。相反,newString
是一个 String
—— 它是使用 Substring
创建的,拥有一片自己的内存空间。下面的图展示了他们之间的关系:
注意
String
和 Substring
都遵循 StringProtocol
协议, 这意味着操作字符串的函数使用 StringProtocol
会更加方便。你可以传入 String
或 Substring
去调用函数。
比较字符串
Swift 提供了三种方式来比较文本值: 字符串和字符相等、前缀相等、后缀相等。
字符串和字符相等
如果他们的扩展字形集是 统一码等价,则这两个 String
值 (或者两个 Character
值) 被认为是等同的。如果它们具有相同的语言含义和外观,即使它们是由不同语义的 Unicode 标量组成,扩展字形集也是等同的。
例如,LATIN SMALL LETTER E WITH ACUTE
(U+00E9
) 在规范上等同于 LATIN SMALL LETTER E
(U+0065
) 加上 COMBINING ACUTE ACCENT
(U+0301
)。这两个扩展字形簇都是表示字符 é
的有效方法,因此它们被认为是规范等价的
相反,英文中的 LATIN CAPITAL LETTER A
(U+0041
,或 「A」
),和俄文中的 CYRILLIC CAPITAL LETTER A
(U+0410
, 或 「А」
) 不相等。这两个字符在视觉上相似,但具有不同的语言含义
注意
Swift 中的字符串和字符比较不是区域敏感的。
前缀和后缀比较
可以使用字符串的 hasPrefix(_:)
和 hasSuffix(_:)
方法来检查一个字符串是否有特定的前缀、后缀。这两个方法接收一个 String
类型的参数返回一个布尔值。
注意
hasPrefix(_:)
和 hasSuffix(_:)
方法都是在每个字符串的扩展字符集中逐个字符进行比较, 如本文所述 字符串和字符的比较。
字符串的 Unicode
表示形式
当一个 Unicode
字符串被写入文本文件或者一些其他存储时,字符串中的 Unicode
标量会用 Unicode
定义的几种 编码格式
编码。每一个字符串中的小块编码都叫做 代码单元
。这些包括 UTF-8
编码格式 (编码字符串为 8 位的代码单元),UTF-16
编码格式 (编码字符串为16位的代码单元) , 以及 UTF-32
编码格式 (编码字符串32位的代码单元) 。
Swift 提供几种不同的方式来访问字符串的 Unicode
表现形式。 你可以使用 for - in
对字符串进行便利, 进而访问其中单个 Character
字符值作为 Unicode
扩展的字符群集。 这个过程描述在 使用字符。
另外,也可以通过其他三种 Unicode
兼容的方式访问字符串的值:
- UTF-8 代码单元集合(利用字符串的
utf8
属性进行访问) - UTF-16 代码单元集合 (利用字符串的
utf16
属性进行访问) - 21 位的 Unicode 标量值集合,也就是字符串的 UTF-32 编码格式(利用字符串的
unicodeScalars
属性进行访问)
下面有D
,o
,g
,!!
(DOUBLE EXCLAMATION MARK
,或Unicode 标量U+203C
)和🐶
(DOG FACE
,Unicode 标量为U+1F436
)组成的字符串中的每一个字符代表着一种不同的表示:1
let dogString = "Dog‼🐶"
UTF-8 表示形式
你可以通过遍历 String
的 utf8
属性来访问他的 UTF-8 表示。这个属性是 string.UTF8View
类型的,UTF8View
是无符号 8 位( UInt8
)值得集合,每一个字节都对应一个字符串的 UTF-8 的表现形式:
1 | for codeUnit in dogString.utf8 { |
上面的例子中,前三个 10 进制 codeUnit
值(68
,111
,103
)代表了字符 D
o
和 g
,他们的 UTF-8 表示和 ASCII 表示相同。接下来的三个 10 进制 codeUnit
值(226
,128
, 188
)是 DOUBLE EXCLAMATION MARK
的 3 字节 UTF-8 表示形式。 最后四个 codeUnit
值 (240
, 159
, 144
, 182
) 是 DOG FACE
的 4 字节 UTF-8 表示形式。
UTF-16 表示形式
你可以通过遍历 String
的 utf16
属性来访问它的 UTF-16
表示形式。它是 String.UTF16View
类型的属性, 它是一个无符号 16 位 (UInt16
) 值的集合,每一个 UInt16
都是一个字符的 UTF-16 表示形式:
1 | for codeUnit in dogString.utf16 { |
同样,前三个 codeUnit
值 (68
, 111
, 103
) 代表了字符 D
, o
, 和 g
, 他们的 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
类型的属性, UnicodeScalarView
是 UnicodeScalar
类型的值得集合。
每一个 UnicodeScalar
都有一个 value
属性,可以返回对应的 21 位数值,用 UInt32
值来表示:
1 | for scalar in dogString.unicodeScalars { |
前三个 UnicodeScalar
值 (68
, 111
, 103
) 的 Value
属性依旧代表着字符 D
, o
, and g
。
第四个 codeUnit
值 (8252
) 依旧是一个等于十六进制 203C
的十进制值, 这代表了 DOUBLE EXCLAMATION MARK
字符的 Unicode 标量 U+203C
。
第五个 UnicodeScalar
值的 Value
属性, 128054
, 是一个十六进制 1F436
的十进制表现, 它代表 DOG FACE
字符的 Unicode 标量 U+1F436
。
作为查询他们的 value
属性的一种替代方法, 每一个 UnicodeScalar
值也可以用来构建一个新的 String
值, 比如在字符串插值中使用:1
2
3
4
5
6
7
8for scalar in dogString.unicodeScalars {
print("\(scalar) ")
}
// D
// o
// g
// ‼
// 🐶