Swift的知识点总结
Swift学习总结
语言基础
程序是指令的集合,写程序就是写一系列的指令去控制计算机做我们想做的事情。
编译:将程序设计语言转换成计算机能够理解的机器语言或者某种中间代码的过程。
冯诺依曼体系结构的计算机:
1.使用二进制
2.程序存储执行
常量和变量
定义变量和常量是为了保存数据。变量和常量就是某种类型的值的存储空间。
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
说明:
1.Swift有非常强大的类型推断,
所以定义变量或常量时如果可以的话应该
直接使用类型推断不用手动指定类型;
2.如果可以的话应该尽可能使用常量而不是变量。
语言元素
var a:Int = 10
关键字:有特殊含义的单词
标识符:你给变量、常量、函数、类、结构、协议、方法、属性等起的名字
- 字母、数字、下划线构成,数字不能开头
- 大小写敏感(区分大小写)
- 不能使用关键字做标识符
- 使用驼峰命名法(命名变量、常量、函数、方法、属性第一个单词小写,从第二个单词开始每个首字母大写;命名类、结构,、协议、枚举每个单词首字母都要大写)
- 见名知意
- 命名私有的属性和方法时以下划线开头
运算符:Swift中的运算符其实都是函数
1.赋值运算符:=、+=、-=、....
2.算术运算符:+、-、*、/、%
3.比较运算符:==、!=、<、<=、>、>=
4.逻辑运算符:&&、||、!
5.条件运算符:?:
6.其他运算符:[]、.、??、?、!
字面(常)量:
1.整数字面量:10 、1_234_567、0x10、 0o10、0b10
2.小数字面量: 123.45、1.2345e2、0xab.cdp2
3.字符字面量:"c"、"\n"、"\u{41}"、"\u{9}"
4.字符串字面量:"Hello"、"caf\u{e9}"
5.布尔字面量:true、false
6.空值字面量:nil
7.类型字面量:String.self、UILable.self
分隔符:将不同的语言元素符号分开
说明:
Swift中每个语句后面的分号是可写可不写的,写代码时尽量保证一行只有一条语句这样就可以省略掉分号。
分支和循环
分支
- if...else...
下面的程序是判断闰年的
print("请输入年份:",terminator:"")
let year = inputInt()
if year % 4 == 0 && year % 100 != 0 && year % 400 == 0 {
print("\(year)是闰年")
}
else {
print("\(year)不是闰年")
}
- switch...case...default
let score = 92.5
let level:String
switch score {
case 0..<60:
level = "E"
case 60..<70:
level = "D"
case 70..<80:
level = "C"
case 80..<90:
level = "B"
case 90...100:
level = "A"
default:
level = "输入错误"
}
print(level)
循环
以求1+2+...+100的和为例。
- while
var sum = 0
var i = 0
while i <= 100 {
sum += i
i += 1
}
print(sum)
- repeat...while
var sum = 0
var i = 0
repeat {
sum += i
i += 1
}while i <=100
print(sum)
- for
var sum = 0
for i in 1...100{
sum += i
}
}
print(sum)
穷举法:穷尽所有可能性知道找到正确定答案
下面的程序实现了"百钱百鸡"问题的求解
for x in 0...20{
for y in 0...33{
let z = 100 - x - y
if 5 * x + 3 * y + z / 3 == 100 && z % 3 == 0{
print("\(x)只公鸡,\(y)只母鸡,\(z)只小鸡")
}
}
}
//0只公鸡,25只母鸡,75只小鸡
//4只公鸡,18只母鸡,78只小鸡
//8只公鸡,11只母鸡,81只小鸡
//12只公鸡,4只母鸡,84只小鸡
说明:
在循环中可以使用break关键字来提前终止循环,也可以使用continue关键字使循环直接进入下一轮,但是应该尽量减少对break和continue的使用,因为它们不会让你的程序变得更好。
综合案例: Craps赌博游戏。
游戏规则: 玩家摇两颗色子,如果第一次摇出了7点或11点,玩家胜;如果摇出2、3、12点,庄家胜;其他的点数游戏继续。在继续的过程 中玩家重新摇色子,如果摇出了七点,庄家胜;如果摇出了与第一次一样的点数,玩家胜;否则游戏继续。
func roll() -> Int {
return Int(arc4random_uniform(6)) + 1
}
var money = 10000
repeat {
print("玩家总资产:\(money)")
var debt:Int
repeat {
print("请下注:",terminator:"")
debt = inputInt()
}while debt <= 0 || debt > money
var needsGoOn = false
let firstPoint = roll() + roll()
print("玩家摇出了\(firstPoint)点")
switch firstPoint {
case 7,11:
print("玩家胜")
money += debt
case 2,3,12:
print("庄家胜")
money -= debt
default:
needsGoOn = true
}
while needsGoOn {
let currentPoint = roll() + roll()
print("玩家摇出了\(currentPoint)点")
if currentPoint == 7{
print("庄家胜!")
money -= debt
needsGoOn = false
}
else if currentPoint == firstPoint {
print("玩家胜!")
money += debt
needsGoOn = false
}
}
}while money > 0
print("你破产了!!!")
容器
数组
数组时使用连续的内存空间保存多个同类型的元素的容器,因为数组中的元素在内存中是连续的,所以可以使用下标运算来访问数组中的元素,实现随机存取。
- 创建数组
var array1: [Int] = []
var array2: Array<Int> = []
var array3 = [1,2,3,4,5]
var array4 = [Int](count:5,repeatedValue: 0)
var array5 = Array<Int>(count:5,repeatedValue: 0)
- 添加元素
array1.append(2)
array1.append(3)
array1.insert(1, atIndex: 0)
array1.insert(4, atIndex: array1.count)
array1 += [5]
array1 += [6,7,8]
- 删除元素
array1.removeAtIndex(2)
array1.removeFirst()
array1.removeFirst(2)
array1.removeLast()
array1.removeRange(1...2)
array1.removeAll()
- 修改元素
array3[0] = 100
array3[array3.count - 1] = 500
print(array3)
- 遍历数组
1.方式1
for i in 0...array3.count {
print(array3[i])
}
2.方式2
for temp in array3 {
print(temp)
}
说明:for-in循环是一个只读循环,这也意味着在循环的过程中不能对循环进行修改。
3.方式3
for (i,temp) in array3.enumerate() {
if i == 0 {
array3[i] = 1
}
print("\(i),\(temp)")
}
提醒:
操作数组时最重要的是不要越界访问元素。数组对象的count属性表明了数组中有多少个元素,那么有效的索引(下标)范围是0到count-1.
数组中的元素也可以是数组,因此我们可以构造多位数组。最常见的是二维数组,它相当于是一个有行有列的数组,数组中的每个元素代表一行该数组中的每个元素代表行中的列,二维数组可以模拟现实世界中的表格、数学上的矩阵、棋类游戏的棋盘、各类2D游戏的地图,所以在实际开发中使用非常广泛。
下面的程序使用二维数组模拟表格的例子
func randomInt(min: UInt32, max: UInt32) -> Int {
return Int(arc4random_uniform(max - min + 1) + min)
}
let namesArray = ["关羽","张飞","赵云","马超","黄忠"]
let coursesArray = ["语文","数学","英语"]
var scoresArray = [[Double]](count: namesArray.count, repeatedValue: [Double](count: coursesArray.count, repeatedValue: 0))
for i in 0..<scoresArray.count {
for j in 0..<scoresArray[i].count {
scoresArray[i][j] = Double(randomInt(0,max: 100))
}
}
for (index, array) in scoresArray.enumerate() {
var sum = 0.0
for score in array {
sum += score
}
let avg = sum / Double(coursesArray.count)
print("\(namesArray[index])的平均成绩为:\(avg)")
}
for i in 0..<coursesArray.count {
var sum = 0.0
for row in 0..<scoresArray.count {
sum += scoresArray[row][i]
}
let avg = sum / Double (namesArray.count)
print("\(coursesArray[i])的平均成绩为:\(avg)分")
}
集合
集合在内存中是离散的,集合中的元素通过计算Hash Code(哈希码或散列码)来决定存放在内存中的什么位置,集合中不允许有重复元素。
- 创建集合
var set: Set<Int> = [1,2,1,2,3,5]
- 添加元素
set.insert(100)
- 删除元素
set.remove(5)
set.removeFirst()
set.removeAll()
- 集合遍历
for temp in set {
print(temp)
}
- 集合运算(交集、并集、差集)
var set1: Set<Int> = [1, 2, 1, 2, 3, 5]
var set2: Set<Int> = [1, 3, 5, 7]
set1.intersect(set2)
set1.union(set2)
set1.subtract(set2)
字典
字典是以键值对的方式保存数据的容器,字典中的每个元素都是键值对组合,通过键可以找到对应的值。
- 创建字典
var dict: [Int: String] = [
1: "hello",
2: "good",
3: "nima",
5: "vaafa"
]
- 添加、修改、删除元素
print(dict[1]!)
if let str = dict[1] {
print(str)
}
else {
print("找不到")
}
dict[3] = "bfafafv,xmv"
dict[4] = "shit"
dict[5] = nil
dict.count
- 遍历元素
for key in dict.keys {
print("\(key)----> \(dict[key]!)")
}
for value in dict.values {
print(value)
}
for (index,value) in dict.enumerate() {
print("\(index).\(value.0) ----> \(value.1)")
}
重要操作
- 排序
- sort
let a = [1,3,2,5,7,4,9,8,6]
let b = a.sort(<)
print(b)
- sortInPlace
var a = [1,3,2,5,7,4,9,8,6]
a.sortInPlace(<)
print(a)
说明:
排序方法的参数是一个闭包(closure),该闭包的作用是比较数组中的两个元素的大小。
let array = [23,45,12,89,98,55,7]
array.sort({(one: Int, two: Int) -> Bool in
return one < two
})
array.sort({(one, two) in one < two })
array.sort({one, two in one < two })
array.sort({ $0 < $1 })
array.sort { $0 < $1 }
array.sort(<)
- 过滤
let array = [23,45,12,89,98,55,7]
//筛选掉不满足条件的数据
let newArray = array.filter { $0 > 50}
print(newArray) //[89, 98, 55]
- 映射
let array = [23,45,12,89,98,55,7]
//通过映射对数据进行变换处理
let newArray = array.map{ $0 % 10 }
print(newArray)
- 归约
let array = [23,45,12,89,98,55,7]
let newArray1 = array.filter { $0 > 50}
print(newArray1)
let newArray2 = array.map{ $0 % 10 }
print(newArray2)
let result = newArray2.reduce(0, combine: +)
print(result)
函数和闭包
函数 是独立的可重复的功能模块,如果程序中出现了大量的重复代码,通常都可以将这部分功能封装成一个独立的函数。在swift中,函数是"一等公民"可以作为类型来使用,也就是说函数可以赋值给一个变量或常量,可以将函数作为函数的参数或返回值,还可以使用高阶函数。
func 函数名([参数1: 类型,参数2: 类型, ...]) [throws|rethrows] [->返回类型] {
函数的执行体
[return 表达式]
}
- 外部参数名
//调用函数的时候要写函数的外部参数名
print(myMin(a: 3,b: 5))
- inout参数
//inout - 输入输出参数(不仅将数据传入函数还要从函数中取出数据)
func createX(inout x: Int) {
x = 1000
}
var x = 1
createX(&x)
print(x)
- 可变参数列表(参数可以是任意多个)
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))
闭包 就是没有名字的函数或者称之为函数表达式(lambda表达式),Objective-C中与之对应的概念叫block。如果一个函数的参数类型是函数我们可以传入一个闭包;如果一个函数的返回类型时函数我们可以返回一个闭包;如果一个类的某个属性是函数我们也可以讲一个闭包表达式赋值给它。
{ ([参数列表]) [-> 返回类型] in 代码}
面向对象编程(OOP)
基本概念
对象:接受消息的单元,对象是一个具体的概念
类:对象的蓝图和模版,类是一个抽象概念
消息:对象之间通信的方式,通过给对象发消息可以让对象执行对应的操作来解决问题。
四大支柱
抽象:
- 定义类的过程就是一个抽象的过程,需要做数据抽象和行为抽象,数据抽象找到对象的属性(保存对象状态的存储属性),行为抽象找到对象的方法(可以给对象发的消息)。
封装:
- 观点1: 我们在类中写方法其实就是在封装API,方法的内部实现可能会很复杂,但是这是对调用者来说是不可见的,调用只能看到方法有一个简单清晰的接口。
- 观点2: 将对象的属性和操作这些属性的方法绑定在一起。
- 观点3: 隐藏一切可以隐藏的实现细节,只提供简单清晰的接口(界面)。
继承:
- 从已有的类创建新的新类的过程
提供继承信息的称为父类(超类/基类)
得到继承信息的称为子类(派生类/衍生类)
多态:
- 同样的对象类型(pet类型)
接收相同的消息(调用相同的方法)
但是做了不同的事情
这就是多态(polymorphism) - 实现多态的关键步骤:
- 方法重写(子类在继承父类的过程中
对父类已有的方法进行重写,
而且不同的子类给出各自不同的实现版本)
- 方法重写(子类在继承父类的过程中
- 对象造型(将子类对象当成父类型来使用)
三个步骤
1.定义类
- 数据抽象
- 存储属性
- 行为抽象
- 方法:
- 对象方法
- 类方法
- 计算属性
- 方法:
- 构造器
- 指派构造器
- 便利构造器
- 必要构造器
2.创建对象
3.给对象发消息
class Triangel {
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
}
//这个方法是发给三角形这个类的,不是三角形这个对象的。就像是三角形自带属性,不传入abc无法对这个类实现,
// 类方法(发给类的消息与对象状态无关)
// 此处的static也可以换成class作用相同
static func isValid(a:Double,_ b:Double,_ c:Double) -> Bool {
return a + b > c && a + c > b && b + c > a
}
// 对象方法(发给对象的消息与对象状态有关)
func perimeter() -> Double{
return a + b + c
}
}
// 在创建对象前先调用类方法
let a = 3.0
let b = 3.0
let c = 3.0
// 在创建对象前先调用类方法给定的三条边能否构成三角形
// 类方法是发给类的消息所以不用创建对象直接通过类名调用
if Triangel.isValid(a, b, c) {
let t = Triangel(a: a, b: b, c: c)
// 对象方法是发给对象的消息要先创建对象才能用
print(t.perimeter())
}
else {
print("无法创建三角形")
}
相关内容
- 枚举
GET:枚举是定义符号常量的最佳方式
GET:符号常量总是优于字面常量
- 结构(体)
总结: 类和结构的区别有哪些?什么时候用结构什么时候用类(绝大时候用类,如果定义底层结构才使用结构)
区别1: 结构的对象是值类型, 类的对象是引用类型
值类型在赋值的时候会在内存中进行对象的拷贝
引用类型在赋值的时候不会进行对象拷贝只是增加了一个引用
结论: 我们自定义新类型时优先考虑使用类而不是结构
除非我们要定义的是一种底层的数据结构(保存其他数据的类型)
// 类
class Student1 {
var name: String
var age: Int
var tel: String?
init(name: String, age: Int) {
self.name = name
self.age = age
}
func getOlder() {
age += 1
}
func study(courseName: String) {
print("\(name)正在学习.")
}
}
//结构体
struct Student2 {
var name: String
var age: Int
func study(courseName: String) {
print("\(name)正在学习.")
}
// 区别3: 结构中的方法在默认情况下是不允许修改结构中的属性除非加上mutating关键字
mutating func getOlder() {
age += 1
}
}
引用类型的类
let stu1 = Student1(name: "阿郎", age: 35)
var stu3 = stu1 // 此处内存中仍然只有一个学生对象
stu3.name = "罗小号"
stu3.age = 18
print(stu1.name)
print(stu1.age)
值类型的结构
区别2: 结构会自动生成初始化方法
let stu2 = Student2(name: "阿郎", age: 35)
var stu4 = stu2 // 此处内存中会复制一个新的学生对象
stu4.name = "王大锤"
stu4.age = 18
print(stu2.name)
print(stu2.age)
区别2: 结构会自动生成初始化方法
区别3: 结构中的方法在默认情况下是不允许修改结构中的属性
除非加上mutating关键字
- 扩展(extension)
如果在某个特定的应用场景中你发现现有的类缺少某项功能
那么可以通过类扩展(extension)的方式现场添加这项功能
- 运算符重载
class Fraction {
private var _num:Int //分子
private var _den:Int //分母
var info:String {
get {
return _num == 0 || _den == 1 ? "\(_num)" : "\(_num)/\(_den)"
}
}
init(num: Int, den: Int) {
_num = num
_den = den
}
// 加
func add(other:Fraction) -> Fraction {
return Fraction(num: _num * other._den + other._num * _den, den: _den * other._den).simplify().normalize()
}
// 减
func sub(other:Fraction) -> Fraction {
return Fraction(num: _num * other._den - other._num * _den, den: _den * other._den).simplify().normalize()
}
// 乘
func mul(other:Fraction) -> Fraction {
return Fraction(num: _num * other._num, den: _den * other._den).simplify().normalize()
}
// 除
func div(other:Fraction) -> Fraction {
return Fraction(num: _num * other._den, den: _den * other._num).simplify().normalize()
}
//将负号放到分子上
func normalize() -> Fraction {
if _den < 0 {
_num = -_num
_den = -_den
}
return self
}
// 化简
func simplify() -> Fraction {
if _num == 0 {
_den = 1
}
else {
let x = abs(_num)
let y = abs(_den)
let g = gcd(x, y)
_num /= g
_den /= g
}
return self
}
}
//运算符重载(为自定义的类型定义运算符)
func +(one:Fraction, two:Fraction) -> Fraction {
return one.add(two)
}
func -(one:Fraction, two:Fraction) -> Fraction {
return one.sub(two)
}
func *(one:Fraction, two:Fraction) -> Fraction {
return one.mul(two)
}
func /(one:Fraction, two:Fraction) -> Fraction {
return one.div(two)
}
- 下标运算(subscript)
- 访问修饰符
- private
- internal
- public
存储属性通常是Private的,因为数据要保护起来
方法一般是Public的 因为方法是对象接受的消息
如果自定义的类没有打算在其他项目中使用 可以不写访问修饰符
直接使用默认的internal修饰符表示在本项目中公开对其他项目私有
面向协议编程(POP)
协议中全是抽象概念(只有声明没有实现)
遵循协议的类可以各自对协议中的计算属性和方法给出自己的实现版本,
这样当我们面对协议编程的时候就可以把多态的优势发挥到淋漓尽致
可以写出更通用更灵活的代码(符合开闭原则)
协议
是方法的集合(计算属性相当于就是方法)
可以把看似不相关的对象的公共行为放到一个协议中
protocol 协议名[:父协议1,父协议2, ...] {
//方法的集合(计算属性相当于就是方法)
}
协议在Swift开发中大致有三种作用:
- 能力 - 遵循了协议就意味着具备了某种能力
- 约定 - 遵循了协议就一定要实现协议中的方法
- 角色 - 一个类可以遵循多个协议,
一个协议可以被多个类遵循,
遵循协议就意味着扮演了某种角色,
遵循多个协议就意味着可以扮演多种角色
依赖倒转原则
说明:
- 声明变量的类型时应该尽可能使用协议类型
- 声明方法参数类型时应该尽可能使用协议类型
- 声明方法返回类型时应该尽可能使用协议类型
用协议实现委托回调
一个对象想做某件事情但是自身没有能力去做这件事情就可以使用委托回调,具体的步骤是:
- 设计协议,让被委托方要遵循协议并实现协议中的方法
- 委托方有个属性是协议类型的,通过该属性可以调用协议中的方法
注意: 委托方的协议类型属性通常是可空类型,因为要写出weak引用
其他
-
协议组合:protocol<协议1,协议2,...>
-
可选方法
-
协议扩展:对协议中的方法给出默认实现
设计模式
- 代理模式
- 委托回调
- 设计一个协议,让被委托方遵循协议并实现协议中的方法
- 委托方有一个属性是协议类型的,通过该属性可以调用协议中的方法
- 委托方的协议类型属性通常是可空类型,因为要写成weak(弱)引用。
其他
- 协议组合: protocol<协议1,协议2,...>
- 可选方法
- 协议扩展:对协议中的方法给出默认实现
泛型
让类型不再是程序中的硬代码(hard code)。
- 泛型函数
- 泛型类/结构/枚举
相关知识
- 泛型限定
- where子句
错误处理
enum MyError: ErrorType {
case A
case B
case c
}
- throw
- throws/ rethrows
- do
- catch
- try
边角知识
- ARC
- 正则表达式
- 嵌套类型