【Swift 3.1】03 - 字符串和字符 (Strings and Characters)

字符串和字符 (Strings and Characters)

自从苹果2014年发布Swift,到现在已经两年多了,而Swift也来到了3.1版本。去年利用工作之余,共花了两个多月的时间把官方的Swift编程指南看完。现在整理一下笔记,回顾一下以前的知识。有需要的同学可以去看官方文档>>


字符串字面值 (String Literals)

使用字符串字面值来初始化一个常量或者变量:

let someString = "Some string literal value"

Swift根据字面值来推断出someStringString类型。

初始化一个空字符串

为了创建更长的字符串,我们通常要先初始化一个空字符串,我们可以使用下面两种方法:

var emptyString = ""                // 空字符串字面值
var anotherEmptyString = String()   // 使用默认构造函数
// 这两个字符串都是空的,并且是相等的

通过检查字符串的布尔类型属性isEmpty来判断一个字符串是否为空:

if emptyString.isEmpty {
    print("这里什么都没有")
}
// Prints "这里什么都没有"

字符串的可变性 (String Mutability)

使用let声明一个不可变的字符串,用var声明一个可变的字符串:

var variableString = "Horse"
variableString += "and carriage"
// variableString 现在是 "Horse and carriage"

let constatString = "Highlander"
constantString += "and another Highlander"
// 会产生一个编译错误,因为constantString是一个常量,不能被改变

在OC中,需要使用NSStringNSMutableString来分别定义不可变和可变字符串。

字符串是值类型 (Strings Are Value Types)

Swift的String类型是值类型。如果我们创建一个新的字符串,那么把这个字符串以复制的形式传给一个函数或方法,或者把它赋值给另外一个常量或常量。

在Swift的底层实现,Swift的编译器会优化字符串的使用,在有必要的时候才会进行真正的复制,这就意味着把字符串作为值类型在使用的时候,都有非常好的性能。

与字符交互 (Working with Characters)

使用for-in循环遍历字符串的characters属性来访问单个字符:

for character in "Dog!🐶".characters {
    print(character)
}
// D
// o
// g
// !
// 🐶

字符串可以通过传递一个字符数组给String的构造函数来初始化:

let catCharacters: [Character] = ["C", "a", "t", "!", "🐱"]
let catString = String(catCharacters)
print(catString)
// Prints "Cast!🐱"

连接字符串和字符 (Concatenating Strings and Characters)

使用加号运算符(+)来拼接两个字符串:

let string1 = "hello"
let string2 = "there"
let welcome = string1 + string2
// welcome 等于 "hello there"

使用加法赋值运算符来拼接一个已经存在字符串:

var instruction = "look over"
instruction += string2
// instruction 等于 "look over there"

使用String类型的append()方法来拼接:

let exclamationMark: Character = "!"
welcome.append(exclamationMark)
// welcome 等于 "hello there!"

注意:不能添加一个String或者Character到一个已经存在的Character变量,因为Character类型的值只能包含一个字符。

字符串插值 (String Interpolation)

字符串插值可以把常量、变量、字面值和其他表达式混合在一起,拼接成一个新的字符串。

let multiplier = 3
let message = "\(multiplier) 乘以 2.5 等于 \(Double(multiplier) * 2.5)"
// message is "3 乘以 2.5 等于 7.5"

Unicode

Unicode是在不同系统中编码、展示和处理文本的国际标准。可以让我们以国际化的形式在不同的语言中展示几乎任何字符,从外部文件(例如text文件或网页)读取那些字符或者把那些字符写到外部文件。Swift的StringCharacter类型是完全兼容Unicode的。

Unicode标量 (Unicode Scalars)

在底层,Swift的原生String类型是从Unicode标量值建立的。一个Unicode标量是一个字符或者修饰符唯一的21-bit数字,例如U+0061LATIN SMALL LETTER A("a"),U+1F425FRONT-FACING BABY CHICK ("🐥")。

注意:一个Unicode标量是在U+0000U+D7FF(包含首尾)这个范围中的一个代码点(code point),或者是U+E000U+10FFFF(包含首尾)这个范围的一个代码点。Unicode标量不包含Unicode代理对(surrogate pair)代码点,代理对的代码点范围是U+D800U+DFFF(包含首尾)。

