美文网首页
Swift学习笔记(二)--字符串,集合类型与流控制

Swift学习笔记(二)--字符串,集合类型与流控制

作者: MD5Ryan | 来源:发表于2016-01-10 17:19 被阅读220次

    字符串和字符(Strings and Characters)

    1. 在新版Swift中, 对String进行了本质性的修改, 之前String是字符的集合, 所以, 那个时候可以这样遍历字符串:
    for c in "hello" {
      print(c)
    }
    

    现在还这么玩就要报错了, 原来是字符数组被放入了一个叫characters的成员变量中, 所以最新的遍历姿势是

    for c in "hello".characters {
      print(c)
    }
    // 同样的, String的长度一般由"hello".characters.count来获取
    
    1. 可变数组:
      正如笔记一里面说的, 如果String用var来声明, 就是可变的, 在Swift里面, 数组的追加更加方便, 应该是得益于操作符重载的功劳, 如:
    var variableString = "Horse"
    variableString += " and carriage" // variableString变为"Horse and carriage"
    
    1. 字符串是值类型:
      所谓值类型就是赋值的时候会copy一遍, 赋值一次多一份, 与之对应的是引用类型, 只copy地址, 赋值多少次都只有一份. 在Swift里面, String, Array, Dictionary和Set都是值类型, 这也就是为什么说Swift安全的原因之二.

    2. 字符串与字符数组的转换:
      二者之前的转换非常自然, String有characters返回字符数组, 而字符数组可以通过String的初始化函数转换为String, 如:

    let catCharacters: [Character] = ["C", "a", "t", "!", "🐱"]
    let catString = String(catCharacters)
    
    1. Unicode编码:
      这一块内容很多, 值得了解, 属于重要的内容但是用的时候不会太多, 可以到时候遇到问题再来翻翻文档即可.

    2. 字符串索引
      关于这块内容, 一开始看的时候觉得苹果设计的很奇怪, 先看例子吧:

    let greeting = "Guten Tag!"
    greeting[greeting.startIndex]   // startIndex肯定是0啦
    // G
    greeting[greeting.endIndex.predecessor()]  // endIndex是最后的index+1, 这点会让人比较困惑, 为什么不是最后的index呢
    // !
    greeting[greeting.startIndex.successor()] // successor从当前往后移一位
    // u
    let index = greeting.startIndex.advancedBy(7) // 往前移7位
    greeting[index]
    // 所以下面这样导致runtime error的:
    greeting[greeting.endIndex] // error
    greeting.endIndex.successor() // error
    

    一般情况下我们要对String的字符进行访问, 会直接写index, 但是这样会比较危险, 说不定就越界了, 所以苹果专门有个Index的类型, 还有一些操作的函数来辅助

    字符数组有个indices的属性, 所以它的的访问方式还可以是这样的:

    for index in greeting.characters.indices {
        print("\(greeting[index]) ")
    }
    
    1. 插入和删除
      如果理解了上面的Index, 那么插入就很常规了, 可以插入字符, 字符串和字符数组, 主要还是要操作好index

    2. 比较字符串
      仍然得益于操作符重载, 现在可以直接==来判断2个字符串相等了, 例如:

    let greeting = "hello"
    //2016-3-17更正:
    //let aGreeting = "hell"+"o"  // 这么写两个常量实际指向同一份数据, 
    var aGreeting = "hell"
    aGreeting.appendContentsOf("o") // 这么写才能保证指向不同的数据
    
    for index in greeting.characters.indices {
        print("\(greeting[index])", separator: "|", terminator: ".")
    }
    if greeting == aGreeting {
        print("OK")
    }
    // 用unsafeAddressOf(greeting)和unsafeAddressOf(aGreeting)可以看到2个变量的地址, 发现并不一样, 说明的确是进行了字符串内容的比较, 而不是比较地址
    // 2016-3-17 更正
    上面用unsafeAddressOf()函数获取地址的方法貌似并不准确, 关于String的地址, 目前可以通过str._core._baseAddress来观察, Xcode的Debug查看变量也能看到, 这样最直观.
    

    值得一说的是, 因为String里面有Unicode的原因, 所以对Unicode字符的比较会更加科学, 具体的例子参考官方文档看字符串比较的那一大段

    集合类型

    数组, 字典和集合(Set)都是集合类型, 在Swift中CollectionType是一个协议, 只要你实现了这个协议, 都算集合, 并且如前文所述, 这些集合都是值类型.
    这些集合都支持通用类型(generic types), 可以理解为C++的模板. 在ObjC的最新加的三大特性之一也是这个.

    以Array为例子, Set和Dictionary基本上都差不多, 有特别的会单独点出来.

    1. 声明方式:
    var someInts = [Int]() // 貌似以前不是这么玩的
    print("someInts is of type [Int] with \(someInts.count) items.")
    var threeDoubles = [Double](count: 3, repeatedValue: 0.0)// 默认值的方式
    var shoppingList: [String] = ["Eggs", "Milk"]
    
    // 字典声明也差不多:
    var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
    
    1. 操作:
      追加什么的都比较常规, 直接看一些得益于操作符重载的用法吧:
    shoppingList += ["Baking Powder"]
    shoppingList += ["Chocolate Spread", "Cheese", "Butter"]
    // 注意不能直接 += "Apple"
    

    比较有特色的玩法是:

    shoppingList[4...6] = ["Bananas", "Apples"]//把index为4,5,6的替换为后面的数组, 但是前面是3个元素, 后面是2个, 多出的那个呢? 被丢掉了...
    // 注意: 左边多右边少是可以的, 反过来就错了
    

    Dictionary里面用下标赋值为nil是不会crash的, 与Objc里面setValue:forKey:一样会移除原有值(如有), 但是, 调用updateValue 设置为nil则会crash, 同时, Dictionary里面取出的值是Optional的, 记得拆包再用

    1. 遍历:
      和之前字符串那个差不多, 看一下元组在这里的应用:

    for (index, value) in shoppingList.enumerate() {
    print("Item (index + 1): (value)")
    }
    // cmd+单击可以看到enumerate()返回一个EnumerateSequence对象, 再点进去是一个结构体, 发现有一个next函数, 是这样的
    //func next() -> (index: Int, element: Base.Element)?
    这是所有可遍历对象需要实现的方法, 返回的就是一个元素, 包含index和element

    // 同样Dictionary也可以这么遍历:
    for (airportCode, airportName) in airports {
    print("(airportCode): (airportName)")
    }
    // 如果想单独遍历key或者value, .keys和.values可以拯救

    
    4. 排序:
    提供了sort(), 调用即可:
    

    var numberArray = [3,1,4,8,2]
    numberArray.sort() // 默认升序

    当然还可以自己写, 然后传给sort, 这里牵涉到闭包, 会有很多写法, 最简单的是:
    

    numberArray.sort(>) // 降序排列

    
    4. 集合关系
    intersect: 2个集合的交集
    union: 2个集合的并集
    exclusiveOr: 2个集合并集减去交集
    subtract: 集合减去与另一集合的交集
    官方图:
    https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/setVennDiagram_2x.png
    
    
    简单介绍了下集合类型, 其实里面包含了不少东西, 也牵涉到了苹果对于Swift的设计理念--面向协议编程, 和闭包的东西. 暂时先介绍到这里, 更进一步的可以看看[官方文档](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html#//apple_ref/doc/uid/TP40014097-CH8-ID105)
    
    
    ###流程控制
    
    1. for循环:
    之前已经讲过一些了, 比如:
    

    for i in 1...5 {
    print("(index) times 5 is (index * 5)")
    }
    //比较有意思的是下划线(_)在这里的使用
    for _ in 1...5 {
    // do something five times
    }
    // 用下划线代表这个值我不需要

    // 当然也保留了传统的方式, 只是会用的人应该会很少了
    for var index = 0; index < 3; ++index {
    print("index is (index)")
    }

    
    2. while循环:
    和别的语言差不多, 有区别的是引入了一个repeat, 替换掉了do-while, 因为do因为用在do-try-catch里面去了, 解释好牵强...
    
    3. if条件语句
    基本上也差不多, 只是不需要加括号了, 同时判断的值必须要是实现了BooleanType协议的, 不然会报错, 例如:
    

    var i = 1
    if i { // 会报错, 因为Int没有实现BooleanType
    // print("i is not 0")
    }

    
    4. switch:
    switch在Swift得到了大量的增强, 因为switch涉及到模式匹配, 而Swift的模式匹配十分强大.
    4.1 switch不仅仅匹配整数, 还可以匹配字符,字符串, 严格说来是任何只要实现了Equatable协议的对象
    4.2 switch的case默认自带break, 但是如果这一case一行语句都没有, 是需要手动加上break的. 同时如果你想让这个case不自动break, 就要加上关键字fallthrough, 
    4.3 范围匹配, 以官网例子说明:
    

    let approximateCount = 62
    let countedThings = "moons orbiting Saturn"
    var naturalCount: String
    switch approximateCount {
    case 0:
    naturalCount = "no"
    case 1..<5:
    naturalCount = "a few"
    case 5..<12:
    naturalCount = "several"
    case 12..<100:
    naturalCount = "dozens of"
    case 100..<1000:
    naturalCount = "hundreds of"
    default:
    naturalCount = "many"
    }
    print("There are (naturalCount) (countedThings).")

    值得说明的是, 范围必须包含条件的全部可能的范围, 简单来说就是, 如果不是枚举, 就把default写上
    4.4 元组在switch里面也得到了很广的使用, 还是用例子来说明:
    

    let somePoint = (1, 1)
    switch somePoint {
    case (0, 0):
    print("(0, 0) is at the origin")
    case (_, 0): // 下划线代表任意值都可以匹配上
    print("((somePoint.0), 0) is on the x-axis")
    case (0, _):
    print("(0, (somePoint.1)) is on the y-axis")
    case (-2...2, -2...2):
    print("((somePoint.0), (somePoint.1)) is inside the box")
    default:
    print("((somePoint.0), (somePoint.1)) is outside of the box")
    }

    4.5 值绑定, 依然例子:
    

    let anotherPoint = (2, 0)
    switch anotherPoint {
    case (let x, 0):
    print("on the x-axis with an x value of (x)")
    case (0, let y):
    print("on the y-axis with a y value of (y)")
    case let (x, y):
    print("somewhere else at ((x), (y))")
    }
    // 同时还可以加条件判断, 不过是用where:
    let yetAnotherPoint = (1, -1)
    switch yetAnotherPoint {
    case let (x, y) where x == y:
    print("((x), (y)) is on the line x == y")
    case let (x, y) where x == -y:
    print("((x), (y)) is on the line x == -y")
    case let (x, y):
    print("((x), (y)) is just some arbitrary point")
    }

    5. 流程跳转:
    其实也就是break, continue, fallthrough, return和throw, 这些关键字都可能打破当前的流程, 跳到别的流程中去, fallthrough已经介绍过, 其余的都和别的语言一致
    
    6. 标签:
    与C语言的标签可以跳来跳去不通, Swift的标签配合循环使用, 个人感觉用的范围应该不多, 有兴趣的在官网看下例子就好.
    
    7. guard:
    Swift2引入了一个新的关键字, guard, 可以在函数中对参数进行一些判断, 以官网例子来看代码:
    

    func greet(person: [String: String]) {
    guard let name = person["name"] else { // 必须保证有name, 否则就返回
    return
    }
    print("Hello (name)!")
    guard let location = person["location"] else { // 必须保证有location, 否则就返回
    print("I hope the weather is nice near you.")
    return
    }
    print("I hope the weather is nice in (location).")
    }

    如果一定要问guard和if的区别是什么, 那么直接援引一个例子来感受一下吧(更多guard优于if的例子请[查看原文](http://swift.gg/2015/08/06/swift-guard-better-than-if/)):
    

    struct Person {
    let name: String
    var age: Int
    }

    func createPerson() throws -> Person {
    guard let age = age, let name = name
    where name.characters.count > 0 && age.characters.count > 0 else {
    throw InputError.InputMissing
    }

        guard let ageFormatted = Int(age) else {
            throw InputError.AgeIncorrect
        }
    
        return Person(name: name, age: ageFormatted)
    }
    

    // 如果用if来写, 会是这样的:
    func createPersonNoGuard() -> Person? {
    if let age = age, let name = name
    where name.characters.count > 0 && age.characters.count > 0
    {
    if let ageFormatted = Int(age) {
    return Person(name: name, age: ageFormatted)
    } else {
    return nil
    }
    } else {
    return nil
    }
    }
    需要不停地if-let, 还要写 else {return nil}, 如果再多一点属性需要判断, 就更多的if-let和else{return nil}...

    
    8. 检查版本可用性
    
    这个是Swift2带来的大杀器, 在iOS或者OSX版本更迭中, 总有一些新的API出现, 也有旧的API废弃, 如果你的APP需要兼容多个系统版本, 那么势必要为这些API进行甄别, 但是这样的成本过高, 必须要对这些API什么时候引入, 什么时候废除有谱, 或者每次去查看, 这都对APP出现unrecognized selector异常埋下隐患, 所以这是Swift安全的原因之三, 同时, 如果你写了一些API是高于你在工程中设置的最低版本的话, 编译器是会给你报错, 强制你去判断的, 我觉得这点非常人性化. 判断的具体写法是这样的:
    

    if #available(iOS 9, OSX 10.10, ) {// 最后的这个是通配符, 主要是为以后拓展做的准备, 假如将来来了一个CarOS或者HouseOS, 那么就能匹配上了
    // Use iOS 9 APIs on iOS, and use OS X v10.10 APIs on OS X
    } else {
    // Fall back to earlier iOS and OS X APIs
    }

    
    同时, 如果你的某个类或者某个函数使用了高版本的API, 那么你就要在前面写上@available, 这样编译器就会为你做检查了:
    

    @available(iOS 9.2, OSX 10.10.3, *)
    class MyClass {
    // class definition
    }

    
    简单介绍到这里, 这一篇就算结束了, 前面基本算都是基础知识, 惯例, 细节部分参考[官方文档](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ControlFlow.html#//apple_ref/doc/uid/TP40014097-CH9-ID120)

    相关文章

      网友评论

          本文标题:Swift学习笔记(二)--字符串,集合类型与流控制

          本文链接:https://www.haomeiwen.com/subject/sveqkttx.html