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
說明:1. Swift有非常強大的類型推斷,所以定義變量或常量時如果可以的話應該直接使用類型推斷不用手動指定類型;2. 如果可以的話應該盡可能使用常量而不是變量。
語言元素
var a: Int = 10
關鍵字:有特殊含義的單詞
標識符:給變量、常量、函數、類、結構、協議、枚舉、方法、屬性等起的名字
- 字母(Unicode字符)、數字、下劃線,數字不能開頭
- 大小寫敏感(區分大小寫)
- 不能使用關鍵字做標識符
- 使用駝峰命名法(命名變量、常量、函數、方法、屬性第一個單詞小寫,從第二個單詞開始每個單詞首字母大寫;命名類、結構、協議、枚舉每個單詞首字母都要大寫)
- 見名知意
- 命名私有的屬性和方法時以下劃線開頭
運算符:Swift中的運算符其實都是函數
- 賦值運算符:=、+=、-=、……
- 算術運算符:+、-、*、/、%
- 比較運算符:==、!=、<、<=、>、>=
- 邏輯運算符:&&、||、!
- 條件運算符:?:
- 其他運算符:[]、.、??、?、!
字面(常)量:
- 整數字面量:10、1_234_567、0x10、0o10、0b10
- 小數字面量:123.45、1.2345e2、0xab.cdp2
- 字符字面量:"c"、"\n"、"\u{41}"、"\u{9a86}"
- 字符串字面量:"Hello"、"caf\u{e9}"
- 布爾字面量:true、false
- 空值字面量:nil
- 類型字面量:String.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
}
print("f(\(x))=\(y)")
- switch...case...default
下面的程序實現了將百分制的成績轉換成A-E的等級。
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)
循環
- 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 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 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
}
}
容器
數組
數組是使用連續的內存空間保存多個同類型的元素的容器,因為數組中的元素在內存中是連續的,所以可以使用下標運算來訪問數組中的元素,實現隨機存取。
- 創建數組
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
- 遍歷數組
- 方式1
for i in 0..<array3.count {
print(array3[i])
}
- 方式2
for temp in array3 {
print(temp)
}
for temp in array3[1...3] {
print(temp)
}
說明:for-in循環是一個只讀循環,這也就意味著再循環的過程中不能對數組中的元素進行修改
- 方式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(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..<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()
- 集合運算(交集、并集、差集)
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 = [
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)")
}
重要操作
- 排序
- sort
- sortInPlace
說明:排序方法的參數是一個閉包(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 result = array.reduce(0, combine: +)
print(result)
函數和閉包
函數是獨立的可重復使用的功能模塊,如果程序中出現了大量的重復代碼,通常都可以將這部分功能封裝成一個獨立的函數。在Swift中,函數是"一等公民",可以作為類型來使用,也就是說函數可以賦值給一個變量或常量,可以將函數作為函數的參數或者返回值,還可以使用高階函數。
func 函數名([參數1: 類型, 參數2: 類型, ...]) [throws|rethrows] [-> 返回類型] {
函數的執行體
[return 表達式]
}
- 外部參數名
func foo(a a: Int,b: Int){
a + b
}
foo(a: 10,b: 11)
- inout參數
func sum(inout a: Int, inout _ b: Int) {
a + b
}
- 可變參數列表
func m(a: Int...) -> Int{
return a[0]
}
print(m(1,2,3))
閉包就是沒有名字的函數(匿名函數)或者稱之為函數表達式(Lambda表達式),Objective-C中與之對應的概念叫block。如果一個函數的參數類型是函數我們可以傳入一個閉包;如果一個函數的返回類型是函數我們可以返回一個閉包;如果一個類的某個屬性是函數我們也可以將一個閉包表達式賦值給它。
{ ([參數列表]) [-> 返回類型] in 代碼 }
面向對象編程(OOP)
基本概念
對象:接收消息的單元,對象是一個具體的概念。
類:對象的藍圖和模板,類是一個抽象概念。
消息:對象之間通信的方式,通過給對象發消息可以讓對象執行對應的操作來解決問題。
四大支柱
抽象:定義類的過程就是一個抽象的過程,需要做數據抽象和行為抽象,數據抽象找到對象的屬性(保存對象狀態的存儲屬性),行為抽象找到對象的方法(可以給對象發的消息)。
封裝:
- 觀點1: 我們在類中寫方法其實就是在封裝API,方法的內部實現可能會很復雜,但是這些對調用這來說是不可見的,調用只能看到方法有一個簡單清晰的接口。
- 觀點2: 將對象的屬性和操作這些屬性的方法綁定在一起。
- 觀點3: 隱藏一切可以隱藏的實現細節,只提供簡單清晰的接口(界面)。
繼承:指一個對象直接使用另一對象的屬性和方法
多態:在面向對象語言中,接口的多種不同的實現方式即為多態。引用Charlie Calverts對多態的描述——多態性是允許你將父對象設置成為一個或更多的他的子對象相等的技術,賦值之后,父對象就可以根據當前賦值給它的子對象的特性以不同的方式運作(摘自“Delphi4 編程技術內幕”)。
三個步驟
- 定義類
- 數據抽象
- 存儲屬性
- 行為抽象
- 方法(寫到類里面的函數或者說跟對象綁定的行為就是方法)
- 對象方法:給對象發的消息
- 類方法:給類發的消息,與對象的狀態無關的方法
- 計算屬性
- 方法(寫到類里面的函數或者說跟對象綁定的行為就是方法)
- 構造器
- 指派構造器
- 便利構造器(convenience)
- 必要構造器(required)
- 創建對象
- 給對象發消息
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: 結構的對象是值類型, 類的對象是引用類型
// 值類型在賦值的時候會在內存中進行對象的拷貝
// 引用類型在賦值的時候不會進行對象拷貝只是增加了一個引用
// 結論: 我們自定義新類型時優先考慮使用類而不是結構除非我們要定義的是一種底層的數據結構(保存其他數據的類型)
// 引用類型的類
//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)
// 在Swift中同名函數只要參數列表不同是可以共存的 這個叫函數的重載
func changeName(inout name: String) {
name = "王大錘"
}
// 參數前面加var的做法在Swift 3中肯定是要廢掉的
func changeName(var stu: Student2) {
stu.name = "王大錘"
}
// 計算機的硬件由五大部件構成:
// 運算器、控制器、存儲器、輸入設備、輸出設備
// 運算器 + 控制器 => CPU (中央處理器)
// 存儲器 => 內存 (RAM - Random Access Memory)
// 程序員可以使用的內存大致分為五塊區域:
// 棧 (stack) - 我們定義的局部變量/臨時變量都是放在棧上
// - 特點: 小、快
// 堆 (heap) - 我們創建的對象都是放在堆上的
// - 特點: 大、慢
// 靜態區 (static area)
// - 數據段 - 全局量
// - 只讀數據段 - 常量
// - 代碼段 - 函數和方法
- 擴展(extension)
class Name {
func a() {
}
}
extension Name {
func c () {
}
}
let a = Name()
a.c()
- 運算符重載
func *(one: Fraction,two: Fraction) -> Fraction {
return one.cheng(two)
}
下標運算(subscript)
-
訪問修飾符
- private
- internal
- public
面向協議編程(POP)
協議
protocol 協議名[: 父協議1, 父協議2, ...] {
// 方法的集合(計算屬性相當于就是方法)
}
- 能力:
- 約定:
- 角色:
總結// 少用繼承多用協議
// 協議是方法的集合(計算屬性相當于就是方法)
// 可以把看似不相關的對象的公共行為放到一個協議中
// 協議在swift開發中大致有三種作用
// 1. 能力 - 遵循了協議就意味著具備了某種能力
// 2. 約定 - 遵循了協議就一定要實現協議中的方法
// 3. 角色 - 一個類可以遵循多個協議,一個協議可以被多個類遵循,遵循協議就意味著扮演了某種角色,遵循多個協議就意味著可以扮演多種角色
// swift中的繼承是單一繼承(一個類只能有一個父類),如果希望讓一個類具備多重能力可以使用協議來實現(c++里面是通過多重繼承來實現的,這是一種非常狗血的做法)
依賴倒轉原則
- // 依賴倒轉原則(面向協議編程)
// 1. 聲明變量的類型時應該盡可能使用協議類型
// 2. 聲明方法參數類型時應該盡可能使用協議類型
// 3. 聲明方法返回類型時應該盡可能使用協議類型 - 依賴倒置原則(Dependence Inversion Principle)是程序要依賴于抽象接口,不要依賴于具體實現。簡單的說就是要求對抽象進行編程,不要對實現進行編程,這樣就降低了客戶與實現模塊間的耦合。
用協議實現委托回調
一個對象想做某件事情但是自身沒有能力做這件事情就可以使用委托回調,具體的步驟是:
- 設計一個協議,讓被委托方遵循協議并實現協議中的方法
- 委托方有一個屬性是協議類型的,通過該屬性可以調用協議中的方法
注意:委托方的協議類型的屬性通常是可空類型,因為要寫成弱引用(weak)。
其他
協議組合:protocol<協議1, 協議2, ...>
1.// 協議的組合
let array: [protocol <Flyable,Fightable>] = [
// Bird(),
Superman()
// Airplane(),
]可選方法
協議擴展:對協議中的方法給出默認實現
- protocol Fightable {
func fight()
}
// 協議擴展 - 可以在協議擴展中給協議中的方法提供默認實現
// 也就是說如果某個類遵循了協議但是沒有實現這個方法就直接使用默認實現
// 那么這個方法也就....
extension Fightable {
func fight() {
print("正在打架")
}
}
泛型
讓類型不再是程序中的硬代碼(hard code),可以設計出更通用的代碼。
- 泛型函數
// 定義一個虛擬類型T, 調用函數時根據傳入的參數類型來決定T到底是什么
func mySwap<T>(inout a: T, inout _ b: T) {
let temp = a
a = b
b = temp
}
- 泛型類/結構/枚舉
// Swift中的類、結構和枚舉都可以使用泛型
struct Stack<T> {
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<String>()
stack.push("hello")
stack.push("good")
stack.push("zoo")
while !stack.isEmpty {
print(stack.pop())
}
相關知識
- 泛型限定
// 泛型限定
// <T: Comparable>限定T類型必須是遵循了Comparable協議的類型
func bubbleSort<T: Comparable>(array: [T]) -> [T] {
var newArray = array
for i in 0..<newArray.count - 1 {
var swapped = false
for j in 0..<newArray.count - 1 - i {
if newArray[j] > newArray[j + 1] {
mySwap(&newArray[j], &newArray[j + 1])
swapped = true
}
}
if !swapped {
break
}
}
return newArray
}
- where子句
錯誤處理
enum MyError: ErrorType {
case A
case B
case C
}
- throw
- throws / rethrows
- do
- catch
- try
邊角知識
- ARC
- 正則表達式
- 嵌套類型