Swift5 基礎(一)Swift編譯流程慎冤、基礎語法、流程控制沧卢、函數蚁堤、枚舉

Swift5 基礎教程與進階合集

一、Swift編譯流程

Swift編譯器流程

Swift前端使用swiftc編譯器做詞法分析但狭,后端使用LLVM編譯器披诗,生成對應平臺的二進制代碼以及對二進制代碼進行相應的優(yōu)化

在C/OC中,前端編譯使用的是clang立磁,后端也是LLVM

Swift編譯流程

一開始是你自己編寫的Swift代碼【Swift Code】,然后再根據swiftc前端編譯器生成語法樹【Swift AST】,接下來再生成Swift特有的中間代碼【Raw Swift IL】,再生成一個簡潔的版本(Swift特有的中間代碼)【Canonical Swift IL】呈队。Swift代碼不是一步到位變成二進制代碼的,是有一個流程唱歧。中間代碼生成完畢之后宪摧,轉交給后端(LLVM),生成一個【LLVM IR】代碼颅崩,它是LLVM的中間代碼几于。LLVM編譯器又會針對IR代碼進行相應的優(yōu)化。優(yōu)化完畢之后沿后,最終轉成匯編代碼【Assembly】,匯編代碼最終變成二進制代碼【Executable】沿彭。

總結流程為:Swift代碼 -> 語法樹 -> 中間代碼 -> 轉交給LLVM -> 匯編代碼 -> 二進制代碼

swiftc存放在Xcode內部:Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin,同樣clang也在這個位置

swiftc常用命令:

  1. 生成可執(zhí)行文件 swiftc -o main.out main.swift
  2. 生成抽象語法樹的命令(AST) swiftc main.swift -dump-ast
  3. 生成中間語言(SIL) swiftc main.swift -emit-sil
  4. LLVM中間表示層(LLVM IR) swiftc main.swift -emit -ir
  5. 生成匯編語言 swiftc main.swift -emit-assembly

二尖滚、基礎語法

常量

  • 只能賦值一次
  • 它的值不要求在編譯時期確定喉刘,但使用之前必須賦值一次
  • 常量、變量在初始化之前熔掺,都不能使用

標識符

  • 標識符(常量名饱搏、變量名、函數名)幾乎可以使用任何字符
  • 限制:標識符不能以數字開頭置逻,不能包含空白字符推沸、制表符、箭頭等特殊字符

常見數據類型

  • 值類型(value type):swift中除了類和閉包券坞,都是值類型
  • 引用類型(refrence type):類鬓催、閉包

整數類型
Int類型在32位平臺為Int32,64位平臺為Int64
整數的最值:UInt8.max恨锚、Int16.min

浮點類型
Float 32位宇驾,精度只有6位
Double 64位,精度至少15位

字面量
整數和浮點數可以添加額外的零或者添加下劃線來增強可讀性
100_0000猴伶、1_000_000.000_000_1课舍、000123.456

  • 類型轉換
//整數轉換
let int1 : UInt16 = 2_000
let int2 : UInt8 = 1
let int3 = int1 + UInt16(int2)//答案:2001

//浮點類型轉換
let int = 3
let double = 0.14159
let pi = Double(int) + double //3.14159
let intPi = Int(pi) //3

//字面量可以直接相加塌西,因為數字字面量本身沒有明確的類型
let result = 3 + 0.1415926 // result類型推導為Double,值為3.1415926

元組

let http404Error = (404,"Not Found")
let (statusCode,StatusMessage) = http404Error
let (justStatusCode,_) = http404Error
let http200Status = (statusCode,200,description: "OK")

三筝尾、流程控制

if-else

  • if后面的條件可以省略小括號
  • 條件后面的大括號不可以省略(和OC不同捡需,OC中單行表達式可以省略大括號)
  • if后面的條件只能是Bool類型,因為swift沒有非零即真的概念

while筹淫、repeat-while

  • repeat-while相當于C語言中的do-while
  • swift3開始站辉,去除了++、--運算符损姜,只能用+=1饰剥,-=1替代

for

  • swift也沒有C中的for(int i=0;i<5;i++),使用for(i in 0..<5)替代
  • 遍歷中i默認為let,可以顯示聲明為var摧阅,就可以在大括號中改變值使用了
  • 區(qū)間運算符(...汰蓉、..<,...number)
//閉區(qū)間運算符:a...b,等價于 a<=取值<=b
let range1 = 1...3//let range1: ClosedRange<Int>
//半開區(qū)間運算符:a..<b,等價于 a<=取值<b
let range2 = 1..<3//let range2: Range<Int>
//單側區(qū)間:...a等價于,讓區(qū)間朝著左側盡可能遠棒卷,最大不超過a; b...等價于古沥,讓區(qū)間朝著右側盡可能遠,最小不低于b
let range3 = ...5//let range3: PartialRangeThrough<Int>
let range4 = ..<5//let range: PartialRangeUpTo<Int>
let range5 = 5... //let range5: PartialRangeFrom<Int>

