语言基础
程序是指令的集合,写程序就是写一系列的指令去控制计算机做我们想做的事情。
编译: 将程序设计语言转换成计算机能够理解的机器语言或者某种中间代码的过程。
冯诺依曼体系结构的计算机:
- 使用二进制
- 程序存储执行
变量和常量
定义变量和常量是为了保存数据,变量和常量就是某种类型的值的存储空间。
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有非常强大的类型推断,所以定义变量或常量时,应该直接使用类型推断,不用手动指定类型;
- 如果可以的话应该尽可能使用常量而不是变量。
语言元素
var a: Int = 101
关键字: 有特殊含义的单词
标识符: 给变量、常量、函数、类、结构、协议、枚举、方法、属性等起的名字
命名规则:
1.字母(Unicode字符)、数字、下划线,数字不能开头;
2.大小写敏感(区分大小写);
3.不能使用关键字作标识符;
4.使用驼峰命名法(命名变量、常量、函数、方法、属性,第一个单词小写,从第二个单词开始首字母大写;命名类、结构、协议、枚举每个单词首字母都要大写)
5.见名知意;
6.命名私有的属性和方法时以下划线开头。
运算符: 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.类型字面量: Sting.self、 UILabel.self
分隔符: 将不同的语言元素符号分开
说明:
Swift中每个语句后面分号是可写可不写的,写代码时尽量保证一行只有一条语句这样就可以省略分毫。
分支和循环
分支结构
- if...else...
下面的代码实现了分段函数求值的功能。
let x = 3.2
let y: Double
if x < -1 {
y = 3 * x + 5
}
else if x <= 1 {
y = 5 * x - 3
}
else {
y = 7 * x + 1
}
- switch...case...default
let score = 92.5
let level: String
switch score {
case 0...59:
level = "D"
case 60...70:
level = "C"
case 71...85:
level = "B"
case 85...100:
level = "A"
default:
level = "❌:输入错误"
}
print(level)
循环结构
下面的程序实现了1-100求和。
- while
var i = 1
var sum = 0
while i <= 100 {
sum += i
i += 1
}
print(sum)
- repeat...while...
var i = 1
var sum = 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)")
}
}
}
说明
在循环中可以使用break关键字来提前终止循环,也可以是用continue关键字使循环直接进入下一轮,但是应该尽量避免使用break和continue,因为它们不会让你的程序变得更好。
综合案例: Craps赌博游戏🎲🎮
游戏规则: 玩家摇骰子,如果第一次摇出了7点活着11点,玩家胜;如果摇出了2点,3点或者12点,庄家胜;如果摇出了第一次摇的点数,玩家胜;否则游戏继续直到分出胜负。
func roll() -> Int {
return Int(arc4random_uniform(6)) + 1
}
let firstPoint = roll() + roll()
print("玩家摇出了\(firstPoint)点")
var needsGoOn = false
switch firstPoint {
case 7,11:
print("玩家胜")
needsGoOn = false
case 2,3,12:
print("庄家胜")
needsGoOn = false
default:
print("游戏继续!")
needsGoOn = true
}
while needsGoOn {
let currentPoint = roll() + roll()
print("玩家摇出了\(currentPoint)点")
if currentPoint == 7 {
print("庄家胜")
needsGoOn = false
}
else if currentPoint == firstPoint {
print("玩家胜")
needsGoOn = false
}
}
容器
数组
数组时是用连续的内存空间保存多个同类型的容器,因为数组中的元素在内存中是连续的,所以可以使用下标运算来访问数组中的元素,实现随机存取。
- 创建数组
var array1: [Int] = []
var array2: Array<Int> = []
var array3 = [1,2,3,4,5]
var array4 = [Int](count: 5, repeatedValue: 0)
var array4 = Array[Int](count: 5, repeatedValue: 0)
- 添加元素
array1.append(1)
array1.insert(1, atIndex: 1)
array1.insert(2, atIndex: array1.count)
array1 += [5]
array1 += [4,56,68,86]
- 删除元素
array1.removeAtIndex(2)
array1.removeFirst()
array1.removeLast()
array1.removeFirst(3)
array1.removeAll()
array1.removeRange(1...5)
- 修改元素
array3[0] = 153545
array3[array.count - 1] = 1388
print(array3)
- 遍历数组
方式1:
for i in 0..<array3.count {
print(array3[i])
}
方式2:
for temp in array3[1...3] {
print(temp)
}
说明: for - in 循环是一个只读循环,这也就意味着在循环的过程中不能对数组中的元素进行修改。
方式3:
for (i, temp) in array3.enumerate() {
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 courseArray = ["语文","数学","英语"]
var scoresArray = [[Double]](count: 5, repeatedValue: [Double](count: 3, repeatedValue: 0))
for i in 0..<scoresArray.count {
for j in 0..<scoresArray[i].count {
scoresArray[i][j] = Double(randomInt(0, max: 100))
}
}
for i in 0..<courseArray.count {
for (index, array) in scoresArray.enumerate() {
var sum = 0.0
for score in array {
sum += score
}
let avg = sum / Double(courseArray.count)
print("\(namesArray[index])\(courseArray[i])的平均成绩是: \(avg)")
}
var sum = 0.0
for row in 0..<scoresArray.count {
sum += scoresArray[row][i]
}
let avg = sum / Double(namesArray.count)
print("\(courseArray[i] )的平均成绩是: \(avg)")
}
集合
集合在内存中时离散的,集合中的元素通过计算Hash Code(哈希码或散列码)来决定存放在内存中的什么位置。集合中不允许有重复元素。
- 创建集合
var set: Set<Int> = [2,2,5,0,8,8,156,6]
- 添加和删除元素
set.insert(1)
set.remove(5)
set.removeFirst()
- 集合运算(交集、并集、差集)
ar set1: Set<Int> = [0,1,2,3,4,5,6,7,8,9]
var set2: Set<Int> = [1,3,5,7,9]
set1.intersect(set2)
set1.union(set2)
set1.subtract(set2)
字典
字典是以键值对的方式保存数据的容器,字典中的每个元素都是键值对组合,通过键可以找到对应的值。
- 创建字典
let dict Dictionary<Int, String> = [
1: "Zhang Xiaofan",
2: "Lin Jingyu",
3: "Lu Xueqi",
4: "Tian Ling er",
5: "Bi Yao"
]
- 添加元素、删除元素和修改元素
dict[4] = "Jin Ling er"
//dict.removeValueForKey(5)
dict[3] = "Xiao Hui"
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
说明:排序方法的参数是一个闭包(closure),该闭包的作用是比较数组中两个元素的大小。
let array = [23,1456,465,46,1,143,13,153,135,135,14,354,35,4]
array.sort ({ (one, two) -> Bool in
return one < two
})
array.sort { (one, two) in one < two }
array.sort({ $0 < $1 })
array.sort { $0 < $1 }
array.sort(<)
- 过滤
let array = [23,1456,465,46,1,143,13,153,135,135,14,354,35,4]
// 筛选掉不满足条件的数据
let newArray = array.filter { $0 > 50 }
print(newArray)
- 映射
let array = [23,1456,465,46,1,143,13,153,135,135,14,354,35,4]
// 通过映射对数据进行变换处理
let newArray = array.map { sqrt(Double($0)) }
print(newArray)
- 归约
let array = [23,1456,465,46,1,143,13,153,135,135,14,354,35,4]
let result_ = array.reduce(0, combine: +)
print(result_)
函数和闭包
函数是独立的可重复使用的功能模块,如果程序中出现了大量的重复代码,通常都可以将这部分功能封装成独立的函数。在Swift中,函数是"一等公民",函数作为类型来使用,也就是说函数可以赋值给一个变量或常量,可以将函数作为函数的参数或者返回值,还可以使用高阶函数。
func 函数名([参数名1: 类型, 参数2: 类型, ...]) [throws][rethrows] [-> 返回类型]
{
函数的执行体
[return表达式]
}
- 外部参数名(可省略)
func myMin(a x:Int, b y:Int) -> Int {
return x < y ? x : y
}
// 调用函数的时候要写外部参数名
print(myMin(a: 3,b: 5))
func myMin(x:Int, _ b y:Int) -> Int {
return x < y ? x : y
}
// 调用函数的时候不用写外部参数名
print(myMin(3, 5))
- inout参数
输入输出参数(不仅将数据传入函数还要从函数中取出数据)
func swap(inout a : Int ,inout _ b :Int ) -> Void {
//(a, b) = (b, a)
let temp = a
a = b
b = temp
}
var a = 15343 , b = 1553
// 函数调用传参都是传值
// inout类型的参数前要加上&符号
swap(&a, &b)
- 可变参数列表
Swift中函数的参数列表可以是可变参数列表(参数的个数是任意多个)
func sum(nums: Int...) -> Int {
var total = 0
for num in nums {
total += num
}
return total
}
// 调用的时候:
print(sum())
print(sum(32))
print(sum(1,2,3,4,2,3,486,48,5))
print(sum(1,23,36))
闭包就是没有名字的函数或者称之为函数表达式(Lambda表达式),Objective-C中与之对应的概念叫block。如果一个函数的参数类型是函数,我们可以传入一个闭包;如果一个函数的返回类型是函数我们可以返回一个闭包;如果一个类的某个属性是函数,我们也可以将一个闭包表达式赋值给它。
{ ([参数列表]) [-> 返回类型] in 代码 }
{ (parameters) -> return type in
statements
}
面向对象编程(OOP)
基本概念
对象: 接收消息的单元,对象是一个具体的概念。
类: 对象的蓝图和模板,类是一个抽象概念。
消息: 对象之间通信的方式,通过给对象发消息可以让对象执行对应的操作来解决问题。
四大支柱
抽象: 定义类的过程就是一个抽象的过程,需要做数据臭西那个和行为抽象,数据抽象找到对象的属性(保存对象状态的存储属性),行为对象找到对象的方法(可以给对象发消息)。
封装:
- 观点一: 我们在类中写方法其实就是在封装API,方法的内部实现可能会很复杂,但是这些对调用者来说是不可见的,调用只能看到方法有一个简单清晰的接口。
- 观点二: 将对象的属性和操作这些属性的方法绑定在一起。
- 观点三: 隐藏一切可以隐藏的实现细节,只提供简单清晰的接口(界面)。
继承: 从已有的类继承新的类的过程。
多态: 同样的引用类型,但做了不一样点事情。
重载 - overload
重写 - override
三个步骤
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 && a + c > b
}
// 对象方法(发给对象的消息,与对象状态有关)
func perimeter() -> Double {
return a + b + c
}
}
let a = 5.0
let b = 2.0
let c = 3.0
// 在创建对象钱先调用类方法判定给定的三条边能否构成三角形
// 类方法是发给类的消息,所以不用创建对象直接通过类名调用
if Triangle.isValid(a, b: b, c: c) {
let triangle = Triangle(a: a, b: b, c: c)
// 对象方法是发给对象的消息,要先创建对象才能调用
print(triangle.perimeter())
}
else {
print("GG")
}
相关内容
枚举
结构(体)
总结 类和结构的区别到底有哪些?什么时候应该使用结构?什么时候应该使用类?
扩展(extension)
运算符重载
下标运算(subscript)
-
访问修饰符
- private
- internal
- public
面向协议编程(POP)
协议
protocol 协议名 [:父协议1, 父协议2, ...] {
// 方法的集合(计算属性相当于就是方法)
}
1.能力:
2.约定:
3.角色:
依赖倒转原则
设计模式
- 代理模式
- 用协议实现委托回调
一个对象想做某件事情,但是自身没有能力做这件事情就可以使用委托回调,具体的步骤是:- 设计一个协议,被委托方要遵循协议并实现协议中的方法
- 委托方有一个属性是协议类型的,通过该属性可以调用协议中的方法
注意 委托方的协议类型的属性通常是可空类型,因为要要写成weak引用
其他
- 协议组合: protocol<协议1, 协议2, ...>
- 可选方法: 对于协议中的方法可以选择性实现
- 协议扩展:对协议中的方法给出默认实现
泛型
让类型不再是程序中的硬代码(hard code),可以设计出更通用的代码。
泛型函数
泛型类/结构/枚举
相关知识
- 泛型限定
- where子句
错误处理
enum MyError: ErrorType {
case A
case B
case C
}
- throw
- throws / rethrows
- do
- catch
- try
边角知识
- ARC
- 正则表达式
- 嵌套类型