Swift开发实战
上QQ阅读APP看书,第一时间看更新

第5章 字符串和字符

多个字符可以构成字符串,字符串(String)是由0个或多个字符组成的有限序列,是编程语言中表示文本的数据类型。通常以串的整体作为操作对象,例如,在串中查找某个子串、求取一个子串、在串的某个位置上插入一个子串以及删除一个子串等。两个字符串相等的充要条件是:长度相等,并且各个对应位置上的字符都相等。假设p、q是两个串,求q在p中首次出现的位置的运算叫做模式匹配。串的两种最基本的存储方式是顺序存储方式和链接存储方式。在本章的内容中,将详细讲解Swift字符和字符串的基本知识。

5.1 字符和字符串基础

在Swift语言中,字符类型是String,例如“hello, world”、“海贼王”等有序的Character(字符)类型的值的集合,通过String类型来表示。Swift中的String和Character类型提供了一个快速的、兼容 Unicode 的方式来处理代码中的文本信息。在Swift语言中,创建和操作字符串的语法与在C语言中字符串操作相似。字符串连接操作只需通过“+”号将两个字符串相连即可。

在Swift语言中,每一个字符串都是由独立编码的Unicode字符组成,并提供了以不同Unicode表示(representations)来访问这些字符的支持。Swift 可以在常量、变量、字面量和表达式中进行字符串插值操作,可以轻松创建用于展示、存储和打印的自定义字符串。

在Swift语言中,String类型与Foundation NSString类进行了无缝桥接。如果利用Cocoa或Cocoa Touch中的Foundation 框架进行开发工作,那么所有的NSString API都可以调用我们创建的任意 String 类型的值。除此之外,还可以使用本章介绍的 String 特性,在任意要求传入NSString实例作为参数的API中使用String类型的值作为替代。

本章字符串和字符的基本内容如图 5-1所示。

图5-1 Swift字符串和字符

5.2 字符串字面量

在Swift语言中,可以在代码中包含一段预定义的字符串值作为字符串字面量(String Literal)。字符串字面量是由双引号“""”包裹着的具有固定顺序的文本字符集。

在Swift语言中,字符串字面量可以用于为常量和变量提供初始值。例如,如下所示的演示代码。

    let someString = "Some string literal value"

在上述演示代码中,变量someString通过字符串字面量进行初始化,Swift 因此推断该变量为String类型。

在Swift语言中,字符串字面量可以包含如下所示的特殊字符。

□ 转义字符\0(空字符)、\\(反斜线)、\t(水平制表符)、\n(换行符)、\r(回车符)、\"(双引号)、\'(单引号)。

□ 单字节 Unicode 标量,写成\xnn,其中nn为两位十六进制数。

□ 双字节 Unicode 标量,写成\unnnn,其中nnnn为4位十六进制数。

□ 四字节 Unicode 标量,写成\Unnnnnnnn,其中nnnnnnnn为8位十六进制数。

例如下面的代码演示了各种特殊字符的使用过程。

实例文件main.swift的具体实现代码如下所示。

    import Foundation
    let wiseWords = "\"做人当如孙仲谋\" -那一夜"
    let dollarSign = "\x24"        // $,  Unicode 标量 U+0024
    let blackHeart = "\u2665"        // ♥,  Unicode 标量 U+2665
    let sparklingHeart = "\U0001F496"  // , Unicode 标量 U+1F496
    println(wiseWords)
    println(dollarSign)
    println(blackHeart)
    println(sparklingHeart)

本实例执行后的效果如图5-2所示。

图5-2 执行效果

其中常量 wiseWords 包含了两个转移特殊字符(双括号),常量 dollarSign、blackHeart 和sparklingHeart演示了3种不同格式的Unicode标量。

5.3 初始化空字符串

在Swift语言中,为了构造一个很长的字符串,可以创建一个空字符串作为初始值。可以将空的字符串字面量赋值给变量,也可以初始化一个新的String实例。

实例文件main.swift的具体实现代码如下所示。

    var emptyString = ""           // 空字符串字面量
    var anotherEmptyString = String()  // 初始化 String 实例
    // 两个字符串均为空并等价。

接下来可以通过检查其Boolean类型的isEmpty属性,以判断该字符串是否为空。

    if emptyString.isEmpty {
        println("什么都没有")
    }

本实例执行后的效果如图5-3所示。

图5-3 执行效果

