Swift 语言初探2

1.字典

字典是用于存储键值对的容器
字典中的key是通过hash表存储的,基本保证key唯一对应一个value
通过键获得对应的值(可空类型,因为所给的键可能没以后相与之对应的值)


使用方法

var dict: [String:String] = ["abacus":"算盘", "abnormal":"异常的","hello":"你好","good":"好的"]
key -> value
print(dict["hello"])
// 添加元素
dict["delicious"] = "美味的"

// 删除元素
dict.removeValueForKey("hello")
dict["hello"] = nil

// 修改元素
dict["delicious"] = "难吃的"

// 遍历字典中的所有值
for value in dict.values {
    print(value)
}
// 遍历字典中的所有键
for key in dict.keys {
    print("\(key) -> \(dict[key])")
}
// 直接通过一个元组获得字典中的键和值
for (key, value) in dict {
    print("\(key) -> \(value)")
}


2. 集合

集合的存储方式使用的是哈希码(hash code)/ 散列码
集合中所有的元素都是离散存储的,相同的元素由于哈希码相同所以不会重复显示
使用方法

// 注意一定要使用Set表示集合,
// 如果不写,Swift会将其自动推断为Array
var a:Set = [1, 2, 3, 1, 2, 5]
// 添加元素
a.insert(100)
// 删除元素
a.remove(1)
var b:Set = [3, 5, 7, 9, 11]
// 交集
print(a.intersect(b))
// 并集
print(a.union(b))
// 差集(a有但是b没有的元素)
print(a.subtract(b))
// 判断a和b是否是同一集合 返回true相同 false不相同
print(a == b)
// 判断b是否是a的子集
print(b.isSubsetOf(a))
// 判断b是否是a的超集
print(b.isSupersetOf(a))
// 判断a和b是否有相同元素 返回true没有 false有
print(a.isDisjoinWith(b))

3.函数

定义函数
func 函数名(参数列表) -> 返回类型 { 函数执行体 }
Swift中的函数可以设定默认值
如果调用函数时没有赋值,默认使用默认值
只有当函数的返回值为Void的时候,函数执行体中没有return


调用函数
Swift中函数从第二个参数开始需要写参数名(注:在Swift3.0开始,第一个参数也需要写参数名了)
可以使用元组(tuple)让函数依次返回多条数据
使用方法

func minMax(array: [Int]) -> (min:Int, max: Int)? {
  return(Min, Max)
}

Swift中的参数列表允许是可变列表(参数可以是任意多个)
使用方法

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

在Swift中的函数还有一种输入输出参数 - inout
使用方法

func swap(inout a:Int , inout _ b:Int) -> Void {
    let temp = a
    a = b
    b = temp   
}
var a = 5 , b = 10
// 函数调用传参都是传值
swap(&a, &b)
print("a = \(a)")
print("b = \(b)")

小技巧

// 获取系统时间
let date = NSDate()
// 获取系统当前时间
let cal = NSCalendar.currentCalendar()

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

  1. 有递归公式
  2. 有收敛条件

递归计算1~n的和

func sum(n:Int) -> Int {
  if n == 1 {
    return 1
  }
  return n + sum(n - 1)
}

思想


求1~n的和等同于求n +函数对(n - 1)求和
将n和n之前的部分分别看成不同的2个部分。
又将(n-1)和(n-1)之前的部分分别看成2个不同的部分。


用递归求阶乘

func f(n: Int) -> Double {
  if n == 0 || n == 1 {
    return 1
  }  
  return Double(n) * f( n -1 )
}

汉诺伊塔问题

func haoi(n:Int,_ a:String, _ b:String, _ c:String) {
    if n > 0 {
        haoi(n - 1,  a,  c,  b)
        print("\(a) --> \(b)")
        haoi(n - 1, c, b, a)
        
    }
}
haoi(1,"A" ,"B" ,"C")

15个基督教和15个非基督教徒