字符娇跟、字符串也可以使用區(qū)間運算符,但默認不能用在for-in中

let range = "cc"..."ff"http://let range: ClosedRange<String>
range.contains("cb")//false
range.contains("ge")//false
range.contains("ef")//true

// \0囊括了所有可能要用到的ASCII字符
let characterRange  : ClosedRange<Character> = "\0"..."~"
characterRange.contains("G")//true

帶間隔的區(qū)間值

let hours = 11
let hourInterval = 2
//tickMark的取值:從4開始太颤,累加2苞俘,不超過11
for tickMark in stride(from: 4, to: hours, by: hourInterval) {
    print(tickMark)
}//4 6 8 10

switch

  • case、default后面不能寫大括號
  • switch默認為不貫穿龄章,不像C中必須加上break才是不貫穿吃谣,如果想要貫穿,在case的語句最后一句加上fallthrough
  • switch必須保證能處理所有情況做裙,也就是說岗憋,如果不能使用case列舉出所有情況,那么必須有default
  • case锚贱、default后面至少要有一條語句仔戈,如果不想做任何事,加個break即可
  • swift的switch中不像C只能用int值拧廊,也支持Character监徘、String類型、元組類型等
  • switch的case也可以使用區(qū)間匹配(case 1...5),元組匹配(case (_,0)),這里的_表示忽略某個值,case匹配屬于模式匹配(Pattern Matching)的范疇
  • case中可以使用值綁定(用一個常量或變量來接收值),即(case (let x,var y))
  • case中還可以使用where,即(case let(x,y) where x == y:)

for中也可以使用where來過濾(for num in numbers where num > 0)
數組其實可以使用filter函數來過濾,(numbers.filter($0 > 0)),這樣就過濾得出大于0的所有成員的數組

語句的標簽

  • 標簽語句一般用于多重循環(huán)或條件語句嵌套中吧碾,用于標記是結束哪一個循環(huán)
outer: for i in 1...4 {
    for k in 1...4 {
        if k == 3 {
            continue outer
        }
        if i == 3 {
            break outer
        }
        print("i == \(i), k == \(k)")
    }
}
打印結果:
i == 1, k == 1
i == 1, k == 2
i == 2, k == 1
i == 2, k == 2

可以看出凰盔,沒有k==3的打印,因為一旦執(zhí)行到k==3直接跳到了外層的循環(huán)繼續(xù)

四倦春、函數

函數的定義

  • 使用func關鍵字定義函數 func pi(num: Double) -> Double{}
  • 形參默認是let户敬,也只能是let
  • 無返回值可以寫為Void落剪、()、或者直接不寫
  • 可以使用元組實現多返回值

隱式返回(Implicit Return)

  • 如果函數體是一個單行表達式尿庐,那么函數會隱式返回這個表達式
func sum<T : FloatingPoint>(v1: T,v2: T) -> T{
    v1 + v2
}
sum(v1: 10, v2: 20.0)//30

參數標簽(Argument Label)

  • 可以修改參數標簽(添加外部名)忠怖,默認外部名和內部名相同
  • 可以使用下劃線_ 省略外部名
func work(at time : String){
    print("work at time \(time)")
}
work(at: "am 9:00")//work at time am 9:00

func sum<T: FloatingPoint>(_ v1: T,_ v2: T) -> T {
    v1 + v2
}
sum(10, 20)//30

默認參數值

  • 參數可以有默認值,如func sum(v1: Int = 10,v2: Int)
  • C++的默認參數值有個限制屁倔,必須從右往左設置脑又,但由于Swift擁有參數標簽,因此并沒有此類限制
  • 有默認值的在函數調用時可不傳入有默認值的參數锐借,但是沒有默認值的參數必須設置值

可變參數

  • 一個函數最多只能有1個可變參數
  • 可變參數在函數體內作為數組處理
  • 緊跟在可變參數后面的參數不能省略參數標簽
func test(_ numbers: Int...,string: String,_ other: String){
    for i in numbers {
        print(i)
    }
    print(string + other)
}
test(10,20,30, string: "這里的標簽不能省略", "不是緊跟著就可以省略外部名")

Swift自帶的print函數

  • print(_ items: Any..., separator: String = " ", terminator: String = "\n")
  • seperator參數表示用什么分隔 默認是空格
  • terminator表示打印完了之后做什么问麸,默認是\n換行