实例文件main.swift的具体实现代码如下所示。

    import Foundation
    //-----------判断字符串是否为空
    var str3:String = " 啊啊啊啊 "
    if str3.isEmpty { //isEmpty是字符串的一个属性,判断字符串是否为空
        println("str3的值是空")
    }
    else{
        println("str3的值不为空")
    }

本实例执行后的效果如图5-4所示。

图5-4 执行效果

5.4 字符串可变性

在Swift语言中,可以通过将一个特定字符串分配给一个变量来对其进行修改,或者分配给一个常量的方式来保证其不会被修改。例如,如下所示的演示代码。

    var variableString = "Horse"
    variableString += " and carriage"
    // variableString 现在为 "Horse and carriage"
    let constantString = "Highlander"
    constantString += " and another Highlander"
    // 这会报告一个编译错误 (compile-time error) - 常量不可以被修改。

在 Objective-C 程序和 Cocoa 程序中,通过选择两个不同的类(NSString 和 NSMutableString)来指定该字符串是否可以被修改。在Swift语言中,字符串是否可以修改仅通过定义的是变量还是常量来决定,实现了多种类型可变性操作的统一。

实例文件main.swift的具体实现代码如下所示。

    import Foundation
    //------------------换行符----------------
    var  strA = "Hello World \n  1"// \n是换行符
    println("strA=\(strA)")
    //------------------双引号----------------
    var  strB = "\"双引号" // \" 是代表双引号
    println("strB=\(strB)")
    //------------------单引号----------------
    var  strC = "\'单引号"// \' 代表单引号
    println("strC=\(strC)")
    var  aa = "你好"// \n是换行符
    aa += "你好"
    println(aa)

本实例执行后的效果如图5-5所示。

图5-5 执行效果

5.5 字符串是值类型

在Swift语言中,String类型是值类型。如果创建了一个新的字符串,那么当对其进行常量、变量赋值操作时,或在函数/方法中被传递时,会进行值复制操作。在任何情况下,都会对已有字符串值创建新副本,并对该新副本进行传递或赋值操作。

在Swift语言中,与 Cocoa 中的NSString不同,当在 Cocoa 中创建了一个NSString实例,并将其传递给一个函数/方法,或者赋值给一个变量时,被传递或被赋值的是该NSString实例的一个引用,除非特别要求进行值复制,否则字符串不会生成新的副本来进行赋值操作。

在Swift语言中,通过默认字符串复制的方式保证了在函数/方法中传递的是字符串的值。由此可见,无论该值来自哪里,都是我们独自拥有的,开发者可以放心您传递的字符串本身不会被更改。在实际编译时,Swift 编译器会优化字符串的使用,使实际的复制只发生在绝对必要的情况下,这意味着您将字符串作为值类型的同时可以获得极高的性能。

5.6 字符串遍历

在Swift语言中,String类型表示特定序列的Character(字符)类型值的集合。每一个字符值代表一个 Unicode 字符。在Swift程序中,可利用for-in循环来遍历字符串中的每一个字符。

for in 是一个遍历语句,for 后面跟临时变量,in 后面跟数组,临时变量不需要定义,编译器自动生成一个临时变量。for in 会遍历字符集合,然后把每个集合赋值给临时变量。

例如,如下所示的演示代码。

    for character in "Dog!" {
        println(character)
    }
    // D
    // o
    // g
    // !
    //

另外,通过标明一个Character类型注解并通过字符字面量进行赋值,可以建立一个独立的字符常量或变量。例如,如下所示的演示代码。

    let yenSign: Character = "¥"

实例文件main.swift的具体实现代码如下所示。

    import Foundation
    var str = "ABCDEFG"
    /*
    1:str 是字符串变量,是字符集合
    2:temp是临时变量
    3:for in 会遍历字符集合,然后把每个集合赋值给临时变量temp
    */
    for temp  in str {
        println(temp)
    }

本实例执行后的效果如图5-6所示。

图5-6 执行效果

5.7 计算字符数量

在Swift程序中,定义字符的格式如下所示。

    变量关键字和常量关键字  变量 : Character =  字符值

“字符值”必须用双引号括起来,必须是一个字符。字符串和字符的关系是:字符串是由N个字符组成的,即字符串是字符的集合。

实例文件main.swift的具体实现代码如下所示。

    import Foundation
    var ch :Character = "c"  // 字符值必须用双引号,并且是一个字符
    println("ch=\(ch)")

本实例执行后的效果如图5-7所示。

图5-7 执行效果

在 Swift 语言中,通过调用全局 countElements 函数将字符串作为参数进行传递,可以获取该字符串的字符数量。