问题简述
杀死15个人,每次从1报数到9,杀死。然后后面一个人在报数1。在杀第9个。这样杀死15个人之后,剩下的全是基督教徒。问这30个人是如何排序的?
模型分析
首先我们需要模拟一个报数场景。目标是杀死其中15个人。所以需要创建一个变量来保存杀死的人数-counter。当counter = 15,终止循环。确定杀人的方式,每次报9的人将会被杀死。所以,需要创建一个变量来保存每个人报的数字-num。当num=9,杀死这个人。由于死人是不能报数的。所以我们可以创建一个Bool类型的值来区分活人和死人。这里我们用true表示活人,如果num=9,我们就将这个人赋值为false。因此,我们还需要创建一个存储有30个Bool值的数组。由于需要让counter=15。显然需要让30个人循环报数。因此,当循环到第30个人的时候,我们需要让他成为程序中的第0个人。这样就实现了循环报数。直到counter=15。终止循环,然后打印基督教徒和非基督教徒的站位方式。
完整代码

var array = [Bool](count: 30, repeatedValue: true)
var count = 0
var num = 0
var i = 0
while count < 15 {
    if array[i] {
        num += 1
        if num == 9  {
            array[i] = false
            count += 1
            num = 0
        }
    }
    i += 1
    if i == 30 {
        i = 0
    }
}
for i in 0..

传入年月日,返回该日是这一年的第几天

模型分析
由于1年有12个月。每个月的天数不同。所以我们创建一个数组保存每个月的天数。因为,有闰年和平年之分。所以我们还需要判断输入的年份是否为闰年。第多少天=这个月的几号+这个月之前月份的总天数。
完整代码

