Swift5 基础(一)Swift编译流程、基础语法、流程控制、函数、枚举

Swift5 基础教程与进阶合集

一、Swift编译流程

Swift编译器流程

Swift前端使用swiftc编译器做词法分析,后端使用LLVM编译器,生成对应平台的二进制代码以及对二进制代码进行相应的优化

在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代码进行相应的优化。优化完毕之后,最终转成汇编代码【Assembly】,汇编代码最终变成二进制代码【Executable】。

总结流程为:Swift代码 -> 语法树 -> 中间代码 -> 转交给LLVM -> 汇编代码 -> 二进制代码

swiftc存放在Xcode内部:Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin,同样clang也在这个位置

swiftc常用命令:

  1. 生成可执行文件 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,就可以在大括号中改变值使用了
  • 区间运算符(...、..<,...number)
//闭区间运算符:a...b,等价于 a<=取值<=b
let range1 = 1...3//let range1: ClosedRange<Int>
//半开区间运算符:a..<b,等价于 a<=取值<b
let range2 = 1..<3//let range2: Range<Int>
//单侧区间:...a等价于,让区间朝着左侧尽可能远,最大不超过a; b...等价于,让区间朝着右侧尽可能远,最小不低于b
let range3 = ...5//let range3: PartialRangeThrough<Int>
let range4 = ..<5//let range: PartialRangeUpTo<Int>
let range5 = 5... //let range5: PartialRangeFrom<Int>

字符、字符串也可以使用区间运算符,但默认不能用在for-in中

let range = "cc"..."ff"//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

带间隔的区间值

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也可以使用区间匹配(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的所有成员的数组

语句的标签

  • 标签语句一般用于多重循环或条件语句嵌套中,用于标记是结束哪一个循环
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的打印,因为一旦执行到k==3直接跳到了外层的循环继续

四、函数

函数的定义

  • 使用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

内联函数

  • 如果开启了编译器优化(release模式默认会开启优化),编译器会自动将某些函数变成内联函数.
    XCode在debug下打开内联:Build Settings->搜索optimization->Optimization Level下debug选择Optimize for Speed
  • 将函数调用展开成函数体
  • 在Release模式下,编译器已经开启优化,会自动决定哪些函数需要内联,因此没必要使用@inline
//永远不会被内联(即使开启了编译器优化)
@inline(never) func test() {
    print("永不会内联")
}
//开启编译器优化后,即使代码很长,也会被内联(递归调用函数、动态派发的函数除外)
@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阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345