Swift学习总结
基础部分
程序是指令的集合,写程序就是写一系列的指令去控制计算机去做我们想做的事,
编译:将程序设计语言转换成计算机能理解得机器语言或者某种中间代码的过程。
冯诺依曼体系结构的计算机:
- 使用二进制
- 程序存储执行
变量和常量
定义变量或者常量是为了保存数据。变量和常量就是某种类型的值的存储空间。
var a: Int = 10
a = 100
var b: Int
b = 1000
var c = 10000
let d: Int = 10
// d = 100 //compiler error
let e = 1000
说明:Swift有非常强大的类型推断,所以定义变量和常量时,1.如果可以的话应该直接使用类型推断不用手动制定类型 2.如果可以的话应该尽可能使用常量而不是变量
swift语言元素
var a: Int = 10
关键字:在swift语言中有特殊含义的单词:func 、var、class
标识符:你给变量、常量、函数、类、结构、协议、枚举、方法、属性等取的名字
标识符的命名规则:
- 字母、数字、下划线、数数字不能开头字不
- 大小写敏感(区分大小写)
- 不能使用关键字做标识符
- 使用驼峰命名法(命名变量等第一个单词小写,从第二个单词时每个单词首字母大写。 命名类,结构,协议,枚举每个单词首字母大写)
- 见名知意
- 命名私有的属性和方法时以下划线开头(private)
运算符:swift中的运算符其实都是函数
- 赋值运算符(=、 +=、 -=、 *=、 /=)
- 算术运算符(=、 -、 *、 /、 %)
- 关系运算符(==、 !=、 <、 <=、 >、 >=)结果是Bool值
- 逻辑运算符(&& 、||、 !)
- 条件运算符( ? : )
- 其他运算符([]、 .、 ??(可空类型的三元运算符)、?(不能确定)、 !(能确定,如果没有程序崩溃))
字面(常)量:
- 整数字面量:10、1_234_567、0x10、 0o10、0b10
- 小数字面量: 123.45、1.2345e2、oxab.cdp2
- 字符字面量: "c"、"\n"、"\u{41}"(万国码字符)
- 字符串字面量:"hello"、"caf\u{e9}"
- 布尔字面量: true、false
- 空值字面量:nil
- 类型字面量:string.self(计算类型)、 UILable
分隔符:将不通的语言元素符号分开
说明:swift中每个语句后面的分号可写可不写,写代码时尽量保证一行只有一条语句这样就可以省略分号
分支和循环
分支
-if...else
var x = 5
if x < 1{
y = 3 * x
}
else if x < 5{
y = 2 * x
}
else{
y = 5 * x
}
print(y)
-switch... case...default
var face = Int(arc4Rodom_uniform(6) + 1)
switch face{
case 1:
print("唱一首")
case 2...5:
print("喝一杯")
default:
print("重新来过")
}
循环
-while
下面程序实现了1...100求和
var i = 1
var sum = 0
while i <= 100{
sum += i
i +=1
}
print(sum)
-repeat...while...(至少执行一次代码)
下面程序实现了1...100求和
var sum = 0
var i = 1
repeat{
sum += i
i += 1
}while i <= 100
print(sum)
-for...
下面程序实现了1...100求和
var sum = 0
for i in 1...100{
sum += i
}
print(sum)
穷举法:穷尽所有可能性直到找到正确的答案
下面实现百钱百鸡问题求解。
for i in 0...20{
for j in 0...33{
let k = 100 - i - j
if 5 * i + 3 * j + k / 3 == 100 && k % 3 == 0{
print("公鸡有\(i)只,母鸡有\(j)只,小鸡有\(k)只")
}
}
}
在循环中可以使用break关键字来提前终止循环,也可以使用continue关键字使循环直接进入下一轮,但是应该尽量减少对break和continue的使用,因为他们不会让你的程序变得更好。
综合案例:Craps赌博游戏
游戏规则:玩家摇骰子,如果第一次摇出了7点或11点玩家胜;如果庄家摇出了2、3、12点,庄家胜;其他点数游戏继续。在继续的过程中玩家重新摇骰子,如果摇出7点,庄家胜;如果摇出了第一次的点数,玩家胜;否则,游戏继续。
func roll() -> Int{
return Int(arc4Rodm_uniform(6)) + 1
}
let firstPoint = roll() + roll()
print("玩家摇出了\(firstPoint)点",terminator:"")
var needsGoOn = false
switch firstPoint{
case 7,11:
print("玩家胜")
case 2,3,12:
print("庄家胜")
default:
needsGoOn = true
}
while needsGoOn{
let currentPoint = roll() + roll()
print("玩家摇出了\(currentPoint)点",terminator:"")
if currentPoint == 7{
print("庄家胜")
needsGoOn = false
}
else if currentPoint == firstPoint{
print("玩家胜")
needsGoOn = false
}
else{
needsGoOn = true
}
容器
数组
-创建数组
let array1: [Int] = []
let array2: Array = []
let array3 = [1,2,3,4,5]
var array4 = [Int](count:5,repeatedValue:0)
var array5 = Array(count:5,repeatValue:0)
- 添加元素
array1.append(2)
array1.append(3)
array1.insert(1,atIndex:array.count)
array += [5]
-删除元素
array1.removeAtIndex(2)
array1.removeFirst()
array1.removeFirst(2)
removeLast()
array1.removeRange(1..2)
array1.removaAll()
-修改元素
array3[0] = 100
array3[array3.count - 1] = 500
-遍历元素
for i in 0..
2.只读循环
for x in array3{
print(x)
}
说明for in循环时一个只读循环,这也就意味着在循环的过程中不能对数组中的元素进行修改
3.元素可以修改
for (i,temp) in array3.enumerate(){
print("\(i).\(temp)")
}
提醒:操作数组时最重要的是不要越界访问元素,数组对象的count属性表明了数组中有多少个元素,name有效的索引(下标)范围是0...count - 1
数组中的元素也可以是数组,因此我们可以构造多位数组,最常见的是二维数组,它相当于是一个有行有列的数组,数组中的每个元素代表一航,该数组中的每个元素代表行里面的咧。二维数组可以模拟现实世界里的表格,数学中的矩阵,棋类游戏的期盼,2d游戏的地图,所以实际开发中应用广泛
以下是用二维数组模拟成绩的例子
let namesArray = ["关羽","张飞","赵云","马超","黄忠"]
let coresArray = ["语文","数学","英语"]
var scoreArray = [[Double]](count:5repeatedValue:[Double](count:3,repeatedValue:0))
for i in 0..
集合
集合在内存里面是离散的,集合中的元素通过计算哈希码或散列码来决定它放在什么位置,集合里面不允许有重复元素。
-创建集合
var set: Set = [1,2,1,2,3,5]
-添加元素
set.insert(100)
-删除元素
set.removeAll()
set.remove(5)//把5删除了
-集合运算(交集,并集,差集)
var set1: Set = [1,2,1,2,3,4,5]
var set2: Set = [1,3,5,7]
set1.intersect(set2) //交集
set1.union(set2) //并集
set1.subtract(set2) //差集
字典
字典是以键值对的方式保存数据的容器,字典中的每个元素都是键值对的组合
-创建字典
let dict: [Int:String] = [
1:"hello"
2:"good"
3:"wonderful"
5:"delicious"
]
-添加元素
dict[4] = "shit" //中括号你的为键
-删除元素
dict[5] = nil
dict.removeValueForKey(5)
-修改元素
dict[3] = "hi"
-字典的遍历
for key in dict.keys{
print("\(key) ---> \(dict[key]!)")
}
for (key,value) in dict.enumerate(){
print("\(index).\(value.0) ---> \(value.1)")
}
for value in dict.values{
print(value)
}
重要操作
-排序
1.sort
2.sortInPlace
说明:排序方法的参数是一个闭包,该闭包的作用是比较数组中两个元素的大小
let array = [23,45,85,45,62,7]
array.sort({(one,two) -> Bool in
return one < two
}) //依次向下化简
array.sort({(one,two) in one < two})
array({one,two in one
处理数据三要素:
- 过滤 (筛选掉条件以外的数据)
let array = [23,25,84,75,12]
let newArray = array.filter{$0 > 50}
- 映射 (通过映射对数据进行变换处理)
let newArray1 = array.map{$0 * $0}
- 归约
let newArray2 = array.reduce(0,combine: +)
函数
函数是独立的课重复使用的功能模块,如果程序中出现了大量重复的代码,通常可以将着部分功能封装成一个地理的函数。在swift中函数是一等公民(函数可以作为类型来使用,函数可以赋值给一个变量和常量,可以讲函数作为函数的参数或者返回值,还可以使用高阶函数)
func 函数名([参数1:类型,参数2:类型])[throws|rethrows] [->返回类型]{
函数的执行体
[return 表达式]//中括号表示可有可无
}
-外部参数名
func myMin(a x: Int, b y: Int) -> Int {
return x < y ? x : y
}
如果不写外部参数名那么内部参数名也是外部参数名
func myMin(x: Int, y: Int) -> Int {
return x < y ? x : y
}
可以使用_来作为外部参数名表示省略外部参数名(注意:下划线要用空格隔开)
func myMin(x: Int, _ y: Int) -> Int {
return x < y ? x : y
}
-inout参数-- 输入输出参数(不仅将数据传入函数还要从函数中取出数据)
func swap(inout a: Int, inout _ b: Int) -> Void {
(a, b) = (b, a)
}
var a = 300, b = 500
swap(&a, &b)
print("a = \(a)")//输出结果为500
print("b = \(b)")//输出结果为300
-可变参数列表(参数的个数是任意多个)
-以下例子实现参数之和
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(sum(90, 82, 37, 68, 55, 11, 99))
闭包
闭包就是没有名字的函数或者称之为函数表达式,oc中与之对应的是block,如国一个函数的参数类型是函数,我们可以传入一个闭包;如果一个函数的返回类型时函数我们就返回一个闭包;如果一个类的某个属性是函数,我们也可以将一个闭包表达式赋值给它
{([参数列表]) [-> 返回类型] in 代码}
面向对象编程(oop)
基本概念
对象:接受消息的单元(对象是具体的)
类:类是抽象的,类是对象的蓝图和模版
消息:对象之间沟通(通信)的手段,我们通过给对象发消息可以让对象执行对应的操作来解决问题。
四大支柱
抽象:定义类的过程就是一个抽象的过程,需要做数据抽象和行为抽象,数据抽象找到对象的属性(保存对象状态的存储属性),行为抽象找到对象的方法(可以给对象发的消息)
封装:
-观点1:我们在类中写方法其实就是在封装API,方法的内部实现可能会很复杂,但是这些对调用来说是不可见的,调用只能看到方法有一个简单的清晰的接口
-观点2:将对象的属性和操作这些属性的方法绑定在一起
-观点3:隐藏一切可以隐藏的实现细节,只提供简单清晰的接口(界面)
继承:从已有的类创建新类的过程
-提供继承信息的称为父类(超类/基类);得到继承信息的称为子类(派生类/衍生类)
-通常子类除了得到父类的继承信息还会增加一些自己特有的东西;所以子类的能力一定比父类更强大
-继承的意义在于子类可以复用父类的代码并且增强系统现有的功能
多态:同样的引用,调用相同的方法,做了不同的事
-实现多态的关键步骤:
- 方法重写(子类在继承父类的过程中对父类已有的方法进行重写, 而且不同的子类给出各自不同的实现版本)
- 对象造型(将子类对象当成父类型来使用)
三个步骤:
1.定义类
-数据抽象
-存储属性--(保存和相应类的相关的数据的属性)
-计算属性--(通过对存储属性做运算得到的属性;通常获得某个计算出的值的方法都可以设计成计算属性)
-行为抽象--找到和相应类的相关的方法(找动词)
-方法(写到类里面的函数就是方法)
-对象方法:给对象发的消息,与对象状态有关,所以先创建对象才能调用
-类方法:给类发的消息,与对象的状态无关的方法,所以不用创建对象直接通过类名调用
class Triangle {
var a: Double
var b: Double
var c: Double
init(a: Double, b: Double, c: Double) {
self.a = a
self.b = b
self.c = c
}
// 类方法(发给类的消息与对象状态无关)
// 此处的static也可以换成class作用相同
static func isValid(a: Double, _ b: Double, _ c: Double) -> Bool {
return a + b > c && b + c > a && c + a > b
}
// 对象方法(发给对象的消息与对象状态有关)
func perimeter() -> Double {
return a + b + c
}
}
let a = 1.0
let b = 2.0
let c = 3.0
// 在创建对象前先调用类方法判定给定的三条边能否构成三角形
// 类方法是发给类的消息所以不用创建对象直接通过类名调用
if Triangle.isValid(a, b, c) {
let t = Triangle(a: a, b: b, c: c)
// 对象方法是发给对象的消息要先创建对象才能调用
print(t.perimeter())
}
else {
print("无法创建三角形")
}
-计算属性
-构造器
-指派构造器(被其他初始化方法调用的初始化方法)
-便利构造器(调用了其他的初始化方法的初始化方法)
-必要构造器(指派构造器前面加上required可以将构造器指定为必要构造器;所谓的必要构造器意味着子类也要提供一模一样的构造器)
2.创建对象
3.给对象发消息
结构
总结类和结构的区别
区别1: 结构的对象是值类型, 类的对象是引用类型
区别2: 结构会自动生成初始化方法
区别3: 结构中的方法在默认情况下是不允许修改结构中的属性除非加上mutating关键字
结论: 我们自定义新类型时优先考虑使用类而不是结构除非我们要定义的是一种底层的数据结构(保存其他数据的类型)
-类扩展(extension)
-运算符重载-(为自定义的类型定义运算符)
-下标运算(subscript)
-访问修饰符
-private(私有的)
-public(公开的)
-internal(内部的)-默认
枚举
-枚举是定义符号常量的最佳方式
-符号常量总是优于字面常量
以下是扑克的四种花色
enum Suite: String {
case Spade = "黑桃"
case Heart = "红心"
case Club = "草花"
case Diamond = "方片"
}
面向协议编程(POP)
协议
protocol 协议名[:父协议1,父协议2...]{
//方法的集合(计算属性相当于就是方法)
}
1.能力-遵循了协议就意味着具备了某种能力
2.约定-遵循了协议就一定要实现协议中的方法
3.角色-一个类可以遵循多个协议, 一个协议可以被多个类遵循, 遵循协议就意味着扮演了某种角色, 遵循多个协议就意味着可以扮演多种角色
依赖倒转原则
设计模式
-代理模式以下例子为懒惰的学生在考试时找人代理其考试
//枪手代理学生考试
protocol ExamCandidate: class {
func answerTheQuestion()
}
class LazyStudent: ExamCandidate {
var name: String
init(name: String) {
self.name = name
}
func answerTheQuestion() {
}
}
class Gunman: ExamCandidate {
var name: String
var target: LazyStudent?
init(name: String) {
self.name = name
}
func answerTheQuestion() {
if let stu = target {
print("姓名: \(stu.name)")
print("奋笔疾书答案")
print("提交试卷")
}
}
}
let stu = LazyStudent(name: "王大锤")
let gun = Gunman(name: "骆昊")
gun.target = stu
gun.answerTheQuestion()
输出结果为:
姓名: 王大锤
奋笔疾书答案
提交试卷
Program ended with exit code: 0
-用协议实现委托回调
一个对象想做一件事自己却做不了酒可以使用委托回调,具体的步骤是:
1.设计一个协议,让被委托方遵循协议并实现协议中的方法
2.委托方有一个属性是协议类型的,通过该属性就可以调用协议中的方法
3.委托方的协议类型的属性通常是可空类型,不要写成weak引用
其他
-协议组合:protocol<协议1,协议2...>
-协议扩展:对协议中的方法给出默认实现
泛型
让类型不再是程序中的硬代码
-泛型函数(定义一个虚拟类型,调用函数时根据传入的参数类型来决定 T 到底是什么)
func mySwap(inout a: T, inout _ b: T) {
let temp = a
a = b
b = temp
}
-泛型类/结构/枚举
Swift中的类、结构和枚举都可以使用泛型
struct Stack {
var data: [T] = []
// 入栈
mutating func push(elem: T) {
data.append(elem)
}
// 出栈
mutating func pop() -> T {
return data.removeLast()
}
var isEmpty: Bool {
get { return data.count == 0 }
}
}
var stack = Stack()
stack.push("hello")
stack.push("good")
stack.push("zoo")
while !stack.isEmpty {
print(stack.pop())
}
相关知识
-泛型限定
func myMin(a: T, _ b: T) -> T {
return a < b ? a : b
}
-where子句
-可空链语法(适用于开火车式的编程)
class Person {
var car: Car?
}
class Car {
var engine: Engine?
}
class Engine {
var id: String?
}
let p = Person()
print(p.car?.engine?.id?.uppercaseString)
错误处理
enum MyError:ErrorType{
case A:
case B:
case C:
}
错误关键字
-throw
-throws/rethrows
-do
-catch
-try
边角知识
-ARC(内存清理)
-正则表达式
-嵌套类型