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(9a86)"
4.字符串字面量:"Hello"、"caf\u{e9}"
5.布尔字面量:true、false
6.空值字面量:nil
7.类型字面量:String.self、UILable.self
分隔符:将不同的语言元素符号分开
说明:Swift中每个语句后面的分号可写可不写的,写代码时尽量保证一行只有一条语句这样就可以省略掉分号。
分支和循环
分支
- if...else...
下面的程序实现了对是否是闰年问题的判断。
let year = 2005
if year % 4 == 0 && year % 100 != 0 || year % 400 == 0{
print("\(year)是闰年")
}
else{
print("\(year)不是闰年")
}
- switch...case...default...
下面的程序实现了将百分制的成绩转换成不同级别。
let score = 85.5
let msg:String
switch score{
case 90...100:msg = "优秀"
case 80..<90:msg = "良好"
case 70..<80:msg = "中等"
case 60..<70:msg = "及格"
default:msg = "不及格"
}
print{msg}
循环
- for
下面的程序实现了1-100的求和。
var sum = 0
for i in 1...100{
sum += i
}
print(sum)
- while
下面的程序实现了1-100的求和。
var sum = 0
var i = 1
while i <= 100{
sum += i
i += 1
}
print(sum)
- repeat...while...
下面的程序实现了1-100的求和。
var sum = 0
var j = 1
repeat{
sum += j
j += 1
}while j <= 100
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)")
}
}
}
说明 在循环中可以使用break关键字来提前终止循环,也可以使用continue关键字使循环直接进入下一轮,但是应该尽量减少对break和continue的使用,因为它们不会让你的程序变得更好。
综合案例:Craps赌博游戏.
游戏规则:玩家摇两颗色子,如果第一次摇出了7点或11点,玩家胜;如果摇出了2点、3点、12点,庄家胜;摇出其它点数游戏继续。在继续的过程中玩家重新摇色子,如果摇出了第一次摇的点数,玩家胜;摇出7点,庄家胜;其它点数游戏继续直到一方获得胜利。
func roll() -> Int{
return Int(arc4random_uniform(6)) + 1
}
let firstPoint = roll() + roll()
print("玩家摇出了\(firstPoint)点")
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)")
if currentPoint == 7{
print("庄家胜")
needsGoOn = false
}
else if currentPoint = firstPoint{
print("玩家胜")
needsGoOn = false
}
else{
needsGoOn = true
}
}
容器
数组
数组是使用连续的内存空间保存多个同类型的元素的容器,因为数组中的元素在内存中是连续的,所以可以使用下标运算来访问数组中的元素,实现随机存取。
- 创建数组
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)
arrar1.removeFirst()
array1.removeFirst(2)
array1.removeLast()
array1.removeRange(1...2)
array1.removeAll()
- 修改元素
array3[0] = 100
array3.[array.count-1] = 500
- 遍历数组
- 方式一
for i in 0..<array3.count{
print(array3[i])
}
- 方式二
for temp in array3{
print(temp)
}
for temp in array3[1...3]{
print(temp)
}
说明for-in循环是一个只读循环,这也就意味着在循环的过程中不能对数组中的元素进行修改
- 方式三
for(i,temp) in array3.enumerate(){
if i == 0{
array[i] = 1
}
print("\(i).\(temp)")
}
提醒:操作数组时最重要的是不要越界访问元素。数组对象的count属性表明了数组中有多少个元素,那么在有效的索引范围是0到count-1
数组中的元素也可以是数组,因此我们可以构造多维数组,最常见的是二维数组,它相当于是一个有行有列的数组,数组中的每个元素代表一行,该数组中的每个元素代表行里面的列。二维数组可以模拟现实世界中的表格、数学上的矩阵、棋类游戏的棋盘、2D游戏中的地图,所以在实际开发中应用广泛。
下面的程序是用二维数组模拟表格的例子。
func randomInt(min:UInt32,max:UInt)->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(50,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..<coursesArray.count{
sum += scoresArray[row][i]
}
let avg = sum/Double(namesArray.count)
print("\(coursesArray[i])课的平均成绩为:\(avg)")
}
集合
集合在内存中是离散的,集合中的元素通过计算Hash Code(哈希码或散列码)来决定存放在内存中的什么位置,集合中不允许有重复元素。
- 创建集合
let set:Set<Int> = [1,2,1,2,3,5,10,200]
- 添加和删除元素
set.insert(100)
set.removeFirst()
set.remove(5)
set.removeAll()
- 集合运算(交集、并集、差集)
var set1:Set<Int> = [1,2,1,2,3,4,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:"wonderful"
5:"delicious"]
- 添加、删除、修改元素
dict[3]= "terrible"
dict[4] = "shit"
dict[5] = nil
- 遍历元素
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)")
}
重要操作
- 排序
1.sort
2.sortInPlace
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(<)
说明:排序方法的参数是一个闭包(closure),该闭包的作用是比较数组中两个元素的大小
- 过滤:筛选掉不满足条件的数据
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{ sqrt(Double($0))}
print(newArray)
- 归约
let array = [23,45,12,89,98,55,7]
let newArray = array.reduce(0,combine:+)
print(newArray)
函数和闭包
函数是独立的可重复使用的功能模块,如果程序中出现了大量的重复代码,通常都可以将这部分功能封装成一个独立的函数。在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
}
- inout参数
inout - 输入输出参数(不仅将数据传入函数还要从函数中取出数据),inout类型的参数前要加上&符号
func createX(inout x: Int) {
x = 1000
}
var x = 1
createX(&x)
print(x)
- 可变参数列表
Swift中函数的参数列表可以是可变参数列表(参数的个数是任意多个)
func sum(nums: Int...) -> Int {
var total = 0
for num in nums {
total += num
}
return total
}
闭包就是没有名字的函数或者称之为函数表达式(Lambda表达式),Objective-C与之对应的概念叫block.如果一个函数的参数类型是函数我们可以传入一个闭包;如果一个函数的返回类型是函数我们可以返回一个闭包;如果一个类的某种属性是函数我们也可以将一个闭包表达式赋值给它。
{ ([参数列表]) [->返回类型] in 代码 }
面向对象编程(OOP)
基本概念
-
对象:接收消息的单元。
-
类:对象的蓝图和模板,类是一个抽象概念。
-
消息:对象之间通信的方式,通过给对象发消息可以让对象执行对应的操作来解决问题
四大支柱
-
抽象:定义类的过程就是一个抽象的过程,需要做数据抽象和行为抽象,数据抽象找到对象的属性(保存对象状态的存储属性),行为抽象找到对象的方法(可以给对象发的消息)。
-
封装:
观点一:我们在类中写的方法其实就是在封装API,方法的内部实现可能会很复杂,但是这些对调用者来说是不可见的,调用只能看到方法有一个简单清晰的接口,这就是封装。
观点二:将对象的属性和操作这些属性的方法绑定在一起。
观点三:隐藏一切可以隐藏的实现细节,只提供简单清晰的编程口(界面)。 -
继承:从已有的类创建新类的过程,提供继承信息的称为父类(超类/基类,得到继承信息的称为子类(派生类/衍生类)。通常子类除了得到父类的继承信息还会增加一些自己特有的东西,所以子类的能力一定比父类更强大。继承的意义在于子类可以复用父类的代码并且增强系统现有的功能。
-
多态:同样的引用调用相同的方法但是做了不同的事情。
三个步骤
1.定义类
- 数据抽象
- 存储属性:类自身具有的属性(找名词)
- 行为抽象
- 方法:写到类里面的函数或者说跟对象绑定的行为就是方法
- 对象方法:给对象发的消息
- 类方法:给类发的消息,与对象的状态无关的方法
- 计算属性:通过对存储属性做运算得到的属性,通常获得某个计算出的值的方法都可以设计成计算属性。
- 方法:写到类里面的函数或者说跟对象绑定的行为就是方法
- 构造器
- 指派构造器:被其他初始化方法调用的初始化方法。
- 便利构造器(convenience):调用了其他的初始化方法的初始化方法。
- 必要构造器(required):必要构造器意味着子类也要提供一模一样的构造器.
2.创建对象
3.给对象发消息
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("无法创建三角形")
}
相关内容
- 枚举:枚举是定义符号常量的最佳方式, 符号常量总是优于字面常量。
- 结构(体)
总结:类和结构的区别,什么时候使用类,什么时候使用结构
区别1: 结构的对象是值类型, 类的对象是引用类型,值类型在赋值的时候会在内存中进行对象的拷贝,引用类型在赋值的时候不会进行对象拷贝只是增加了一个引用.
区别2: 结构会自动生成初始化方法
区别3: 结构中的方法在默认情况下是不允许修改结构中的属性除非加上mutating关键字.
结论: 我们自定义新类型时优先考虑使用类而不是结构除非我们要定义的是一种底层的数据结构(保存其他数据的类型)
- 扩展(extension)
如果在某个特定的应用场景中你发现现有的类缺少了某项功能,那么可以通过类扩展(extension)的方式现场添加这项功能
- 运算符重载:为自定义的类型定义运算符
class Student{
var name:String
init(name:String){
self.name = name
}
}
func <(one: Student, two: Student) -> Bool {
return one.name < two.name
}
-
下标运算(subscript)
-
访问修饰符
- private(私有的):存储属性通常是private的,因为数据要保护起来。
- internal(内部的):如果自定义的类没有打算在其他项目中使用,可以不写访问修饰符。直接使用默认的internal修饰符表示在本项目中公开对其他项目私有。
- public(公共的):方法一般是public的,因为方法是对象接受的消息。
面向协议编程(POP)
协议
protocal 协议名[父协议1,父协议二...]{
//方法的集合(计算属性相当于就是方法)
}
协议是方法的集合(计算属性相当于就是方法),可以把看似不相关的对象的公共行为放到一个协议中。协议在Swift开发中大致有三种作用:
1.能力:遵循了协议就意味着具备了某种能力
2.约定:遵循了协议就一定要实现协议中的方法
3.角色:一个类可以遵循多个协议, 一个协议可以被多个类遵循, 遵循协议就意味着扮演了某种角色, 遵循多个协议就意味着可以扮演多种角色
依赖倒转原则
用协议实现委托回调
一个对象想做某件事情但是自身没有能力做这件事就可以使用委托回调,具体步骤是:
1.设计协议,让被委托方遵循协议并实现协议中的方法
2.委托方有一个属性是协议类型的,通过该属性可以调用协议中的方法
注意:委托方的协议类型的属性通常是可空类型,要写成弱引用(weak)
其他
- 协议组合:protocal<协议1,协议2,...>
- 可选方法
- 协议扩展:对协议中的方法给出默认实现,可以设计出更通用的代码
泛型
让类型不再是程序中的硬代码
- 泛型函数
- 泛型类/结构/枚举
定义一个虚拟类型T, 调用函数时根据传入的参数类型来决定T到底是什么
func mySwap<T>(inout a: T, inout _ b: T) {
let temp = a
a = b
b = temp
}
注意:Swift中的类、结构和枚举都可以使用泛型.
相关知识
- 泛型限定:<T: Comparable>限定T类型必须是遵循了Comparable协议的类型
- where字句
错误处理
enum MyError:ErrorType{
case A
case B
case C
}
- throw:
- throws/rethrows:
- do
- catch
- try:
边角知识
- ARC
- 正则表达式
- 嵌套类型
网友评论