zoukankan      html  css  js  c++  java
  • 2.Swift快速浏览

    传统认为,在一个新的语言的第一个程序要在屏幕上显示“Hello world!”。在Swift,可以用一行代码来完成:

    println("Hello, world!")

    如果你已经在C或Objective-C中编写过代码,这个语法看起来对你来说应该很熟悉 - 在Swift中,这行代码就是一个完整的程序。你并不需要为输入/输出或字符串处理函数导入一个单独的库。写在全局范围内的代码作为切入点程序,所以你并不需要一个main函数。你也不必在每个语句结尾写分号。

    这次浏览给你足够的信息来开始通过Swift编写代码来向您展示如何完成各种编程任务。如果你不明白的地方,在本书的其余部分都会详细的解释和介绍,请不要担心。

    注:最好的经验,打开本章,Xcode的Playgrounds。Playgrounds允许你编辑代码列表,并立即看到结果。

    简单的值

    使用let来做一个常量,var来做一个变量。常量值并不需要在编译时已知,但你必须给他确切地分配一次值。这意味着你能使用常量来命名一个值,确定一次,多个地方使用。

    var myVariable = 42
    myVariable = 50
    let myConstant = 42

    一个常量或变量必须具有相同的类型作为你要分配给它的值。但是,你并不总是有明确的写入类型。你提供一个值,当你创建一个常量或变量让编译器推断出它的类型。在上面的例子中,编译器推断myVariable的是整数,因为它的初始值是一个整数。

    如果初始值不提供足够的信息(或者如果不存在初始值),写入变量后可指定类型,由冒号分开后写它的指定类型。

    let implicitInteger = 70
    let implicitDouble = 70.0
    let explicitDouble: Double = 70

     值永远不会隐式转换为另一种类型。如果需要的值转换为不同的类型,明确地制造一个该类型的实例。

    let label = "The width is "
    let width = 94
    let widthLabel = label + String(width)

    像Java中直接label+width这种方式是不能用在Swift中的

    还有一个更简单的方法,包括在字符串值:写在括号中的数值,并写在括号前添加一个反斜线()。例如:

    let apples = 3
    let oranges = 5
    let appleSummary = "I have (apples) apples."
    let fruitSummary = "I have (apples + oranges) pieces of fruit."

    创建数组和字段使用使用方括号([]),和通过写在括号内的索引或键访问元素。

    var shoppingList = ["catfish", "water", "tulips", "blue paint"]
    shoppingList[1] = "bottle of water"
     
    var occupations = [
        "Malcolm": "Captain",
        "Kaylee": "Mechanic",
    ]
    occupations["Jayne"] = "Public Relations"

    要创建一个空数组或字典,使用初化的语法如下。

    let emptyArray = [String]()
    let emptyDictionary = [String: Float]()

    如果类型信息可以推断,你可以写一个空数组[]和一个空字典为[:] - 例如,当您为变量设定一个新值或参数传递给一个函数。

    shoppingList = []
    occupations = [:]

    这种数组和字典只能使用静态复制方式shoppingList["ss",0],因为他们没有指定类型

    控制流

    使用if和switch来写条件语句,使用for-in,for,while,do-while做循环。周围的条件或循环变量括号是可选的。围绕本体括号是必需的。

    let individualScores = [75, 43, 103, 87, 12]
    var teamScore = 0
    for score in individualScores {
        if score > 50 {
            teamScore += 3
        } else {
            teamScore += 1
        }
    }
    println(teamScore)

    在if语句,条件必须是布尔表达式,这意味着,这样的代码,if score{...}是一个错误,它没有隐式布尔条件。

    你可以使用if和let一起工作来判断可能丢失的值。这些值被表示为可选。一个可选值包含一个值或包含无(nil)。在值的类型之后写一个问号(?),以标记这个值为可选。

    var optionalString: String? = "Hello"
    println(optionalString == nil)
     
    var optionalName: String? = "John Appleseed"
    var greeting = "Hello!"
    if let name = optionalName {
        greeting = "Hello, (name)"
    }

    如果可选的值是nil,条件为false的,括号中的代码被跳过。否则,可选的值被解开并分配到常量let后面,这使得解包值可用于代码块内。

    switch支持任何类型的数据和各种比较的操作,他们并不局限于整数。

    let vegetable = "red pepper"
    switch vegetable {
    case "celery":
        let vegetableComment = "Add some raisins and make ants on a log."
    case "cucumber", "watercress":
        let vegetableComment = "That would make a good tea sandwich."
    case let x where x.hasSuffix("pepper"):
        let vegetableComment = "Is it a spicy (x)?"
    default:
        let vegetableComment = "Everything tastes good in soup."
    }

    这里不能删掉default,不然编辑器会报错

    注意let在这里只能一个模式分配一个值,也可以匹配一个模式的一部分

    在执行完上面那段代码后,只要匹配到一个情况,switch就不会继续往下执行,所以这里不需要用到其他语言中的break语法,来跳出switch。当然在循环中,你还是可以使用break语法

    你可以使用for-in来迭代每一个字典中的键值对,字典是一个无序的集合所以他们的键和值以任意顺序迭代。

    let interestingNumbers = [
        "Prime": [2, 3, 5, 7, 11, 13],
        "Fibonacci": [1, 1, 2, 3, 5, 8],
        "Square": [1, 4, 9, 16, 25],
    ]
    var largest = 0
    for (kind, numbers) in interestingNumbers {
        for number in numbers {
            if number > largest {
                largest = number
            }
        }
    }
    println(largest)

    使用while重复一个代码块直到条件改变。一个循环的条件能在结束中替换,从而确保环运行至少一次。

    var n = 2
    while n < 100 {
        n = n * 2
    }
    println(n)
     
    var m = 2
    do {
        m = m * 2
    } while m < 100
    println(m)

    你能使用索引来保持一个循环,通过使用..<来使用一个索引范围,或者使用显示的初始化,条件,自增。下面两个循环做的是同样的事情

    var firstForLoop = 0
    for i in 0..<4 {
        firstForLoop += i
    }
    println(firstForLoop)
     
    var secondForLoop = 0
    for var i = 0; i < 4; ++i {
        secondForLoop += i
    }
    println(secondForLoop)

    使用..<做出一个范围,它忽略了上限值,你也可以使用...包含一个范围,包括了上限值。

    函数和闭包

    使用func来声明函数。通过名字和参数列表来调用函数。用 ->来获取函数的返回类型。

    func greet(name: String, day: String) -> String {
        return "Hello (name), today is (day)."
    }
    greet("Bob", "Tuesday")

    使用一个数组做一个复合值,例如,从一个函数返回多个值。一个元组的元素可以按名称或编号引用。

    func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) {
        var min = scores[0]
        var max = scores[0]
        var sum = 0
        
        for score in scores {
            if score > max {
                max = score
            } else if score < min {
                min = score
            }
            sum += score
        }
        
        return (min, max, sum)
    }
    let statistics = calculateStatistics([5, 3, 100, 3, 9])
    println(statistics.sum)
    println(statistics.2)

    函数还可以带可变数量的参数,收集他们到一个数组。

    func sumOf(numbers: Int...) -> Int {
        var sum = 0
        for number in numbers {
            sum += number
        }
        return sum
    }
    sumOf()
    sumOf(42, 597, 12)

    函数可以嵌套。嵌套函数可以访问到外部函数中声明的变量。您可以使用嵌套函数来组织很长或很复杂代码的函数。

    func returnFifteen() -> Int {
        var y = 10
        func add() {
            y += 5
        }
        add()
        return y
    }
    returnFifteen()

    函数也是一种的类型。这意味着一个函数可以返回另一个函数作为其值。

    func makeIncrementer() -> (Int -> Int) {
        func addOne(number: Int) -> Int {
            return 1 + number
        }
        return addOne
    }
    var increment = makeIncrementer()
    increment(7)

    函数可以获取另一个函数作为它参数中的一员。(这个有点绕,就是函数中的某个参数是一个函数)

    func hasAnyMatches(list: [Int], condition: Int -> Bool) -> Bool {
        for item in list {
            if condition(item) {
                return true
            }
        }
        return false
    }
    func lessThanTen(number: Int) -> Bool {
        return number < 10
    }
    var numbers = [20, 19, 7, 12]
    hasAnyMatches(numbers, lessThanTen)

     函数实际上有一种特殊情况叫闭包:代码块可以在以后调用。在一个封闭的代码中可以访问像变量和函数这样的东西,并且是在创建封闭的范围内可用,即使在闭包在不同的范围内被执行时,您已经看见过这样的例子就是嵌套函数不同的范围。您可以通过使用括号({})并且不用写名字来封闭。在使用单独的参数和返回类型中使用。

    numbers.map({
        (number: Int) -> Int in
        let result = 3 * number
        return result
    })

    你有几种更简洁的闭包写法可选。当一个封闭的类型是已知的,诸如委托回调,可以省略它的参数类型,它的返回类型,或两者的类型。单封语句隐式返回他们唯一的语句的值。

    let mappedNumbers = numbers.map({ number in 3 * number })
    println(mappedNumbers)

    你可以按编号,而不是按名称,这种方法是指参数是在很短的闭包内特别有用。

    let sortedNumbers = sorted(numbers) { $0 > $1 }
    println(sortedNumbers)

    对象和类

    使用class后面跟一个类的名字来创建一个类。属性和函数都写在类里面,这点和其他语言差不多

    class Shape {
        var numberOfSides = 0
        func simpleDescription() -> String {
            return "A shape with (numberOfSides) sides."
        }
    }

    通过在类名后接一对小括号来创建一个类的实例。使用点语法来访问属性和实例的方法。这里和其他语言不一样,它不需要new

    var shape = Shape()
    shape.numberOfSides = 7
    var shapeDescription = shape.simpleDescription()

    这个版本的Shape类缺少一些重要的东西:类的初始化。可使用init创建,类似Java,C++构造函数。

    class NamedShape {
        var numberOfSides: Int = 0
        var name: String
        
        init(name: String) {
            self.name = name
        }
        
        func simpleDescription() -> String {
            return "A shape with (numberOfSides) sides."
        }
       }
    var NamedShape=NamedShape(name:"myname")

    注意self用于区分是类里面的属性还是init中的参数。如果你写了init函数,在实例化对象时,需要传入参数名加冒号,在接上参数值。这是swift的语法,和Java,C直接构造函数传值不太一样。

    你还可以使用deinit代码块来处理,对象释放时的操作。如果你要写继承,类名后接冒号,在接上父类名,不会使用例如extends这样的关键字

    如果你要覆盖子类的方法,请使用override关键字

    class Square: NamedShape {
        var sideLength: Double
        
        init(sideLength: Double, name: String) {
            self.sideLength = sideLength
            super.init(name: name)
            numberOfSides = 4
        }
        
        func area() ->  Double {
            return sideLength * sideLength
        }
        
        override func simpleDescription() -> String {
            return "A square with sides of length (sideLength)."
        }
    }
    let test = Square(sideLength: 5.2, name: "my test square")
    test.area()
    test.simpleDescription()

    属性除了能存储简单的值,它还可以有一个getter和一个setter。

    class EquilateralTriangle: NamedShape {
        var sideLength: Double = 0.0
        
        init(sideLength: Double, name: String) {
            self.sideLength = sideLength
            super.init(name: name)
            numberOfSides = 3
        }
        
        var perimeter: Double {
            get {
                return 3.0 * sideLength
            }
            set {
                sideLength = newValue / 3.0
            }
        }
        
        override func simpleDescription() -> String {
            return "An equilateral triangle with sides of length (sideLength)."
        }
    }
    var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
    println(triangle.perimeter)
    triangle.perimeter = 9.9
    println(triangle.sideLength)

    对于perimeter有一个设置器,新的值可以隐式的使用newValue,当然你也可以显示的使用自定义的字段比如

      set (arg) {
                sideLength = arg / 3.0
      }

    请注意,初始化的是,EquilateralTriangle类有三个不同的步骤:

    1.设置该子类声明属性的值。
    2.调用父类的初始化。
    3.通过改变父类中定义的属性值。你也可以使用getter,setter方法做任何额外的设置工作。

    如果你不需要计算属性,但你仍然需要提供代码在执行之前或设置之后做一些处理的话,你可以使用willSet和didSet。例如,下面的类确保其三角形的边长总是和正方形的边长相同。

    class TriangleAndSquare {
        var triangle: EquilateralTriangle {
            willSet {
                square.sideLength = newValue.sideLength
            }
        }
        var square: Square {
            willSet {
                triangle.sideLength = newValue.sideLength
            }
        }
        init(size: Double, name: String) {
            square = Square(sideLength: size, name: name)
            triangle = EquilateralTriangle(sideLength: size, name: name)
        }
    }
    var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape")
    println(triangleAndSquare.square.sideLength)
    println(triangleAndSquare.triangle.sideLength)
    triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
    println(triangleAndSquare.triangle.sideLength)

    在类中的方法和外部函数有个重要区别。函数中的参数名称仅在函数中使用(局部参数名),但方法中的参数名字,能在你调用方法时,需要使用外部参数名。默认情况下类中方法的第一个参数名默认为局部参数,所以调用时,不需要显示输入参数名,

    但第二和后续的参数名,要显示输入,并且同时作为局部参数名称和外部参数名称,当然你也可以分别指定第二个参数或之后的参数哪个为局部名,哪个为外部名称,中间用空格区分,空格前面为外部名,空格后面为内部名称

    class Counter {
        var count: Int = 0
        func incrementBy(amount: Int, numberOfTimes times: Int) {
            count += amount * times
        }
    }
    var counter = Counter()
    counter.incrementBy(2, numberOfTimes: 7)

    当你用可选值工作时,你可以在操作符之前使用?如果在?前的值为nil,那么在?后之后的一切都被忽略,并且整个表达式的值为nil。否则可选值被解包,在?后的一切将被展开。在这两种情况下,整个表达式的值都是可选值

    let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
    let sideLength = optionalSquare?.sideLength

    枚举和结构体

    用enum来创建一个枚举。如类和所有其他命名类型,枚举可以有与它们相关联的方法。

    enum Rank: Int {
        case Ace = 1
        case Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten
        case Jack, Queen, King
        func simpleDescription() -> String {
            switch self {
            case .Ace:
                return "ace"
            case .Jack:
                return "jack"
            case .Queen:
                return "queen"
            case .King:
                return "king"
            default:
                return String(self.rawValue)
            }
        }
    }
    let ace = Rank.Ace
    let aceRawValue = ace.rawValue

    在上面的例子中,枚举的原始值类型为Int,所以你只需要指定的第一个原始值。原始值的其余部分会按顺序分配。您还可以使用字符串或浮点数作为原始类型枚举。使用rawValue属性来访问枚举成员的原始值。

    使用init?(rawValue:)初始化原始值的一个实例。Rank(rawValue: 3)表达式是一个可选值

    if let convertedRank = Rank(rawValue: 3) {
        let threeDescription = convertedRank.simpleDescription()
    }

    枚举的成员都有实际值,写他们的原始值的方法不只一种。事实上,有些情况下,原始值是没有意义的,你不必非要提供它。

    enum Suit {
        case Spades, Hearts, Diamonds, Clubs
        func simpleDescription() -> String {
            switch self {
            case .Spades:
                return "spades"
            case .Hearts:
                return "hearts"
            case .Diamonds:
                return "diamonds"
            case .Clubs:
                return "clubs"
            }
        }
    }
    let hearts = Suit.Hearts
    let heartsDescription = hearts.simpleDescription()

    请注意,枚举Hearts成员上面提到的两种方法:当分配一个值到hearts常量时,枚举成员Suit.Hearts被提到了它的全名,因为这个常量没有指定明确的类型。在switch内部,枚举成员是指由缩写形式.Hearts因为self已经知道是suit。您可以使用缩写形式在任何已经的类型上。

    使用struct来创建结构体。结构体和类相似支持许多相同的行为,包括方法和初始化。其中结构和类之间最重要的区别在于,当他们在你的代码各地的传递时,结构始终按副本(被复制)传递,而类按引用传递。

    struct Card {
        var rank: Rank
        var suit: Suit
        func simpleDescription() -> String {
            return "The (rank.simpleDescription()) of (suit.simpleDescription())"
        }
    }
    let threeOfSpades = Card(rank: .Three, suit: .Spades)
    let threeOfSpadesDescription = threeOfSpades.simpleDescription()

    一个枚举成员的实例可以与值的实例相关联。同一枚举成员的实例可以有不同的值与它们相关联。当你创建实例时,分配一个值。分配的值和原始值是不同的:一个枚举成员的原始值对于它的所有实例是相同的,并且在定义枚举时,提供了原始值。

    例如,考虑从服务器请求关于日出和日落时间的情况。该服务器做出响应,正常的或错误的信息。

    enum ServerResponse {
        case Result(String, String)
        case Error(String)
    }
     
    let success = ServerResponse.Result("6:00 am", "8:09 pm")
    let failure = ServerResponse.Error("Out of cheese.")
     
    switch success {
    case let .Result(sunrise, sunset):
        let serverResponse = "Sunrise is at (sunrise) and sunset is at (sunset)."
    case let .Error(error):
        let serverResponse = "Failure...  (error)"
    }

    协议和扩展

    使用protocol来声明一个协议

    protocol ExampleProtocol {
        var simpleDescription: String { get }
        mutating func adjust()
    }

    类,枚举,结构体都可以采用协议,协议可以理解为java的接口。例如上面的simpleDescription{get}表示只读

    class SimpleClass: ExampleProtocol {
        var simpleDescription: String = "A very simple class."
        var anotherProperty: Int = 69105
        func adjust() {
            simpleDescription += "  Now 100% adjusted."
        }
    }
    var a = SimpleClass()
    a.adjust()
    let aDescription = a.simpleDescription
     
    struct SimpleStructure: ExampleProtocol {
        var simpleDescription: String = "A simple structure"
        mutating func adjust() {
            simpleDescription += " (adjusted)"
        }
    }
    var b = SimpleStructure()
    b.adjust()
    let bDescription = b.simpleDescription

    注意在SimpleStructure的声明使用了关键字mutating的标记修改结构体的方法。SimpleClass是一个类,它并不需要任何方法标记为mutating,因为在一个类中的方法可以随时修改他自己。但结构体不一样,它默认是不允许内部方法修改自己属性的,

    所以加上mutating就表示,结构体也能修改他自己了

    使用extension为现有的类型添加功能,如新方法并计算属性。您可以使用扩展添加协议一致性,用来在别处声明类型,甚至,你从库或框架中导入一个类型。

    extension Int: ExampleProtocol {
        var simpleDescription: String {
            return "The number (self)"
        }
        mutating func adjust() {
            self += 42
        }
    }
    println(7.simpleDescription)

    你能为协议命名其他的名字,例如创建一个有不同类型对象的集合都符合单一的协议,但你使用值类型的协议工作时,协议外部定义的方法不可用

    let protocolValue: ExampleProtocol = a
    println(protocolValue.simpleDescription)
    // println(protocolValue.anotherProperty)  // Uncomment to see the error

     即使变量protocolValue具有运行时类型SimpleClass,编译器也会把他处理成ExampleProtocol类型,这意味着你不能访问实现了协议后的类,它里面的属性和方法。

    泛型

    在尖括号里面写一个名字,来生成一个泛型函数或类型

    func repeat<Item>(item: Item, times: Int) -> [Item] {
        var result = [Item]()
        for i in 0..<times {
            result.append(item)
        }
        return result
    }
    repeat("knock", 4)

    你能使用函数或方法的泛型格式,用于类,枚举和结构体

    // Reimplement the Swift standard library's optional type
    enum OptionalValue<T> {
        case None
        case Some(T)
    }
    var possibleInteger: OptionalValue<Int> = .None
    possibleInteger = .Some(100)

    类型名称后使用where来指定一个列表需求,例如需求这个类型实现一个协议,需求两个类型一样,或者需求类有一个指定的子类

    func anyCommonElements <T, U where T: SequenceType, U: SequenceType, T.Generator.Element: Equatable, T.Generator.Element == U.Generator.Element> (lhs: T, rhs: U) -> Bool {
        for lhsItem in lhs {
            for rhsItem in rhs {
                if lhsItem == rhsItem {
                    return true
                }
            }
        }
        return false
    }
    anyCommonElements([1, 2, 3], [3])

    在简单情况下,你能省略where,并且简单的在冒号后(:)写协议或类<T: Equatable>和 <T where T: Equatable>是一样的

    对于Generator不理解的朋友可以去https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_SequenceType_Protocol/index.html#//apple_ref/swift/tdef/s:PSs12SequenceType9Generator查看

  • 相关阅读:
    c++调用lua
    HTTP实现长连接(TTP1.1和HTTP1.0相比较而言,最大的区别就是增加了持久连接支持Connection: keep-alive)
    C++: std::string 与 Unicode 如何结合?
    统计一下你写过多少代码
    解读jQuery中extend函数
    C#如何通过SOCKET的方式获取HTTPONLY COOKIE
    Java进阶代码
    SQLSERVER聚集索引与非聚集索引的再次研究(上)
    c,c++函数返回多个值的方法
    COM思想的背后
  • 原文地址:https://www.cnblogs.com/tianjian/p/4597154.html
Copyright © 2011-2022 走看看