Swift第一章 基础语法、流程控制、函数

当前语法为swift5.1版本

简介

  1. Swift是Apple在2014年6月WWDC发布的全新编程语言,中文名和LOGO是“雨燕”
  2. 编译流程如下: 1.png

    1. OC语言的通过编译器前端Clang编译,然后交给编译器后端LLVM编译,生成不同平台的二进制码。
    2. Swift语言通过编译器前端swiftc编译,然后交给编译器后端LLVM编译,生成不同平台的二进制码。

swiftc

  1. swiftc存放在Xcode内部:Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
  2. 一些操作
    1. 生成语法树AST: swiftc -dump-ast main.swift
    2. 生成最简洁的SIL代码:swiftc -emit-sil main.swift
    3. 生成LLVM IR代码: swiftc -emit-ir main.swift -o main.ll
    4. 生成汇编代码: swiftc -emit-assembly main.swift -o main.s

Playground

  1. 不用编写main函数,Swift将全局范围内的首句可执行代码作为程序入口
  2. 一句代码尾部可以省略分号(;),多句代码写到同一行时必须用分号(;)隔开
  3. 用var定义变量,let定义常量,编译器能自动推断出变量\常量的类型
  4. Playground可以快速预览代码效果,是学习语法的好帮手
    1. 打开Xcode -》file-》new-》Playground…-》iOS下的Blank,选择文件名、保存位置即可
    2. Command + Shift + Enter:运行整个Playground
    3. Shift + Enter:运行截止到某一行代码
  5. Playground - View: 如何创建一个View并展示

     import UIKit
     import PlaygroundSupport
        
     let xview = UIView()
     xview.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
     xview.backgroundColor = UIColor.red
     //拿到显示屏PlaygroundPage,然后将当前view展示为xview
     PlaygroundPage.current.liveView = xview
        
     //展示图片
     //先将图片logo.png拖拽到左侧的Resources文件夹
     let imageView = UIImageView(image: UIImage(named: "logo"))
     PlaygroundPage.current.liveView = imageView
        
     //展示控制器
     let vc = UITableViewController()
     vc.view.backgroundColor = UIColor.blue
     PlaygroundPage.current.liveView = vc
    
  6. Playground – 多Page
    1. 一个Playground下面可以创建多个page,每次训练的代码放到一个page即可
    2. 右击当前的Playground-》New Playground page,然后取名即可
    3. 然后再当前page下练习Swift代码
  7. Markup语法
    1. Playground的注释支持markup语法(与markdown相似)
    2. 开启markup渲染效果:Editor -> Show Rendered Markup
    3. 注意:Markup只在Playground中有效
     //: 开始markup,单行开启
        
     /*:
     开始markup,多行开启
     */
    

基础语法

常量

  1. 只能赋值1次,它的值不要求在编译时期确定,但使用之前必须赋值1次
  2. 常量、变量在初始化之前,都不能使用
//编译时赋值
let age1 = 10

//编译时age2、age3都没有赋值,真正运行时才赋值
let age2: Int
age2 = 20

func getAge() -> Int {
    return 30
}

let age3 = getAge()

//常量、变量在初始化之前,都不能使用
let age: Int
print(age)

//下面代码错误,声明变量agex时,无法确认类型。
let agex
agex = 20

标识符

  1. 标识符(比如常量名、变量名、函数名)几乎可以使用任何字符
  2. 标识符不能以数字开头,不能包含空白字符、制表符、箭头等特殊字符

常见数据类型

  1. 值类型(value type)
    1. 枚举(enum):Optional
    2. 结构体(struct):Bool、Int、Float、Double、Character、String、Array、Dictionary、Set,Int本质就是struct类型,可以看Int定义
  2. 引用类型(reference type):类(class)
  3. 整数类型:Int8、Int16、Int32、Int64、UInt8、UInt16、UInt32、UInt64
  4. 在32bit平台,Int等价于Int32;在64bit平台, Int等价于Int64
  5. 整数的最值:UInt8.max、Int16.min
  6. 一般情况下,都是直接使用Int即可
  7. 浮点类型:Float,32位,精度只有6位;Double,64位,精度至少15位