实例文件main.swift的具体实现代码如下所示。

    import Foundation
    let unusualMenagerie = "Koala , Snail , Penguin , Dromedary "
    let 啊啊啊 = "2014世界杯4强:巴西、德国、阿根廷、荷兰"
    println("unusualMenagerie has \(countElements(unusualMenagerie)) characters")
    println("啊啊啊 has \(countElements(啊啊啊)) 个字符")

本实例执行后的效果如图5-8所示。

图5-8 执行效果

在Swift程序中,可能需要不同数量的内存空间来存储不同的 Unicode 字符的不同表示方式,以及相同 Unicode 字符的不同表示方式。所以,对于在 Swift 的一个字符串中的字符来说,并不一定占用相同的内存空间。因此,字符串的长度不得不通过迭代字符串中每一个字符的长度来进行计算。如您正在处理一个长字符串,需要注意countElements函数必须遍历字符串中的字符以精准计算字符串的长度。另外需要注意的是,通过countElements返回的字符数量并不总是与包含相同字符的NSString的length属性相同。NSString的length属性是基于利用 UTF-16表示的十六位代码单元数字,而不是基于Unicode字符。为了解决这个问题,NSString的length属性在被Swift的String访问时会成为utf16count。

5.8 连接字符串和字符

在Swift语言中,字符串和字符的值可以通过加法运算符(+)相加在一起,并创建一个新的字符串值。字符串变量和常量用“+”或“+=”来拼接组成新的字符串。例如,如下所示的演示代码。

    let string1 = "hello"
    let string2 = " there"
    let character1: Character = "!"
    let character2: Character = "?"
    let stringPlusCharacter = string1 + character1       // 等于 "hello!"
    let stringPlusString = string1 + string2           // 等于 "hello there"
    let characterPlusString = character1 + string1       // 等于 "!hello"
    let characterPlusCharacter = character1 + character2   // 等于 "!?"

在Swift语言中,也可以通过加法赋值运算符(+=)将一个字符串或者字符添加到一个已经存在字符串变量上,例如,如下所示的演示代码。

    var instruction = "look over"
    instruction += string2
    // instruction 现在等于 "look over there"
    var welcome = "good morning"
    welcome += character1
    // welcome 现在等于 "good morning!"

在Swift语言中,不能将一个字符串或者字符添加到一个已经存在的字符变量上,因为字符变量只能包含一个字符。

实例文件main.swift的具体实现代码如下所示。

    import Foundation
    //-----------多个字符串变量拼接用"+"来拼接
    var  str4 = "hello"
    var  str5 = " swift"
    var  str6 = str4+str5 //字符串变量拼接
    println("str6=\(str6)")
    //-----------字符串变量和常量用"+"或"+="来拼接
    var  str7="hello "
    str7 += "swift" //字符串变量和字符常量拼接可以用"+="拼接
    println("str7=\(str7)")

本实例执行后的效果如图5-9所示。

图5-9 执行效果

5.9 字符串插值

在Swift语言中,字符串插值是一种构建新字符串的方式,可以在其中包含常量、变量、字面量和表达式。插入的字符串字面量的每一项都被包裹在以反斜线为前缀的圆括号中,例如,如下所示的演示代码。

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

在上面的演示代码中,multiplier作为"\(multiplier)"被插入一个字符串字面量中。当创建字符串执行插值计算时,此占位符会被替换为multiplier实际的值。

multiplier的值也作为字符串中后面表达式的一部分。该表达式计算Double(multiplier) * 2.5的值并将结果 (7.5) 插入字符串中。在这个例子中,表达式写为\(Double(multiplier) * 2.5)并包含在字符串字面量中。

在Swift语言中,插值字符串中写在括号中的表达式不能包含非转义双引号“"”和反斜杠“\”,并且不能包含回车或换行符。

5.10 比较字符串

在Swift语言中,提供了3种方式来比较字符串的值,分别是字符串相等、前缀相等和后缀相等。

5.10.1 字符串相等

在Swift语言中,如果两个字符串以同一顺序包含完全相同的字符,则认为两者字符串相等。例如,如下所示的演示代码。

    let quotation = "我们是一样一样滴."
    let sameQuotation = "我们是一样一样滴."
    if quotation == sameQuotation {
        println("这两个字符串被认为是相同的")
    }
    // 打印输出:"这两个字符串被认为是相同的"

5.10.2 前缀/后缀相等

