Swift第二周学习总结

字典与集合

  • 写在前面
    数组、字典、集合是Swift中最广泛使用的三种集合类型(Three Primary Collection Type),在上周的总结中,已经分析了数组类型的内容,这里总结一下字典与集合。

字典

字典也是一种常用的Collection Type,是存放键-值对组合的容器,也就是说,可以通过字典中的键,获取对应的值;字典是可空类型,因为给出的键可以没有相应的值与之对应,字典的键-值表达形式可以表述为:

key  --> value

创建字典
与数组类似,创建一个字典,我们可以用定义常量let以及定义变量var来创建一个字典,但是,如果使用let定义,我们就不能对字典中的元素进行增、删、改等操作,只能对其进行遍历。

var dict: [String: String] = ["abacus": "算盘", "abnormal":"异常的", "hello" :
    "你好", "good": "好的"]

通过打印字典中某个元素的key,我们可以获得其value,当然,如果没有对应的key,系统也只会返回nil。

print(dict["hello"]!)
print(dict["abcxyz"])

// 你好
// nil

增:
在字典中添加元素,我们通常采用的方式是:

dict["shit"] = "鸡腿"
dict["delicious"] = "好吃的"
print(dict)

删:
在字典中删除元素,我们可以直接使用remove方法:

dict.removeValueForKey("hello")

也可以将某个元素的值赋值为空:

dict["hello"] = nil

这两行代码是完全等效的。

改:
修改元素时,直接将某个key的value进行修改:

dict["shit"] = "牛粪"
print(dict)

查:
与数组类似,在字典中,我们也可以对整个字典进行遍历来查询字典中所有的元素,我们可以通过元组直接遍历字典中的键和值:

for (key, value) in dict {
    print("\(key) ---> \(value)")
}

当然,我们也可以只遍历字典中所有的键:

for key in dict.keys {
    print("\(key) ---> \(dict[key])")
}

也可以只遍历字典中所有的值:

for value in dict.values {
    print(value)
}

集合

集合是将集合中的元素在磁盘空间中通过生成哈希码(hash code)散列存放的一种Collection Type。

创建集合
与创建数组和字典相同,我们可以通过定义常量let及定义变量var来创建一个集合

var a: Set<Int> = [1 ,2, 3, 1, 2, 5]

在集合中添加元素,我们使用insert方法:

a.insert(100)            // 添加元素

在集合中删除元素,使用remove方法:

a.remove(2)              // 删除元素

在Swift中的集合,与我们数学上所学的集合是相同的,所以,我们可以对集合进行运算:

var b: Set<Int> = [3, 5, 7, 9, 11]
print(a.intersect(b))   // 交集(a和b都有的元素)
print(a.union(b))       // 并集(a和b的所有元素)
print(a.subtract(b))    // 差集(a有b没有的元素)
print(a == b)               // 判断a和b是否相等
print(b.isSubsetOf(a))      // 判断a是不是b的子集
let c: Set<Int> = [1, 3]
print(a.isSubsetOf(c))      // 判断c是不是a的子集
print(a.isSupersetOf(c))    // 判断c是不是a的超集

函数

函数(Function)

在程序设计中,常将一些常用的功能模块编写成函数,放在函数库中供公共选用。要善于利用函数,以减少重复编写程序段的工作量。

函数的参数名
函数名(外部参数名 内部参数名:类型,外部参数名 内部参数名:类型)
如果不写外部参数名那么内部参数名也是外部参数名
可以使用_来作为外部参数名表示省略外部参数名

func myMIn(a x: Int, b y: Int) -> Int {
    return x < y ? x : y
}

调用函数的时候要写函数的外部参数名

print(myMIn(a: 3, b: 5))

函数的定义和调用
定义函数:
函数定义的关键字:func
func 函数名(参数列表) -> 返回类型 { 函数的执行体 } Swift中函数的参数可以设定默认值
如果在调用函数的时候没有给该参数赋值就直接使用默认值

func sayHello(personName: String, _ alreadyGreeted: Bool = false) -> String {
     let greeting = "Hello," + personName + "!"
     如果函数的返回类型不是void 那么函数中一定有return语句
     return greeting
     person = "王小锤"      // 编译错误
    if alreadyGreeted {
        return "怎么又是你," + personName + "!"
    }
    else {
        return "你好," + personName + "!"
    }
}

调用函数
函数名(参数值)
调用Swift的函数时,在默认情况下从第二个参数开始需要写函数名
如果没有给第二个参数赋值那么就直接使用默认值