輸入輸出參數(In-Out Parameter)

  • 可以用inout定義一個輸入輸出參數:可以在函數內部修改外部實參的值
  • 可變參數不能標記為inout
  • inout參數不能有默認值
  • inout參數只能傳入var變量
  • inout參數的本質是地址傳遞(引用傳遞)
func swapValues(_ a: inout Int,_ b: inout Int){
    /*
     //不用中間變量的三種方法
     //1.加減法 缺點:浮點數交換時可能會出現精度損失
     a += b
     b = a - b
     a = a - b
     
     //2.乘除法 缺點:也會出現精度損失,而且b還必須不能為0
     a *= b
     b = a/b
     a = a/b
     
     //3.異或法 缺點:只能完成整形變量的交換钞翔,對于浮點數無法完成交換
     a ^= b
     b ^= a
     a ^= b
     */
    
    //swift中因為有元組的存在
    (a,b) = (b,a)
}
var num1 = 10,num2 = 20
swapValues(&num1, &num2)
print("num1 = \(num1),num2 = \(num2)")//num1 = 20,num2 = 10,外部實參的值被改變了

函數重載

  • 定義: 函數相同 參數個數不同||參數類型不同||參數標簽不同
  • 返回值類型與函數重載無關
  • 默認參數值和函數重載一起使用產生二義性時严卖,編譯器不會報錯(在C++中會報錯)
func sum(v1: Int,v2: Int) -> Int{
    v1 + v2
}
func sum(v1: Int,v2: Int,v3: Int = 10) -> Int{
    v1 + v2 + v3
}
let result = sum(v1: 1, v2: 2) //調用的是只有兩個參數的
print(result)//3

內聯函數

  • 如果開啟了編譯器優(yōu)化(release模式默認會開啟優(yōu)化),編譯器會自動將某些函數變成內聯函數.
    XCode在debug下打開內聯:Build Settings->搜索optimization->Optimization Level下debug選擇Optimize for Speed
  • 將函數調用展開成函數體
  • 在Release模式下布轿,編譯器已經開啟優(yōu)化哮笆,會自動決定哪些函數需要內聯,因此沒必要使用@inline
//永遠不會被內聯(即使開啟了編譯器優(yōu)化)
@inline(never) func test() {
    print("永不會內聯")
}
//開啟編譯器優(yōu)化后汰扭,即使代碼很長稠肘,也會被內聯(遞歸調用函數、動態(tài)派發(fā)的函數除外)
@inline(__always) func test(){
    print("會被內聯")
}

函數類型(Function Type)

func test(){} //()->Void 或者()->()
func sum(a: Int,b: Int) -> Int{
    a + b
}//(Int,Int)->Int
//定義變量
var fn: (Int,Int) -> Int = sum
fn(2,3)//5 調用時不需要參數標簽
  • 函數類型可以作為函數參數
  • 函數類型可以作為函數返回值萝毛,返回值是函數類型的函數项阴,叫做高階函數(Higher-Order Function)

typealias

  • typealias用來給類型起別名 typealias Byte = UInt8
  • 按照Swift標準庫的定義,Void就是空元組()

public typealias Void = ()

嵌套函數(Nested Function)

嵌套函數就是講函數定義在函數的內部

func forward(_ forward: Bool) -> (Int)->Int{
    func next(_ input : Int) -> Int{
        input + 1
    }
    func previous(_ input: Int) -> Int{
        input - 1
    }
    return forward ? next : previous
}
forward(true)(3) //4
forward(false)(3) //2

五笆包、枚舉

枚舉的定義

enum Direction{
    case north
    case south
    case east
    case west
}
enum Direction{
    case north,south,east,west
}
上面兩種定義方式等價

關聯值(Associated Values)

enum Score{
    case point(Int)
    case grade(Character)
}
enum Date{
    case digit(year: Int,month: Int,day: Int)
    case string(String)
}
var date = Date.string("2020-05-22")
date = .digit(year: 2020, month: 5, day: 20)
switch date {
case .digit(let year,let month,var day):
    day = 10
    print(year,month,day) // 2020 5 10
case let .string(value):
    print(value)
}
枚舉定義時枚舉變量后括號里面的就是關聯值

原始值

枚舉成員可以使用相同類型的默認值預先對應环揽,這個默認值叫做原始值

enum Grade : String{
    case perfect = "A"
    case great = "B"
    case good = "C"
    case bad = "D"
}
//原始值其實就是枚舉變量的rawValue值
print(Grade.perfect.rawValue,Grade.great.rawValue,Grade.good.rawValue,Grade.bad.rawValue)//A B C D

注意:原始值不占用枚舉變量的內存

隱式原始值(Implicitly Assigned Raw Values)