字面量

//布尔
let bool=true //取反是false

// 字符串
let string ="小xxx"

//字符(可存储ASCII字符、Unicode字符)
let character:Character="o"

//整数和浮点数可以添加额外的零或者添加下划线来增强可读性
100_0000、1_000_000.000_000_1、000123.456

//整数
let intDecimal=17 //十进制
let intBinary = 0b10001 //二进制
let intOctal=0o21 //八进制
let intHexadecimal = 0x11 //十六进制

// 浮点数
let doubleDecimal = 125.0 // 十进制,等价于1.25e2,0.0125等价于1.25e-2
let doubleHexadecimal1 = 0xFp2 // 十六进制,意味着15x2^2,相当于十进制的60.0
let doubleHexadecimal2 = 0xFp-2 // 十六进制,意味着15x2^-2,相当于十进制的3.75
/*以下都是表示12.1875
十进制:12.1875、1.21875e1
十六进制:0xC.3p0 */

//数组
let array =[1,3,5,7,9]
// 字典
let dictionary=["age":18,"height" :168,"weight":120]

类型转换

//整数转换
let int1:UInt16= 2_000
let int2:UInt8 =1
let int3 = int1 + UInt16(int2)

//整数、浮点数转换
let int =3
let double =0.14159
let pi = Double(int)+ double
let intPi = Int(pi)

//字面量可以直接相加,因为数字字面量本身没有明确的类型
let result = 3 + 0.14159

元组(Tuple)

let http404Error = (404,"Not Found")
print("The status code is \(http404Error.0)")

let (statusCode , statusMessage) = http404Error
print("The status code is \(statusCode)")

let (justTheStatusCode , _) = http404Error

let http200Status = (statusCode:200 , description:"OK")
print("The status code is \(http200Status.statusCode)")

流程控制

if-else

  1. if后面的条件可以省略小括号,条件后面的大括号不可以省略,if后面的条件只能是Bool类型
let age = 4
if age >= 22 {
    print("Get married")
} else if age >= 18 {
    print("Being a adult")
} else if age >=7 {
    print("Go to school")
} else {
    print("Just a child")
}

//报错:该条件不是bool,因此报错,即不支持非零即真
if age {
}

while、repeat-while

var num = 5
while num > 0{
    print("num is \(num)")
    num -= 1
} // 打印了5次