在Swift语言中,通过调用字符串的hasPrefix/hasSuffix方法来检查字符串是否拥有特定前缀/后缀。这两个方法均需要以字符串作为参数传入并传出Boolean值,并且都执行基本字符串和前缀/后缀字符串之间逐个字符的比较操作。

在下面的演示代码中,以一个字符串数组表示莎士比亚话剧《罗密欧与朱丽叶》中前两场的场景位置。

    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方法来计算话剧中第一幕的场景数,例如,如下所示的演示代码。

    var act1SceneCount = 0
    for scene in romeoAndJuliet {
        if scene.hasPrefix("Act 1 ") {
          ++act1SceneCount
        }
    }
    println("There are \(act1SceneCount) scenes in Act 1")
    // 打印输出:"There are 5 scenes in Act 1"

同样道理,可以用 hasSuffix 方法来计算发生在不同地方的场景数。例如,如下所示的演示代码。

    var mansionCount = 0
    var cellCount = 0
    for scene in romeoAndJuliet {
        if scene.hasSuffix("Capulet's mansion") {
          ++mansionCount
        } else if scene.hasSuffix("Friar Lawrence's cell") {
          ++cellCount
        }
    }
    println("\(mansionCount) mansion scenes; \(cellCount) cell scenes")
    // 打印输出:"6 mansion scenes; 2 cell scenes”

实例文件main.swift的具体实现代码如下所示。

    import Foundation
    var  strA = "Hello"
    var  strB = "Hello"
    //-----------字符串相等 == -------
    if  strA == strB{
        println("字符串-相等")
    }
    else{
        println("字符串-不相等")
    }
    //-----------字符串前缀相等 hasPrefix---------
    if strA.hasPrefix("H"){
        println("字符串前缀-相等")
    }
    else{
        println("字符串前缀-不相等")
    }
    //-----------字符串后缀相等 hasSuffix---------
    if strA.hasSuffix("o"){
        println("字符串后缀-相等")
    }
    else{
        println("字符串后缀-不相等")
    }

本实例执行后的效果如图5-10所示。

图5-10 执行效果

5.10.3 大写和小写字符串

在Swift语言中,可以通过字符串的uppercaseString和lowercaseString属性来访问大写/小写版本的字符串。例如,如下所示的演示代码。

    let normal = "Could you help me, please?"
    let shouty = normal.uppercaseString
    // shouty 值为 "COULD YOU HELP ME, PLEASE?"
    let whispered = normal.lowercaseString
    // whispered 值为 "could you help me, please?"

实例文件main.swift的具体实现代码如下所示。

    import Foundation
    var  strA = "Hello"
    //-----------字符串大写转换
    var  strB = strA.uppercaseString //uppercaseString 字符串大写转换
    println (strB)
    //------------字符串小写转换
    var strC = strA.lowercaseString //lowercaseString 字符串转换小写
    println(strC)
    var  strD = "AAAbbb"
    //-----------字符串大写转换
    var  strE = strD.uppercaseString //uppercaseString 字符串大写转换
    println (strE)
    var strF = strD.lowercaseString //lowercaseString 字符串转换小写
    println(strF)

本实例执行后的效果如图5-11所示。

图5-11 执行效果

5.11 国际标准Unicode

Unicode是一个国际标准,用于文本的编码和表示。它使您可以用标准格式表示来自任意语言中几乎所有的字符,并能够对文本文件或网页这样的外部资源中的字符进行读/写操作。在Swift语言中,字符串和字符类型是完全兼容 Unicode 标准的,它支持如下所述的一系列不同的 Unicode 编码。

5.11.1 Unicode术语

在 Swift 语言中,Unicode 中每一个字符都可以被解释为一个或多个 Unicode 标量。字符的unicode 标量是一个唯一的21位数字(和名称),例如U+0061表示小写的拉丁字母A ("a"),U+1F425表示小鸡表情 (" ")。

在 Swift 语言中,当 Unicode 字符串被写进文本文件或其他存储结构当中,这些 Unicode 标量将会按照 Unicode 定义的集中格式之一进行编码。其包括UTF-8(以8位代码单元进行编码)和UTF-16(以16位代码单元进行编码)。

5.11.2 字符串的 Unicode 表示

在 Swift 语言中,提供了几种不同的方式来访问字符串的 Unicode 表示方式。例如可以利用for-in来对字符串进行遍历,从而以Unicode字符的方式访问每一个字符值。该过程在使用字符中进行了描述。

另外,在Swift语言中能够以其他3种Unicode 兼容的方式访问字符串的值。

□ UTF-8 代码单元集合(利用字符串的utf8属性进行访问)。