如果枚舉的原始值類型是Int、String庵佣,Swift會自動分配原始值

  • Int的原始值如果沒有指定歉胶,那么默認第一個從0開始,依次加1遞增巴粪,如果指定了通今,那么指定的枚舉變量前面的仍然按照0開始,依次加1遞增验毡,指定枚舉變量之后的在指定的基礎上加1遞增衡创;
  • String的原始值默認為枚舉變量名的字符串
enum Direction : Int{
    case north,south,east = 5,west
}
print(Direction.north.rawValue,Direction.south.rawValue,Direction.east.rawValue,Direction.west.rawValue)//0 1 5 6

enum Season : String{
    case spring,summer,autumn = "test",winter
}
print(Season.spring.rawValue,Season.summer.rawValue,Season.autumn.rawValue,Season.winter.rawValue)//spring summer test winter

CaseIterable

枚舉遵守CaseIterable協議可進行遍歷

enum Direction : Int,CaseIterable{
    case north,south,east = 5,west
}
let count = Direction.allCases.count
print(count)//4
for season in Direction.allCases{
    print(season)
}
/*
north
south
east
west
*/

遞歸枚舉(Recursive Enumeration)

  • 遞歸枚舉是一種枚舉類型
  • 有一個或多個枚舉成員使用該枚舉類型的變量作為關聯值
  • 在枚舉成員前加上indirect來表示該成員可遞歸
enum ArithExpr{
    case number(Int)
    indirect case sum(ArithExpr,ArithExpr)
    indirect case minus(ArithExpr,ArithExpr)
}
  • 也可以定義枚舉前加上indirect來讓整個枚舉成員在需要時可遞歸
indirect enum ArithExpr{
    case number(Int)
    case sum(ArithExpr,ArithExpr)
    case minus(ArithExpr,ArithExpr)
}

應用案例

let five = ArithExpr.number(5)
let four = ArithExpr.number(4)
let two = ArithExpr.number(2)
let sum = ArithExpr.sum(five, four)
let minus = ArithExpr.minus(sum, two)

func calcuate(_ expr: ArithExpr) -> Int{
    switch expr {
    case let .number(value):
        return value
    case let .sum(left, right):
        return calcuate(left)+calcuate(right)
    case let .minus(left, right):
        return calcuate(left) - calcuate(right)
    }
}
calcuate(minus)//7

MemoryLayout

可以使用MemoryLayout獲取數據類型占用的內存大小

enum Password{
    case number(Int,Int,Int,Int)
    case other
}
MemoryLayout<Password>.stride//40,分配占用的空間大小
MemoryLayout<Password>.size//33,實際用到的空間到校
MemoryLayout<Password>.alignment//8,對齊參數

var pwd = Password.number(9, 8, 6, 4)
pwd = .other
MemoryLayout.stride(ofValue: pwd)//40
MemoryLayout.size(ofValue: pwd)//33
MemoryLayout.alignment(ofValue: pwd)//8

enum TestEnum{
    case t1(Double,Character),t2,t3
}
MemoryLayout<TestEnum>.stride//24
MemoryLayout<TestEnum>.size//24
MemoryLayout<TestEnum>.alignment//8
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市晶通,隨后出現的幾起案子璃氢,更是在濱河造成了極大的恐慌,老刑警劉巖狮辽,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件一也,死亡現場離奇詭異巢寡,居然都是意外死亡,警方通過查閱死者的電腦和手機椰苟,發(fā)現死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門抑月,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人舆蝴,你說我怎么就攤上這事谦絮。” “怎么了洁仗?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵层皱,是天一觀的道長。 經常有香客問我赠潦,道長叫胖,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任她奥,我火速辦了婚禮瓮增,結果婚禮上,老公的妹妹穿的比我還像新娘哩俭。我一直安慰自己绷跑,他們只是感情好,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布凡资。 她就那樣靜靜地躺著你踩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪讳苦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天吩谦,我揣著相機與錄音鸳谜,去河邊找鬼。 笑死式廷,一個胖子當著我的面吹牛咐扭,可吹牛的內容都是我干的。 我是一名探鬼主播滑废,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼蝗肪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蠕趁?” 一聲冷哼從身側響起薛闪,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎俺陋,沒想到半個月后豁延,有當地人在樹林里發(fā)現了一具尸體昙篙,經...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年诱咏,在試婚紗的時候發(fā)現自己被綠了苔可。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡袋狞,死狀恐怖焚辅,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情苟鸯,我是刑警寧澤同蜻,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站倔毙,受9級特大地震影響埃仪,放射性物質發(fā)生泄漏。R本人自食惡果不足惜陕赃,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一卵蛉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧么库,春花似錦傻丝、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至忱反,卻和暖如春泛释,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背温算。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工怜校, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人注竿。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓茄茁,卻偏偏與公主長得像,于是被迫代替她去往敵國和親巩割。 傳聞我的和親對象是個殘疾皇子裙顽,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348