print(sayHello("王大锤", true))
et str = sayHello("逗比")
print(str)

在Swift中,函数的参数列表是可变参数列表

func sum(nums: Int...) -> Int {
    var total = 0
    for num in nums {
        total += num
    }
    return total
}

print(sum())
print(sum(999))
print(sum(1, 2, 3))
print(90, 82, 37, 68, 55, 11, 99)

我们也可以使用元组(tuple)让函数一次性返回多条数据

func minMax(array: [Int]) -> (min: Int, max: Int) {
    var currentMin = array[0]
    var currentMax = array[0]
    for value in array[1..<array.count] {
        if value < currentMin {
            currentMin = value
        }
        else if value > currentMax {
            currentMax = value
        }
    }
    return (currentMin, currentMax)
}

let b = minMax([23, 45, 99, 68, 72, 12, 55])
    print(b.min)        // print(b.0)
    print(b.max)        // print(b.1)

func swap(inout x: Int, inout _ y: Int) -> Void {
    // (x, y) = (y, x)
    let temp = x
    x = y
    y = temp      // 两种写法等效
}

函数调用传参都是传值

swap(&x, &y)
print("x = \(x)")
print("y = \(y)")

inout - 输入输出函数(不仅将数据传入函数还要从函数中取出数据)

func createX(inout x: Int) {
    x = 1000
}

var x = 1
// inout类型的参数前要加上&符号
createX(&x)
print(x)

函数的递归调用(一个函数直接或间接的调用自身)

递归的条件:

  1. 递归公式
  2. 收敛条件
func f(n: Int) -> Double {
    if n == 0 || n == 1 {
        return 1
    }
    return Double(n) * f(n - 1)
}

递归运算中的一个经典例题----汉诺伊塔(hannoi)

var counter = 1
func hannoi(n: Int, _ a: String, _ b: String, _ c:String) {
    if n > 0 {
    hannoi(n - 1, a, c, b)
    print("\(counter): \(a) --> \(b)")
    counter += 1
    hannoi(n - 1, c, b, a)
    }
}
hannoi(5, "A", "B", "C")

函数的闭包

在Swift中,函数是一种类型,这也就意味着函数可以作为变量或常量的类型,同理函数也可以作为另一个函数的参数或返回值:

func foo (array:[Int], fn: (Int , Int) -> Int) -> Int {
    var sum = array[0]
    for x in array[1..<array.count] {
        sum = fn(sum, x)
    }
    return sum
}
let a = [1, 2, 3, 4, 5]

当调用foo函数时第二个参数可以传什么?

  1. 所有自定义的(Int, Int) -> Int类型的函数
print(foo(a, fn: sum))
  1. 传入二元运算符: +-*/%(因为运算符也是函数)
print(foo(a, fn:  +))
  1. 传入匿名函数(闭包)
    3.1 完整的闭包写法
print(foo(a, fn: { (a: Int, b: Int) -> Int in
    return a + b
}))

3.2 省略掉类型和不必要的括号

print(foo(a, fn: { a, b  in a + b }))

3.3 省略参数名

print(foo(a, fn: { $0 + $1 }))

3.4 尾随闭包

print(foo(a) { (a, b) -> Int in
    return a + b
})
print(foo(a) { $0 + $1 })

如果函数的最后一个参数是闭包可以写成尾随闭包的形式,也就是将闭包放到函数参数的圆括号外面写在一对花括号中
,如果函数后面有尾随闭包且函数的圆括号中没有参数
那么函数的圆括号也可以省略(仅限于有尾随闭包的场景)。

var array = ["game", "abacus", "hello", "cat", "good", "internationalization", "chaos", "dislike", "zealot", "young"]
array.sortInPlace(>)
array.sortInPlace({ $0 > $1})
array.sortInPlace() { $0 > $1}
array.sortInPlace { $0 > $1 }
array.sortInPlace {
 if $0.characters.count == $1.characters.count {
        return $0 < $1
    }
    return $0.characters.count < $1.characters.count
}
print(array)

在数组中,我们常常会遇到一些我们不需要的元素,这个时候,我们就需要一些方法,来挑选出我们需要的元素

1.过滤
过滤方法filter,将我们不需要的元素过滤出去,留下需要的元素,例如:

let array = [23, 37, 96, 55, 40, 92, 68, 88]

我们要从数组中挑选出所有大于50的元素:

let newArray = array.filter { $0 > 50 }
print(newArray)

或者从数组中过滤出所有的偶数:

