• 2.Swift教程翻译系列——Swift概览


    英文版PDF下载地址http://download.csdn.net/detail/tsingheng/7480427

    依照传统学习程序语言都是从hello,world開始,在Swfit里面仅仅须要一行代码即可了

    println("Hello, world")

    你要是学过C语言或者OC。这样的语法应该非常熟悉。可是在Swfit里面这单独一行代码就是一个完整的程序。

    你都不须要再去导入什么库,比方input/output或者string之类的。

    全局作用范围内(global scope)的代码用来作为程序的入口。所以连main方法都不须要了。甚至语句结尾连分号都不用写了。

    这份观光手冊展示了怎么用swift去完毕各种各样的编程任务。要是有什么看不懂的也不要怕,这里介绍的东西到后面都还会有具体介绍。

    NOTE:为了获得最好的学习体验,建议打开XCode建个playgrounds,写完代码立刻就能看到结果了。

    1.简单值

    let用来定义常量,var用来定义变量。

    常亮的值在编译的时候能够是未知的,可是你仅仅能赋值一次。

    你能够定义一个常亮给他赋个值。然后其它地方随便用可是不改他的值。

    var myVariable = 42
    myVariable = 5
    let myConstant = 42

    无论是常量还是变量,赋值的时候值类型跟常量或者变量的类型必须同样。可是我们不必每次都把类型明白写出来。当你声明的时候就给他赋个值,编译器就能判断出来是什么类型了。上面这个样例中,编译器能判断出来myVarible是integer由于初始化的值是integer。要是依据初始化值不能判断出来是什么类型。或者没有初始化值,拿在变量名称后面就必须加上: 类型。比方以下的样例

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

    值从来不会自己主动转换成其它类型。假设你要转换,就用值再创建一个你须要的类型,比方

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

    事实上有更简单的方法能在字符串里加其它类型,把变量名放在括号中,再在括号前面加个即可了。

    比方

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

    如要要创建数组类型或者字典类型就用中括号[]。假设要获取当中的节点就在[]中写节点的下标或者key值

    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 = Dictionary<String, Float>()

    要是类型能够判断出来,就能够用[]和[:]来替换了。比方说上面已经定义过shoppingList数组了。编译器能够判断出shoppingList是一个字符串数组,这个时候我想让他变成空数组,就仅仅须要写shoppingList = []就能够了。字典类型就不用说了吧。

    2.控制流

    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
        }
    }
    teamScore

    在if语句中,条件表达式必须是一个Boolean表达式,所以上面样例中假设写成if score {...}就是错的。

    对于有些可能丢失的值,你能够使用If和let的组合。这样的值后面会介绍,叫做可选类型(optional),一个可选类型值要么有一个确切的值。要么就是nil也就是值丢失了。假设要定义可选类型的值,就在变量类型后面加一个问号。

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

    EXPERIMENT 试试把optionalName的值换成nil,给if语句加一个else分支,else里面给greeting还有一个字符串,看看最后的greeting是什么。

    注意条件语句let name = optionalName,假设optionalName的值是nil,那这个条件语句返回的就是false。大括号中的语句也就不会运行了。否则optionalName的值就被赋给常量name,而且在后面大括号中面能够使用name的值。

    switch语句支持不论什么类型的数据和各种各样的比較运算,不仅仅是比較integer的值是不是相等。

    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." }


    EXPERIMENT 试试去掉default,看看会得到什么错误。

    假设在switch里面匹配到某个case,运行完case的这段代码以后程序就会跳出这个switch。这里可不会像其它语言一样继续向后匹配。所曾经面样例中都找不到一个breakl。。

    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
            }
        }
    }
    largest

    EXPERIMENT 加入另外一个变量来记录最大数字的kind。也就是最大的那个数字是什么。(拿上面这个样例来说结果应该是Square)

    使用while能够反复运行一段代码,知道判断条件改变。

    循环条件语句也能够放在循环体后面,这样能保证循环体至少被运行一次。

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

    在for循环中能够有两种方式使用下标,一种是使用..(两个点)来创建一个下标的范围,或者明白地写初始值,条件表达式,还有增长表达式(非常经典的for(int i = 0; i <= n; i++){})。

    两种方式效果一样

    var firstForLoop = 0
    for i in 0..3 {
        firstForLoop += i
    }
    firstForLoop
     
    var secondForLoop = 0
    for var i = 0; i < 3; ++i {
        secondForLoop += 1
    }
    secondForLoop

    用..(两点儿)的范围是半开半闭区间的。用...(三点儿)的范围两边儿都是闭区间。

    3.函数和闭包

    函数使用keywordfunc来声明。调用函数的时候在方法名称后面加个括号,括号中加上參数值列表。函数返回值类型和參数列表中间用->隔开。

    func greet(name: String, day: String) -> String {
      return "Hello (name), today is (day)."
    }
    greet("Bob", "Tuesday")
    EXPERIMENT 把參数day去掉。加入另外一个參数在返回的问候语里加上今天的午餐。


    能够使用元组返回多个值

    func getGasPrices() -> (Double, Double, Double){
        return (3.59, 3.69, 3.79)
    }
    getGasPrices()

    方法也能够使用变长參数,參数存储在数组中。

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

    EXPERIMENT 写个函数求參数平均值。

    在Swift里函数能够嵌套。内部函数能够訪问外层函数定义的变量。假设有些代码比較长或者非常复杂,你能够使用内部函数来简化。

    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)

    函数实际上是闭包的一种特殊情况。

    你能够写个闭包不要名字,仅仅须要把代码放到大括号中面。在代码里面用in分隔參数与返回类型跟函数体。

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

    EXPERIMENT 写个闭包,对于全部的奇数都返回0。

    另外还有几个选项能够让你更方便的来写闭包。

    假设说一个闭包的类型已经知道了,比方是托付的一个回调函数,那你能够省略他的參数类型或者返回类型,都省略了也行。仅仅有单独一行语句的闭包返回的是那条语句的计算结果。

    numbers.map({number in 3*number})

    你要是不想用參数名来使用參数,也能够用数字,这玩意儿在超短闭包里面非常有用。

    一个闭包作为函数的最后一个參数能够放在括号后面。

    sort([1, 5, 3, 12, 2]){$0 > $1}

    4.对象和类

    使用keywordclass后面加类名,就能创建一个类了。类里面的属性声明跟前面提到的常量或者变量的声明方式一样。除非是类变量。相似的,类里面的函数跟方法的声明也是一样的。

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

    EXPERIMENT 用let加入一个常量属性,再加入一个带參数的方法。

    假设要创建类的实例。在类名后面加上括号即可了。用点操作符来获取实例的属性或者调用实例的方法。

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

    上面定义的Shape类缺了个非常重要的东东。就是初始化,当创建类的实例的时候须要初始化的。初始化方法的名字是init

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

    注意上面init里面有个self,跟java里面的this应该是一个意思,这里是用来区分等号左边的name是对象的name属性。右边的name是初始化方法的參数name。

    实例化方法參数传递跟普通方法传递是一样的。对象的每个属性都必须要实例化,要么实在声明的地方赋值(比方numberOfSide)。要么就在init里面(比方name)。

    假设想在对象被回收的时候做一些其它操作,能够用deinit方法来创建析构函数。

    类继承的方法是在子类后面加上冒号。再加上父类。

    Swift没有规定说一个类必须要继承哪个类,所以不须要继承的时候冒号跟父类就能够省略了。(好像OC必须至少要继承NSObject)

    子类假设要重写父类的方法就要在子类方法前面加override,为了防止你不小心又一次了哪个方法。假设你不写。编译器是要报错的。假设子类的方法父类里面没有,你还加个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()

    EXPERIMENT 创建一个Circle类,继承NameShape。初始化方法參数有半径跟名称。实现Circle的area和describe方法。

    属性除了像上面那样的以外,还能够有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 triagle with sides of length (sideLength)."
        }
    }
    var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
    triangle.perimeter
    triangle.perimeter = 9.9
    triangle.sideLength

    在perimeter的setter方法里面,參数名称默认是newValue,你也能够在set后面加上括号指定參数的名称。

    上面样例中的EquilateralTriangle的初始化方法做了三件事情

    1.给子类定义的属性赋值。

    2.调用父类的初始化方法。

    3.改变父类中定义的属性的值。这里就能够调用对象的各种方法了,包含getter,setter。

    假设你不须要去计算某个属性的值。可是想在属性赋值之前和之后做点儿事情,那你能够用willSet和didSet。有点儿像java里面的回调方法。比方以下的样例能确保三角形周长跟正方形周长相等。

    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")
    triangleAndSquare.square.sideLength
    triangleAndSquare.triangle.sideLength
    triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
    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,假设是第二种情况,可选值是个展开的值(unwrapped value不知道怎么翻译),那跟在问号后面的也是个展开的值。

    两种情况下整个表达式返回的都是个可选值。

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

    5.枚举和结构类型

    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.toRaw())
            }
        }
    }
    let ace = Rank.Ace
    let aceRawValue = ace.toRaw()

    EXPERIMENT 写个方法依据原始数据比較两个Rank类型的大小。

    上面样例中,枚举类型的原始类型是Int,这样的类型你能够仅仅指出第一个枚举的原始值。

    剩下的将会按顺序递增。

    当然原始值类型也能够用浮点数或者字符串。

    toRaw和fromRaw两个函数能够分别获取原始值和枚举值。

    if let convertedRank = Rank.fromRaw(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()

    EXPERIMENT 加入一个方法color,假设是黑桃或者梅花,返回"这牌是黑色的",假设是红桃儿或者方片儿返回“这牌是红色儿的”。

    注意到上面样例中有两种方式来获取枚举值Hearts。

    当要赋值给常量hearts的时候是要用Suit.Heart的。由于这个时候常亮还不知道自己到底是什么类型。可是在switch里面,就省略了点前面的Suit。由于self知道自己就是个Suit。

    仅仅要变量或者常量知道自己是那种枚举类型,就能用这样的缩写形式。

    struct用来创建结构类型。预计做行政的都知道。

    struct支持非常多类的特性,包含方法。初始化器。他跟类最大得差别就是在赋值的时候,class对象是传递引用。结构类型是值传递。假设你是做JAVA的,能够觉得结构类型就好比java里面的基本数据类型一样。

    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()

    EXPERIMENT 给Card加入一个方法,创建一副牌。不要俩王的。

    以下这两句有点儿难理解。对着后面的样例看看就能明白了。枚举的一个成员的能够相应非常多个实例。而这非常多个实例又能够相应不同的值。怎么做到呢。要在你创建枚举成员实例的时候给他提供相应的值。

    这个值跟原始值还是不一样的。对已同一个成员的多个实例来说他们的原始值都是一样的,都是在定义枚举成员的时候指定的。

    比方说要从一台服务器获取日出和日落的时间。

    服务端要么返回一段日落或者日出时间的信息,要么返回一个错误消息。

    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)"
    }

    EXPERIMENT 给ServerResponse和后面的switch再加入一个case。比方表示今儿没出太阳。

    注意上面日出跟日落时间是怎么从ServerResponse中取出来而且在switch里面匹配的。

    6.接口和拓展

    protocol用来声明接口。

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

    类,枚举,结构,都能实现接口。

    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

    EXPERIMENT 写个实现上面接口的枚举。

    注意上面SimpleStructure里面有个方法前面加了个mutating。表明这种方法要改动结构体。那为什么上面的class里面的方法没加呢。由于类里面的方法总是会改变类的类型。

    extension用来给已有的类型加入新的功能。

    比方加入方法。属性啊之类的。你还能够用extension给其它随意地方定义的类型加入接口,甚至是导入的第三方库或者框架都行。

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

    EXPERIMENT 用extension给Double类型加个求绝对值的方法。

    使用接口名称的方式跟使用其它类型方式几乎相同的。

    比方说创建一堆对象。对象类型不一样,可是都实现了同一个接口。当你使用类型是接口的对象时,在接口里面未定义的方法是不能调用的。

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

    上面样例中即使protocolValue实际上是一种SimpleClass的对象,可是编译器仅仅能把他当成ExampleProtocol。所以说没在接口里面定义的方法你是调用不到的。

    (话说JAVA不是能够强制类型转换吗)。

    7.泛型

    泛型的用法是在尖括号中加上类型名称。

    func repeat<ItemType>(item: ItemType, times: Int) -> ItemType[] {
        var result = ItemType[]()
        for i in 0..times {
            result += 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)

    wherekeyword放在类型名称后面用来声明一些条件。

    比方类型必须实现某个接口,或者两个类型必须同样,或者类型必须是某个类的子类。

    跟java里面的概念一样的。

    func anyCommonElements <T, U where T: Sequence, U: Sequence, T.GeneratorType.Element: Equatable, T.GeneratorType.Element == U.GeneratorType.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])

    EXPERIMENT 改动上面的anyCommonElements函数。返回两个序列里面同样元素组成的数组。

    一般简单情况下能够省略where,仅仅在类型名后面加上冒号和父类或者接口的名称。比方<T: Equatable>跟<T where T : Equatable>效果是一样的。

    本章完。下章地址 3.Swift基础知识

  • 相关阅读:
    基于TensorRT的YOLO(V3\4\5)模型部署《方案二》
    realsenseD435采集图片
    LibTorch实战六:U2Net实战部署<三>
    LibTorch实战六:C++版本YOLOV5.4的部署<二>
    Win10环境下YOLO5 快速配置与测试
    《一、YOLOV1细节原理全解析》
    LibTorch实战六:U2Net实战训练<二>
    基于TensorRT的YOLO(V3\4\5)模型部署《方案一》
    《YOLOV4&5原理与源代码解析之六:注意力机制SAM》
    LibTorch实战五:模型序列化
  • 原文地址:https://www.cnblogs.com/mqxnongmin/p/10614726.html
Copyright © 2020-2023  润新知