□ UTF-16 代码单元集合(利用字符串的utf16属性进行访问)。

□ 21位的 Unicode 标量值集合(利用字符串的unicodeScalars属性进行访问)。

例如在下面的演示代码中,由D``o``g``!和(DOG FACE,Unicode 标量为U+1F436)组成的字符串中的每一个字符代表着一种不同的表示。

    let dogString = "Dog!"

5.11.3 UTF-8

在Swift语言中,可以通过遍历字符串的utf8属性来访问它的UTF-8表示。这是为UTF8View类型的属性,UTF8View是无符号8位(UInt8)值的集合,每一个UInt8值都是一个字符的 UTF-8表示。例如,如下所示的演示代码。

    for codeUnit in dogString.utf8 {
        print("\(codeUnit) ")
    }
    print("\n")
    // 68 111 103 33 240 159 144 182

在上面的演示代码中,前4个十进制代码单元值(68, 111, 103, 33)代表了字符D o g和!,它们的 UTF-8 表示与 ASCII 表示相同。后4个代码单元值(240, 159, 144, 182)是DOG FACE的4字节UTF-8 表示。

5.11.4 UTF-16

在Swift语言中,可以通过遍历字符串的utf16属性来访问它的UTF-16表示。这是为UTF16View类型的属性,UTF16View是无符号16位(UInt16)值的集合,每一个UInt16都是一个字符的 UTF-16表示。例如,如下所示的演示代码。

    for codeUnit in dogString.utf16 {
        print("\(codeUnit) ")
    }
    print("\n")
    // 68 111 103 33 55357 56374

在上述演示代码中,同样前4个代码单元值(68, 111, 103, 33)代表了字符D o g和!,它们的UTF-16 代码单元和UTF-8完全相同。第五和第六个代码单元值(55357和56374)是DOG FACE字符的 UTF-16 表示。第一个值为 U+D83D(十进制值为 55357),第二个值为 U+DC36(十进制值为 56374)。

5.11.5 Unicode 标量

在Swift语言中,可以通过遍历字符串的unicodeScalars属性来访问它的 Unicode 标量表示。这是为UnicodeScalarView类型的属性,UnicodeScalarView是UnicodeScalar的集合。UnicodeScalar是21位的 Unicode 代码点。

在Swift语言中,每一个UnicodeScalar拥有一个值属性,可以返回对应的21位数值,用UInt32来表示。例如,如下所示的演示代码。

    for scalar in dogString.unicodeScalars {
        print("\(scalar.value) ")
    }
    print("\n")
    // 68 111 103 33 128054

同样,前4个代码单元值(68, 111, 103, 33)代表了字符D o g和!。第五位数值,128054,是一个十六进制1F436的十进制表示。其等同于DOG FACE的Unicode 标量 U+1F436。

在Swift语言中,作为查询字符值属性的一种替代方法,每个UnicodeScalar值也可以用来构建一个新的字符串值,比如在字符串插值中使用如下所示的演示代码。

    for scalar in dogString.unicodeScalars {
        println("\(scalar) ")
    }
    // D
    // o
    // g
    // !
    // ?

实例文件main.swift的具体实现代码如下所示。

    import Cocoa
    var str = "Hello, playground"
    /**
    注意:在Swfit中不论是 String类型还是Character类型都是双引号("")
    */
    for  character in "asdfasdf"
    {
        println(character)
    }
    var yenSign: Character = "a"
    /**
    countElements是个全局的函数用来计算String长度
    */
    let unusualMenagerie = "asdfadf"
    println("unusualMenagerie has \(countElements(unusualMenagerie)) characters\(unusualMenagerie)")
    /**
    Stirng和Character联系起来,还可以使用"+="运算,但是你不能将这个运算结果赋值给已经存在的Character类型,应用
    Character类型只有一个字符
    */
    let string1 = "hello"
    let string2 = " there"
    let character1: Character = "!"
    let character2: Character = "?"
    let stringPlusCharacter = string1 + character1
    let stringPlusString = string1 + string2
    let characterPlusString = character1 + string1
    let characterPlusCharacter = character1 + character2
    /**
    字符串比较:字符串比较包含等于、小于和大于3种
    注意:两个字符串是否相等直接用“==”来进行比较
    */
    let quotation = "We're a lot alike, you and I."
    let sameQuotation = "We're a lot alike, you and I."
    if quotation == sameQuotation {
        println("These two strings are considered equal")
    }

本实例执行后的效果如图5-12所示。

图5-12 执行效果