// 设计一个函数判断该年份是否为闰年
func isLeapYear(year:Int) -> Bool {
    return year % 4 == 0 && year % 100 != 0 || year % 400 == 0
}
func daysOfYear(year: Int, month: Int, day: Int) -> Int {
  var daysOfMonth = [31,28,31,30,31,30,31,31,30,31,30,31]
  if isLeapYear(year) {
    daysOfMonth[1] = 29
 }
  var sum = 0
  for days in daysOfMonth[0..

函数作为参数传入

// 在swift中函数是一种类型
// 这也意味着函数可以作为变量或常量的类型
// 同理,函数也可以作为另一个函数的参数或者返回值
// 传入一个数组,将数组中的元素求和
func foo(array:[Int],fn:(Int,Int) -> Int) -> Int {
    var sum = 0
    for x in array {
        sum = fn(sum, x)
    }
    return sum
}
let a = [1, 2, 3, 4, 5]
// 当调用foo函数时第二个参数可以传什么?

// 1. 所有自定义的(Int, Int) -> Int类型的函数
print(foo(a, fn: sum))

// 2. 传入二元运算符: +  - * / % (因为运算符都是函数)
print(foo(a, fn: +))

// 3. 传入匿名函数(闭包)
// 3.1 完整闭包写法
print(foo(a, fn: { (a, b) -> 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) { $0 + $1 })

函数的闭包

var array = ["game","abacus","hello","cat","good","internationalization",
"chaos","dislike","zealot","young"]

 array.sortInPlace(>)
 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)

数组的3种用法

let array = [23, 37, 96, 55, 40, 92, 68, 88]
1. 过滤

过滤的关键字是filter

// 这里是留下数组中大于50的元素
let newArray1 = array.filter { $0 > 50 }
print(newArray1)
2. 映射

映射的关键字是map

// 这里是显示数组元素中所有数平方后的元素
let newArray3 = array.map { $0 * $0 }
print(newArray3)
3. 缩减

缩减的关键字是reduce

// 缩减如果是Int型的,需要一个输入初始化值
let result1 = array.reduce(0, combine: +)
print(result1)
let reslut2 = array.reduce(1, combine: *)
print(reslut2)
let max = array[0]
let reslut3 = array.reduce(array[0]) {
    $1 > $0 ? $1 : $0
}
print(reslut3)
let strArray = ["I","love","you"]
// 如果是字符串类型的,可以省略为空字符,但不能是空
let reslut4 = strArray.reduce("") { $0 + " " + $1 }
print(reslut4)

小技巧
如果传入的参数不符合函数的运行要求,需要提示的时候
可以在函数内部使用断言

断言关键字为assert

Class - 类

创建一个类的步骤

这里我们用circle类来做一个例子

0. 发现类
//  - 在对问题的描述里面找名词和动词
//  - 名词称为类中的属性,动词会称为类中的方法

// 访问修饰符
//  - public (公开)
//  - internal (内部的) - 默认
//  - private (私有)
1. 定义类
//  - 数据抽象(属性)
//  - 行为抽象(方法)
//  - 初始化方法
- - - 
class Circle {
    // stored property
    // 存储属性(保存和圆相关的数据的属性)
    var center:Point
    var radius: Double

    // 我们可以在一个类中定义多个初始化方法
    // convenience init()- 便利初始化构造器
     
    // init()是指派初始化方法/ 指派构造器
    // --用于被其他初始化方法调用
    // 应当保证所有的存储属性都被初始化(有初始值)
    init(center:Point, radius:Double) {
        self.center = center
        self.radius = radius
    }
    
    // 通常获得某个计算出的值的方法都可以设计成计算属性
    // computational property
    // 计算属性(通过对存储属性做运算得到的属性)
    var perimeter: Double {
        // 圆的周长是一个只读属性
        // 所以此处只有get{}没有set{}
        get {return 2 * M_PI * radius }
    }
    
    var  area: Double {
        get {return M_PI * radius * radius }
    }
    // 行为抽象
}
// 拟定一个初始圆半径
let r = 5.5
2. 创建对象
let small = Circle(center:Point(x: 0,y: 0),radius: r)
let big = Circle(center:Point(x: 0,y: 0),radius: r + 3)

3. 给对象发消息(调用对象的方法)
let fencePrice = big.perimeter * 1.5
print(NSString(format: "围墙的造价为:¥%.1f元", fencePrice))

let aislePrice = ((big.area) - small.area) * 3.5

print(NSString(format: "过道的造价为:¥%.1f元", aislePrice))


小技巧
点到点的距离调用的方法distanceTo

// 这里使用了自己已经定义好的点类
var va: Point
var vb: Point
var vc: Point
let ab = va.distanceTo(vb)
let bc = vb.distanceTo(vc)
let ca = vc.distanceTo(va)

三角形三条边计算三角形面积公式

sqrt(halfP * (halfP - ab) * (halfP - bc) * (halfP - ca))

产生一个范围类的随机数的通用公式

func randomInt(min:UInt32,_ max:UInt32) -> Int {
    return Int(arc4random_uniform(max - min + 1) + min)
}

短除法

// 短除法(欧几里得算法)
// x和y的最大公约数跟y%x和x的最大公约数一样
// Greatest Common Divisor
func gcd(x:Int ,_ y: Int) -> Int {
    if x > y {
        return gcd(y, x)
    }
    else if y % x != 0 {
        return gcd(y % x, x)
    }
    else {
        return x
    }
}

枚举

例子 - 花色的枚举

enum Suite: String {
    case Spade = "♠️"
    case Heart = "♥️"
    case Club = "♣️"
    case Diamond = "♦️"
}

小技巧

    /**
     姓名隐去最后一个字符
     */
    var name: String {
        get {
            let value = _name.characters.count > 2 ? -2 : -1
//            let displayName = _name.substringToIndex(_name.endIndex.predecessor())
            let displayName = _name.substringToIndex(_name.endIndex.advancedBy(value))
            return displayName + "*"
        }
    }

类的多态与继承

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

示例代码


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

多态:计算属性的重写就是多态行为
实现多态的关键步骤:

  1. 方法重写(子类在继承父类的过程中,对父类已有的方法进行重写)
    -同时不同的子类可以给出不同的实现方式
    -方法的重写的关键字是override
    -重写有时也翻译为置换/覆盖/覆写
    -重写的时候也可以通过super.来调用父类方法
  2. 对象造型(将子类当做父类型进行使用)

每天小技巧
写代码的终极原则:高内聚,低耦合


面向对象的7个原则:

1) 开闭原则;------面向扩展开放,面向修改关闭。 
2) 里氏转换原则;------超类存在的地方,子类是可以替换的。 
3) 依赖倒转原则;------实现尽量依赖抽象,不依赖具体实现。 
4) 接口隔离原则;------应当为客户端提供尽可能小的单独的接口,而不是提供大的总的接口。 
5) 组合/聚合复用原则;------尽量使用合成/聚合达到复用,尽量少用继承。
原则:一个类中有另一个类的对象。 
6) “迪米特”法则;------又叫最少知识原则,一个软件实体应当尽可能少的与其他实体发生相互作用。 
7) 单一职责原则。-----每一个类应该专注于做一件事情。

你可能感兴趣的:(Swift 语言初探2)