var num = -1
repeat {
    print('num is \(num)")
} while num >0 // 打印了1次
  1. repeat-while相当于C语言中的do-while
  2. 这里不用num–,是因为:从Swift3开始,去除了自增(++)、自减(–)运算符

for

  1. 闭区间运算符:a…b, a <= 取值 <= b

     let names =["Anna","Alex","Brian","Jack" ]
     for i in 0...3 {
         print(names[i])
     }// Anna Alex Brian Jack
        
     let range = 1...3
     for i in range {
         print(names[i])
     }// Alex Brian Jack
        
     let a=1
     var b=2
     for i in a...b {
         print(names [i])
     }// Alex Brian
        
     for i in a...3 {
         print(names[i])
     }// Alex Brian Jack
        
     // i默认是let,有需要时可以声明为var
     for var i in 1...3 {
         i += 5
         print(i)
     }//678
        
     //如果for循环中没有使用到i,可以用_代替,防止编译器提示警告i没有使用
     for _ in 1...3 {
         print("for")
     }
    
  2. 半开区间运算符:a..<b, a <= 取值 < b

     for i in 1..<5 {
         print(i)
     }//1234
    

for – 区间运算符用在数组上

  1. 举例

     let names = ["Anna", "Alex", "Brian", "Jack"]
     for name in names[0...3] {
         print(name)
     } // Anna Alex Brian Jack
    
  2. 单侧区间:让区间朝一个方向尽可能的远

     for name in names[2...] {
         print(name)
     } // Brian Jack
        
     for name in names[...2] {
         print(name)
     } // Anna Alex Brian
        
     for name in names[..<2] {
         print(name)
     } // Anna Alex
    
  3. 如果不在数组中使用

     let range = ...5
     range.contains(7) // false
     range.contains(4) // true
     range.contains(-3) // true
    

区间类型

  1. 举例:

     let range1: ClosedRange<Int> = 1...3
     let range2: Range<Int> = 1..<3
     let range3: PartialRangeThrough<Int> = ...5
    
  2. 字符、字符串也能使用区间运算符,但默认不能用在for-in中

     let stringRange1 = "cc"..."ff" // ClosedRange<String>
     stringRange1.contains("cb") // false
     stringRange1.contains("de") // true
     stringRange1.contains("fg") // false
        
     let stringRange2 = "a"..."f"
     stringRange2.contains("d") // true
     stringRange2.contains("h") // false
        
     // \0到~囊括了所有可能要用到的ASCII字符
     let characterRange: ClosedRange<Character> = "\0"..."~"
     characterRange.contains("G") // true
    
  3. 带间隔的区间值

     let hours = 11
     let hourInterval = 2
     // tickMark的取值:从4开始,累加2,不超过11
     for tickMark in stride(from: 4, through: hours, by: hourInterval) {
         print(tickMark)
     } // 4 6 8 10
    

switch

  1. case、default后面不能写大括号{}

     var number = 1
     switch number {
         case 1:
             print("number is 1")
             break
         case 2:
             print("number is 2")
             break
         default:
             print("number is other")
             break
     } // number is 1
    
  2. 默认可以不写break,并不会贯穿到后面的条件

     var number = 1
     switch number {
         case 1:
             print("number is 1")
         case 2:
             print("number is 2")
         default:
             print("number is other")
     } // number is 1
    
  3. 使用fallthrough可以实现贯穿效果

     var number = 1
     switch number {
     case 1:
         print("number is 1")
         fallthrough
     case 2:
         print("number is 2")
     default:
         print("number is other")
     }
     // number is 1
     // number is 2
    
  4. switch注意点

    1. switch必须要保证能处理所有情况
    2. case、default后面至少要有一条语句,如果不想做任何事,加个break即可

       var number = 1
       switch number {
           case 1:
               print("number is 1")
           case 2:
               print("number is 2")
           //1. 这里必须加上default代表除了1、2的其他情况
           //2. default后面必须有至少一条语句
           default:
               break
       }
      
    3. 如果能保证已处理所有情况,也可以不必使用default

       enum Answer { case right, wrong }
       let answer = Answer.right
       switch answer {
           //case Answer.right:
           // 由于已确定answer是Ansewer类型,因此可以省略Answer
           case .right:
               print("right")
           case Answer.wrong:
               print("wrong")
       }
      
    4. switch也支持Character、String类型

       let string = "Jack"
       switch string {
           case "Jack":
               fallthrough
           case "Rose":
               print("Right person")
           default:
               break
       } // Right person
              
       switch string {
           case "Jack", "Rose":
               print("Right person")
           default:
               break
       } // Right person
              
              
       let character: Character = "a"
       switch character {
           case "a", "A":
               print("The letter A")
           default:
               print("Not the letter A")
       } // The letter A
      
    5. 区间匹配、元组匹配

       let count = 62
       switch count {
           case 0:
               print("none")
           case 1..<5:
               print("a few")
           case 5..<12:
               print("several")
           case 12..<100:
               print("dozens of")
           case 100..<1000:
               print("hundreds of")
           default:
               print("many")
       } // dozens of
              
       //某个坐标,在某个范围
       let point = (1, 1)
       switch point {
           case (0, 0):
               print("the origin")
           //可以使用下划线 _ 忽略某个值
           case (_, 0):
               print("on the x-axis")
           case (0, _):
               print("on the y-axis")
           case (-2...2, -2...2):
               print("inside the box")
           default:
               print("outside of the box")
       } // inside the box
      
    6. 值绑定

       let point = (2, 0)
       switch point {
           //y坐标匹配,因此将2赋值给x
           case (let x, 0):
               print("on the x-axis with an x value of \(x)")
           case (0, let y):
               print("on the y-axis with a y value of \(y)")
           case let (x, y):
               print("somewhere else at (\(x), \(y))")
       } // on the x-axis with an x value of 2
      

where

let point = (1, -1)
switch point {
    case let (x, y) where x == y:
        print("on the line x == y")
    case let (x, y) where x == -y:
        print("on the line x == -y")
    case let (x, y):
        print("(\(x), \(y)) is just some arbitrary point")
} // on the line x == -y

//where放到for循环中
// 将所有正数加起来
var numbers = [10, 20, -10, -20, 30, -30]
var sum = 0
for num in numbers where num > 0 { // 使用where来过滤num
    sum += num
}
print(sum) // 60

标签语句

outer: for i in 1...4 {
    for k in 1...4 {
        if k == 3 {
            //使用标签outer控制外循环
            continue outer
        }
        if i == 3 {
            //使用标签outer控制外循环
            break outer
        }
        print("i == \(i), k == \(k)")
    }
}

函数

函数的定义

  1. 函数的形参默认是let,也只能是let,因此let可以直接省略
  2. 举例:

     //1. 无参有返回值
     func pi() -> Double {
         return 3.14
     }
     pi()
        
     //2. 有参、有返回值
     func sum(v1: Int, v2: Int) -> Int {
         return v1 + v2
     }
     sum(v1: 10, v2: 20)
        
     //3. 无参、无返回值
     func sayHello() -> Void {
         print("Hello")
     }
     func sayHello() -> () {
         print("Hello")
     }   
     func sayHello() {
         print("Hello")
     }
    

隐式返回(Implicit Return)

  1. 如果整个函数体是一个单一表达式,那么函数会隐式返回这个表达式,不用写return

     func sum(v1: Int, v2: Int) -> Int {
         //函数体就是一个单一表达式
         v1 + v2
     }
     sum(v1: 10, v2: 20) // 30
    

返回元组:实现多返回值

func calculate(v1: Int, v2: Int) -> (sum: Int, difference: Int, average: Int) {
    let sum = v1 + v2
    return (sum, v1 - v2, sum >> 1)
}
let result = calculate(v1: 20, v2: 10)
result.sum // 30
result.difference // 10
result.average // 15

函数的文档注释

  1. 参考:https://swift.org/documentation/api-design-guidelines/
/// 求和【概述】
///
/// 将2个整数相加【更详细的描述】
///
/// - Parameter v1: 第1个整数
/// - Parameter v2: 第2个整数
/// - Returns: 2个整数的和
///
/// - Note:传入2个整数即可【批注】
///
func sum(v1: Int, v2: Int) -> Int {
    v1 + v2
}

参数标签(Argument Label)

  1. 可以修改参数标签

     //修改time这个参数标签为at
     func goToWork(at time: String) {
         //函数体里面还用修改之前的标签
         print("this time is \(time)")
     }
     //调用时,函数标签用at,好处是:看起来表达通顺
     goToWork(at: "08:00")
     // this time is 08:00
    
  2. 可以使用下划线 _ 省略参数标签

     func sum(_ v1: Int, _ v2: Int) -> Int {
         v1 + v2
     }
     sum(10, 20)
    

默认参数值(Default Parameter Value)

  1. 参数可以有默认值

     func check(name: String = "nobody", age: Int, job: String = "none") {
         print("name=\(name), age=\(age), job=\(job)")
     }
     check(name: "Jack", age: 20, job: "Doctor") // name=Jack, age=20, job=Doctor
     check(name: "Rose", age: 18) // name=Rose, age=18, job=none
     check(age: 10, job: "Batman") // name=nobody, age=10, job=Batman
     check(age: 15) // name=nobody, age=15, job=none
    
  2. C++的默认参数值有个限制:必须从右往左设置。由于Swift拥有参数标签(比如name),因此并没有此类限制
  3. 但是在省略参数标签时,需要特别注意,避免出错

     // 这里的middle不可以省略参数标签
     func test(_ first: Int = 10, middle: Int, _ last: Int = 30) { }
     test(middle: 20)
    

可变参数(Variadic Parameter)

  1. 一个函数最多只能有1个可变参数

     func sum(_ numbers: Int...) -> Int {
         var total = 0
         for number in numbers {
             total += number
         }
         return total
     }
     sum(10, 20, 30, 40) // 100
    
  2. 紧跟在可变参数后面的参数不能省略参数标签

     // 参数string不能省略标签,string标签不能被省略
     func test(_ numbers: Int..., string: String, _ other: String) { }
     test(10, 20, 30, string: "Jack", "Rose")
    

Swift自带的print函数

  1. 定义

     /// - Parameters:
     /// - items: Zero or more items to print.
     /// - separator: A string to print between each item. The default is a single space (`" "`).
     /// - terminator: The string to print after all items have been printed. The
     /// default is a newline (`"\n"`).
     public func print(_ items: Any..., separator: String = " ", terminator: String = "\n")
        
    
  2. 使用

     print(1, 2, 3, 4, 5) // 1 2 3 4 5
     print(1, 2, 3, 4, 5, separator: "_") // 1_2_3_4_5
        
     print("My name is Jake.", terminator: "")
     print("My age is 18.")
     // My name is Jake.My age is 18.
    

输入输出参数(In-Out Parameter)

  1. 前面说过,函数的参数是let类型,那么如果要修改函数的参数呢?–inout
  2. 可以用inout定义一个输入输出参数:可以在函数内部修改外部实参的值
  3. 可变参数不能标记为inout
  4. inout参数不能有默认值
  5. inout参数只能传入可以被多次赋值的,即num1、num2不能用let修饰
  6. inout参数的本质是地址传递(引用传递)

     func swapValues(_ v1: inout Int, _ v2: inout Int) {
         //通常情况下,v1、v2都默认是let,不能被修改的
         let tmp = v1
         v1 = v2
         v2 = tmp
     }
     //传入的参数是可以被多次赋值的
     var num1 = 10
     var num2 = 20
     //调用时必须用&
     swapValues(&num1, &num2)
        
     //简化
     func swapValues(_ v1: inout Int, _ v2: inout Int) {
         (v1, v2) = (v2, v1)
     }
    

函数重载(Function Overload)

  1. 规则
    1. 函数名相同
    2. 参数个数不同   参数类型不同   参数标签不同
  2. 举例

     func sum(v1: Int, v2: Int) -> Int {
         v1 + v2
     }
     //参数个数不同
     func sum(v1: Int, v2: Int, v3: Int) -> Int {
         v1 + v2 + v3
     } 
     // 参数类型不同
     func sum(v1: Int, v2: Double) -> Double {
         Double(v1) + v2
     } 
     // 参数类型不同
     func sum(v1: Double, v2: Int) -> Double {
         v1 + Double(v2)
     } 
     // 参数标签不同
     func sum(_ v1: Int, _ v2: Int) -> Int {
         v1 + v2
     } 
     // 参数标签不同
     func sum(a: Int, b: Int) -> Int {
         a + b
     } 
        
     sum(v1: 10, v2: 20) // 30
     sum(v1: 10, v2: 20, v3: 30) // 60
     sum(v1: 10, v2: 20.0) // 30.0
     sum(v1: 10.0, v2: 20) // 30.0
     sum(10, 20) // 30
     sum(a: 10, b: 20) // 30
    

函数重载注意点

  1. 返回值类型与函数重载无关

     func sum(v1: Int, v2: Int) -> Int { v1+ v2}
     func sum(v1: Int, v2: Int) {}
        
     //报错,上面两个函数不是重载
     sum(v1:10,v2:20)
    
  2. 默认参数值和函数重载一起使用产生二义性时,编译器并不会报错(在C++中会报错)

     func sum(v1: Int, v2: Int) -> Int {
         v1 + v2
     }
     func sum(v1: Int, v2: Int, v3: Int = 10) -> Int {
         v1 + v2 + v3
     }
     // 会调用sum(v1: Int, v2: Int) 
     sum(v1: 10, v2: 20)
    
  3. 可变参数、省略参数标签、函数重载一起使用产生二义性时,编译器有可能会报错

     func sum(v1: Int, v2: Int) -> Int {
         v1 + v2
     }
     func sum(_ v1: Int, _ v2: Int) -> Int {
         v1 + v2
     }
     func sum(_ numbers: Int...) -> Int {
         var total = 0
         for number in numbers {
             total += number
         }
         return total
     }
     //ambiguous这个无误要牢记,代表二义性,歧义
     // error: ambiguous use of 'sum'
     sum(10, 20)
    

内联函数(Inline Function)

  1. 如果开启了编译器优化(Release模式默认会开启优化),编译器会自动将某些函数变成内联函数,将函数调用展开成函数体

    1.png

  2. 哪些函数不会被自动内联?
    1. 函数体比较长
    2. 包含递归调用
    3. 包含动态派发
  3. @inline

     // 永远不会被内联(即使开启了编译器优化)
     @inline(never) func test() {
         print("test")
     }
     // 开启编译器优化后,即使代码很长,也会被内联(递归调用函数、动态派发的函数除外)
     @inline(__always) func test() {
         print("test")
     }
    
  4. 在Release模式下,编译器已经开启优化,会自动决定哪些函数需要内联,因此没必要使用

函数类型(Function Type)

  1. 每一个函数都是有类型的,函数类型由形式参数类型、返回值类型组成

     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,调用时不需要参数标签
    
  2. 函数类型作为函数参数

     func sum(v1: Int, v2: Int) -> Int {
         v1 + v2
     }
     func difference(v1: Int, v2: Int) -> Int {
         v1 - v2
     }
     func printResult(_ mathFn: (Int, Int) -> Int, _ a: Int, _ b: Int) {
         print("Result: \(mathFn(a, b))")
     }
     printResult(sum, 5, 2) // Result: 7
     printResult(difference, 5, 2) // Result: 3
    
  3. 函数类型作为函数返回值

     func next(_ input: Int) -> Int {
         input + 1
     }
     func previous(_ input: Int) -> Int {
         input - 1
     }
     //forward返回值为(Int) -> Int类型
     func forward(_ forward: Bool) -> (Int) -> Int {
         forward ? next : previous
     }
     forward(true)(3) // 4
     forward(false)(3) // 2
    
  4. 返回值是函数类型的函数,叫做高阶函数(Higher-Order Function)

typealias

  1. typealias用来给类型起别名

     typealias Byte = Int8
     typealias Short = Int16
     typealias Long = Int64
        
     //给元祖取别名
     typealias Date = (year: Int, month: Int, day: Int)
     func test(_ date: Date) {
         print(date.0)
         print(date.year)
     }
     test((2011, 9, 10))
        
     typealias IntFn = (Int, Int) -> Int
     func difference(v1: Int, v2: Int) -> Int {
         v1 - v2
     }
     let fn: IntFn = difference
     fn(20, 10) // 10
        
     func setFn(_ fn: IntFn) { }
     setFn(difference)
        
     func getFn() -> IntFn { difference }
    
  2. 按照Swift标准库的定义,Void就是空元组()

     public typealias Void = ()
    

嵌套函数(Nested Function)

  1. 将函数定义在函数内部

     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
    
Table of Contents