let newArray2 = array.filter { $0 % 2 == 0 }
print(newArray2)

2.映射
在数学里,映射则是个术语,指两个元素的集之间元素相互对应的关系,为名词;亦指形成对应关系”这一个动作,动词

let newArray3 = array.map { $0 * $0 }
print(newArray3)

let oArray = array.map { (x: Int) -> Int in
    return x / 2
}

3.缩减(归约)
将数组中所有元素求和:

let result1 = array.reduce(0, combine: +)
print(result1)

将数组中所有元素求积:

let result2 = array.reduce(1, combine: *)
print(result2)

其中,"0"和"1"代表赋的初始值,combine表示缩减的方法。
选出数组中最大的数:

let result3 = array.reduce(array[0]) {
    $1 > $0 ? $1 : $0
}
print(result3)

面向对象程序设计

类(class)

类是面向对象程序设计中的概念,是面向对象编程的基础。类的实质是一种数据类型,类似于int、char等基本类型,不同的是它是一种复杂的数据类型。因为它的本质是类型,而不是数据,所以不存在于内存中,不能被直接操作,只有被实例化为对象时,才会变得可操作。类是对现实生活中一类具有共同特征的事物的抽象。如果一个程序里提供的类型与应用中的概念有直接的对应,这个程序就会更容易理解,也更容易修改。一组经过很好选择的用户定义的类会使程序更简洁。

类的内部封装了方法,用于操作自身的成员。类是对某种对象的定义,具有行为(be-havior),它描述一个对象能够做什么以及做的方法(method),它们是可以对这个对象进行操作的程序和过程。它包含有关对象动作方式的信息,包括它的名称、方法、属性和事件。

创建类
创建一个类,我们一般分为三步:
步骤1: 定义类(如果你要用的类苹果已经提供了就直接进入第二步)
定义类就可以创建出新的类型
例如,我么定义一个学生类

enum Gender {               // 枚举
    case Male, Female
}

class Student {
    // 变量定义到类的外面就叫变量 - variable
    // 变量定义到类的里面就叫属性 - property
    // 数据抽象 - 找到和学生相关的属性(找名词)
    var name: String
    var age: Int
    
    // 初始化方法(构造方法/构造器) - constructor
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
    
    // 函数写到类的外面就叫函数 - function
    // 函数写到类的里面就叫方法 - method
    // 行为抽象 - 找到和学生相关的方法(找动词)
    func eat() {
        print("\(name)正在吃饭.")
    }
    func study(courseName: String) {
        print("\(name)正在学习\(courseName).")
    }
    func watchJapaneseAV() {
        if age >= 18 {
            print("\(name)正在观看岛国爱情动作片.")
        }
        else {
            print("亲爱的\(name),我们推荐你观看《熊出没》.")
        }
    }
}

步骤2: 创建对象(调用初始化方法)

let stu1 = Student(name: "王尼玛", age: 30)

步骤3: 给对象发消息(通过给对象发消息来解决问题)

stu1.eat()
stu1.study("Swift程序设计")
stu1.watchJapaneseAV()

let stu2 = Student(name: "王大锤", age: 15)
stu2.eat()
stu2.study("中国近代史")
stu2.watchJapaneseAV()

初始化方法重载
在一个类中,我们可以定义多个初始化方法,这叫做初始化方法的重载:

public class Point {
    var x: Double
    var y: Double

    convenience init() {
        self.init(x: 0, y: 0)
    }
    
    convenience init(point: (Double, Double)) {
        self.init(x: point.0, y: point.1)
    }
    
 
    init(x: Double, y: Double) {
        self.x = x
        self.y = y
    }
    
    func moveTO(x: Double, _ y: Double) {
        self.x = x
        self.y = y
    }
    
    func distanceTo(other: Point) -> Double {
        let dx = x - other.x
        let dy = y - other.y
        return sqrt(dx * dx + dy * dy)
    }
}

convenience重载过的初始化方法,我们叫做"便利初始化方法",也叫"便利构造器";而被调用的初始化方法,我们叫做"指派初始化方法",也叫"指派构造器"。

访问修饰符
在创建一个类时,我们对用处不同的类可以添加不同的访问修饰符来进行区别,其中:

  • public (公开)
  • internal (内部的) - 默认
  • private (私有)
    public表示本以被外部调用;
    internal表示只能在本项目内使用;
    private表示只能读取,不能更改。

存储属性通常是private的 因为数据要保护起来
方法一般是public的 因为方法是对象接受的消息
如果自定义的类没有打算在其他项目中使用 可以不写访问修饰符
直接使用默认的internal修饰符表示在本项目中公开对其他项目私有