并不是所有的21-bitUnicode标量都赋值给了一个字符,有一些标量是保留起来给未来使用的。被赋值给字符串的Unicode标量都有一个名字,例如上面例子中的LATIN SMALL LETTER A和FRONT-FACING BABY CHICK`。

字符串字面值中的特殊字符 (Special Characters in String Literals)

字符串字面值可以包含以下字符:

  • 转义字符:\0(空字符)、\\(反斜杠)、\t(水平制表符)、\n(换行)、\r(回车)、\"(双引号)、\'(单引号)
  • 一个任意的Unicode标量,写成这个样式\u{n},n是1~8的十六进制数字并且等于一个有效的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
扩展字形集群 (Extended Grampheme Clusters)

每个SwiftCharacter类型实例代表着一个扩展字形集群。一个扩展字形集群是一系列的一个或多个Unicode标量,并形成一个人类能够读懂的字符。

例如,字母é代表这个Unicode标量é(LATIN SMALL LETTER E WITH ACUTE, 或者 U+00E9)。然而这个字母还可以代表一对标量:一个标准的字母e(LATIN SMALL LETTER,或者U+0065),接着是COMBINING ACUTE ACCENT标量(U+0301)。COMBINING ACUTE ACCENT标量被图形化的应用于在它前面的标量,Unicode文本渲染系统把e渲染成é

let eAcute: Character = "\u{E9}"                         // é
let combinedEAcute: Character = "\u{65}\u{301}"          // e followed by ́
// eAcute is é, combinedEAcute is é

扩展字形集群是一个能把很多复杂脚本字符显示为一个Character字符的一种灵活的方式。例如,韩文字母的韩文音节可以用合成或者分解序列来表示:

let precomposed: Character = "\u{D55C}"                  // 한
let decomposed: Character = "\u{1112}\u{1161}\u{11AB}"   // ᄒ, ᅡ, ᆫ
// precomposed is 한, decomposed is 한

扩展字形集群还支持封闭标志(例如COMBINING ENCLOSING CIRCLE,或者U+20DD):

let enclosedEAcute: Character = "\u{E9}\u{20DD}"
// enclosedEAcute is é⃝

代表区域标志符号的Unicode标量还可以结成对,形成一个Character字符。例如这个组合:REGIONAL INDICATOR SYMBOL LETTER U (U+1F1FA)REGIONAL INDICATOR SYMBOL LETTER S (U+1F1F8)

let regionalIndicatorForUS: Character = "\u{1F1FA}\u{1F1F8}"
// regionalIndicatorForUS is 🇺🇸
Counting Characters

字符计数 (Counting Characters)

使用字符串的characterscount属性来后去字符的个数:

let unusualMenagerie = "Koala 🐨, Snail 🐌, Penguin 🐧, Dromedary 🐪"
print("unusualMenagerie has \(unusualMenagerie.characters.count) characters")
// Prints "unusualMenagerie has 40 characters"

注意:在Swift中,使用扩展字形集群形成的字符,在拼接和修改字符串时不一定会影响字符的个数。例如:初始化一个字符串cafe,接着在后面追加COMBINING ACUTE ACCENT (U+0301),最终得到的字符串还是只有4个字符,第四个字符是é,不是e:

var word = "cafe"
print("the number of characters in \(word) is \(word.characters.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.characters.count)")
// Prints "the number of characters in café is 4"

注意:扩展字形集群可以有一个或多个Unicode标尺组成,这意味着不同的字符和同一个字符的不同展示方式需要不同大小的内存来存储。所以,Swift中的字符不会在字符串的展示中占用相同的内存量。那么,如果不遍历一个字符串,就无法知道这个字符串的字符数,从而无法确定字符的扩展字形集群边界。如果我们要使用特别长的字符串,必须清楚地知道字符串的characters属性必须遍历整个字符串的全部Unicode标量来决定字符串的所有字符。

在拥有相同字符得到情况下,Stringcharacters属性返回的字符数不总是等于NSStringlength属性。NSString的长度是基于在字符串的UTF-16显示之内的16-bit代码单元的数字,而不是基于在字符串之内的Unicode扩展字形集群的数字。

访问和修改字符串 (Accessing and Modifying a String)

字符串索引 (String Indices)

每个String都有一个关联的索引类型,String.Index,对应着字符串里的没一个字符的位置。

就像上面说到的,不同的字符需要占用不用的内存量,所以为了得到字符的位置,我们需要遍历整个字符串的全部Unicode标量。所以Swift的字符串不能按整数值进行索引。

使用startIndex来访问字符串的第一个字符的位置,endIndex是字符串最后一个字符的下一个位置。所以,endIndex不是一个有效的字符串索引。如果一个字符串是空的,那么startIndexendIndex是相等的。

使用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)
// a

下面两行代码将会报错,已经超出了字符串的范围:

greeting[greeting.endIndex] // Error
greeting.index(after:greeting.endIndex) Error

使用charactersindices属性来访问字符串中每一个字符的索引:

for index in greeting.characters.indices {
    print("\(greeting[index]) ", terminator: "")
}
// Prints "G u t e n   T a g ! "

注意:我们可以在遵循了Collection协议的任何类型中使用startIndex/endIndex属性和index(before:)/index(after:)/index(_:offsetBy:)方法,包括:StringArrayDictionarySet

插入和移除 (Inserting and Removing)

在特定的位置插入一个字符,使用insert(_:at:);在特定的位置插入另外一个字符串内容,使用insert(contentsOf:at:)

var welcome = "hello"
welcome.insert("!", at: welcome.endIndex)
// welcome 现在是 hello!

welcome.insert(contentsOf: " there".characters, at: welcome.index(before: welcome.endIndex))
// welcome 现在是 hello there!

在特定的位置删除一个字符,使用remove(at:),删除一个范围内的字符串,使用removeSubrange(_:):

welcome.remove(at: welcome.index(before:welcome.endIndex))
// welcome 现在是 hello there

let range = welcome.index(welcome.endIndex, offsetBy: -6)..<welcome.endIndex
welcome.removeSubrange(range)
// welcome 现在是 hello

注意:我们可以在遵循了RangeReplaceableCollection协议的任何类型中使用insert(_:at:)insert(contentsOf:at:)remove(at:)removeSubrange(_:)方法,包括:StringArrayDictionarySet

字符串的比较 (Comparing Strings)

Swift提供了三种方式来进行文本比较:1)字符串和字符相等;2)前缀相等;3)后缀相等。

字符串和字符相等 (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标量合成的。例如,LATIN SMALL LETTER E WITH ACUTE (U+00E9)LATIN SMALL LETTER E (U+0065)COMBINING ACUTE ACCENT (U+0301)的组合是相等的:

// "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"

相反,英语的LATIN CAPITAL LETTER A(U+0041, 或者 "A"),不等于俄语的CYRILLIC CAPITAL LETTER 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(_:)来判断一个字符串是否有特定的前缀或者后缀。

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"
]

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"

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表示的字符串 (Unicode Representations of Strings)

当一个Unicode字符串被写入文本文件或者其他内存时,字符串的Unicode标量会被以一种Unicode定义的编码形式编码。每一种形式将字符串编码到一个小方块,也就是代码单元。这些形式包括:1)UTF-8编码形式(把一个字符串编码为8-bit代码单元);2)UTF-16编码形式(把一个字符串编码为16-bit代码单元);3)UTF-32编码形式(把一个字符串编码为32-bit代码单元)。

访问其中一种符合Unicode标准表示的字符串:

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

下面的例子将以不同的展示方式来展示这两个字符或字符串:1)一个由Dog!!(DOUBLE EXCLAMATION MARK,或者Unicode标量U+203C)组成的字符串;2)字符:🐶 (DOG FACE,或者Unicode标量U+1F436)。

UTF-8 展示 (UTF-8 Representation)

遍历utf8属性来访问字符串的UTF-8展示,这个属性是String.UTF8View类型,是无符号8-bit (UInt8)值的集合。

UTF-8 representation
let codeUnit in dogString.utf8 {
    print("\(codeUnit) ", terminator: "")
}
print("")
// 68 111 103 226 128 188 240 159 144 182
UTF-16 展示 (UTF-16 Representation)

遍历utf16属性来访问字符串的UTF-16展示,这个属性是String.UTF16View类型,是无符号8-bit (UInt8)值的集合。

UTF-16 Representation
for codeUnit in dogString.utf16 {
    print("\(codeUnit) ", terminator: "")
}
print("")
// Prints "68 111 103 8252 55357 56374 "
Unicode标量展示 (Unicode Scalar Representation)

遍历unicodeScalars属性来访问字符串的Unicode标量展示。这个属性是UnicodeScalarView类型,是一个UnicodeScalar类型的值的集合。

每个UnicodeScalar有一个value属性,这个属性返回标量的21-bit值。

Unicode Scalar Representation
for scalar in dogString.unicodeScalars {
    print("\(scalar.value) ", terminator: "")
}
print("")
// Prints "68 111 103 8252 128054 "

作为查询value属性的另外一种方法,每个UnicodeScalar的值可以用来构建一个新的字符串,如字符串的插值:

for scalar in dogString.unicodeScalars {
print("\(scalar)")
}
// D
// o
// g
// ‼
// 🐶

第三部分完。下个部分:【Swift 3.1】04 - 集合类型 (Collection Types)


如果有错误的地方,欢迎指正!谢谢!

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

推荐阅读更多精彩内容