计算属性
在类的方法中,有一些属性是通过对定义的属性做运算得到的属性,但又不能成为方法,这种属性我们叫做计算属性,而被运算的属性叫做存储属性。

public class Circle {
    // stored property
    // 存储属性(保存和圆相关的数据的属性)
    var center: Point
    var radius: Double
    
    init(center: Point, radius: Double) {
        self.radius = radius
        self.center = center
    }
    
    // 通常获得某个计算出的值的方法都可以设计成计算属性
    // computational property
    // 计算属性(通过对存储属性做运算得到的属性)
    var perimeter: Double {
        // 圆的周长是一个只读属性
        // 所以此处只有get{}没有set{}
        get { return 2 * M_PI * radius }
    }
    
    var area: Double {
        get { return M_PI * radius * radius }
    }
}

继承和多态

继承
从已有的类创建新类的过程提供继承信息的称为父类(超类/基类)
得到继承信息的称为子类(派生类/衍生类)
通常子类除了得到父类的继承信息还会增加一些自己特有的东西
所以子类的能力一定比父类更强大
继承的意义在于子类可以服用父类的代码并且增强系统现有的功能

创建一个叫"人"的类

 import Foundation

enum Gender {
    case Male
    case Female
}

class Person {
    var name: String
    var age: Int
    var gender: Gender
   
    init(name: String, age: Int, gender: Gender) {
        self.name = name
        self.age = age
        self.gender = gender
    }
    
    func eat() {
        print("\(name)正在吃饭")
    }    
}

在"人"这个类的基础上,创建一个叫"老师"的子类:

class Teacher: Person {
    var title: String
    
    
    init(name: String, age: Int, gender: Gender, title: String) {
        self.title = title
        super.init(name: name, age: age, gender: gender )
    }
    
    
    func teach(courseName: String) {
        print("\(name)\(title)正在教\(courseName).")
    }

}

我们可以将子类型的对象赋值给父类型的变量(因为子类型跟父类之间是IS-A关系)
学生是人,老师是人,所以学生和老师的对象可以赋值给人类型的变量

let p2: Person = Student(name: "王尼玛", age: 18, gender: .Female, major: "计算机科学与技术")
p2.eat()

在继承中,我们经常会遇到类型转换的问题
如果要将父类型的变量转换成子类型需要用as运算股进行类型转换
如果能够确认父类型的变量中就是某种子类型的的对象可以用as!进行转换
如果不能确定父类型的变量中是哪种子类型可以用as?尝试转换

(p2 as! Student).study("Swift程序设计")
if let temp = p2 as? Teacher {
    temp.teach("Java")
}
else {
    print("\(p2.name)不是老师")
}

多态
对于多态,我们先上代码:
定义一个叫"宠物"的父类:

enum Gender {
    case Male
    case Female
}

class Pet {
    var nickname: String
    var gender: Gender
    var age: Int
    
    init(nickname: String, gender: Gender, age: Int) {
        self.nickname = nickname
        self.gender = gender
        self.age = age
    }
    
    func eat() {
        print("\(nickname)正在吃东西.")
    }
    
    func play() {
        print("\(nickname)正在玩耍.")
    }
    
    func shout() {
        print("\(nickname)发出了叫声.")
    }
}

在"宠物"类的基础上,定义一个"猫"类,一个"狗"类

class Cat: Pet {
    var hairColor: String?
    
    override func play() {
        super.play()
        print("\(nickname)正在玩毛线球.")
    }
    
    override func shout() {
        print("\(nickname):喵喵喵!")
    }
    
    func catchTheMouse() {
        print("\(nickname)正在抓老鼠.")
    }
}

父类有的方法子类可以重新实现 这个过程叫做方法重写
需要在方法前添加override关键字
重写有时也被称为置换/覆盖/复写

我们创建一个对象数组:

let petsArray = [
    Cat(nickname: "加菲", gender: .Female, age: 2),
    Dog(nickname: "旺财", gender: .Male, age: 3, isLarge: true)

调用相同的方法:

for pet in petsArray {
    pet.eat()
    pet.play()
}

但是最后运行代码的时候会发现,调用了相同的方法,得到的结果却完全不同。

同样的对象类型(Pet类型)接收相同的消息(调用相同的方法)
但是做了不同的事情 这就是多态(polymrophism)

实现多态的关键步骤:

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

推荐阅读更多精彩内容