[swift 初学]系列之一:流程控制、函数、枚举、可选项、

959 阅读13分钟

流程控制

闭区间、半闭区间、单侧区间以及区间实际类型分类

闭区间: a...b表示 a<= <= b, 闭区间类型为CLosedRange类型

半闭区间a..<b 表示 a <= <b, 半闭区间类型为Range 类型

**单侧区间 ...a/a...**表示从无穷小到a/从a到无穷大,如果用于数组,单侧区间的边界就是数组不越界, 单侧区间类型为PartialRangeThrough类型

a和b可以是数值,也可以是变量

let a = 0;
let b = 3;
for i in a...b {
  
}

var c = 0;
var d = 3;
for i in c...d {
}

应用于数组取值:

let array = ["name", "age", "location"];
for i in array[0...2] {
}

switch的应用

  • swift中的switch和其他语言是不太一致的,**case:后面是不允许大括号的,并且每个case默认都调用了break,**如果想和以前一样,处于叠加的算法,需要在case后面加一个fallthrougn

  • swift中如果switch如果已知参数类型,可以使用.语法直接获取case,例如:

    enum Answer {case right, wrong};
    let answer = Answer.right;
    
    switch answer {
    case .right:
        print("right");
    case .wrong:
        print("wrong");
    default:
        break;
    }
    
  • switch支持区间匹配和元组匹配

    let count = 62;
    
    switch count {
    case 0...3:
        print("0~3");
    default:
        break;
    }
    
    let point = (1,1);
    switch point {
    case (2,2):
        print("point 为 2,2");
    case (-2...2, -2...2):
        print("正方形 包含point");
    default:
        break;
    }
    

值绑定

值绑定模式绑定匹配的值到一个变量或常量,例如

// 将值对应绑定到对应的变量/常量 3->x, 2->y
let point = (3, 2);
switch point {
  case let(x, y) :
  print("this point is \(x)\(y)");
}

where条件语句

where表示条件,用于筛选,常见于switch和for中,例如

// 筛选0~20中的2的倍数
for let i in 0...20 where (i % 2 == 0) {
}

// 在switch值绑定时,筛选条件
let point = (1, -1);
switch point {
  case let(x,y) where x == y:
  	print(x,y);
  case let (x,y ) where x > y :
  	print(x,y);
  default:
  	break;
}

// switch 值绑定时,筛选
let age = 20;
switch age {
case let x where x > 10 :
    print("大于10:" + "\(x)");
case let x where x < 10 :
    print("小于10:" + "\(x)");
default:
    break;
}

标签语句

标签语句类似于goto语句,可以设置代码标签,然后直接使用标签跳转, 但是只适用于循环语句中,例如for和while

outer: for i in 0...3 {
    for j in 4...9 {
        if j == 6 {
            continue outer;
        }
        else {
            print(j);
        }
    }
}

guard 语句

语句为:guard + 条件 + else {}; 当条件为false时,执行代码,用于退出作用域操作,例如判断条件退出的操作

代码内必须包含退出作用域的关键字,例如return/break/continue等

func check(name:String?,pwd:String?) -> Bool {
    guard name != nil else {
        print("name 为 nil");
        return false;
    }
    
    guard pwd != nil else {
        print("pwd 为 nil");
        return false;
    }
    
    print("\(name!)\(pwd!) 通过验证");
    return true;
}

let name : String? = nil;
let pwd : String? = "pwd";
check(name: name, pwd: pwd);

函数

函数注释

和oc中快捷键一样 command + option + /,不同的是OC使用/** */表示,swift使用///表示文档注释

参数标签 (传参时显示参数名称)

类似C++/Dart中的语法,传参时设置参数标签可以显示参数名,默认格式参数名就是参数标签,可以自定义,也可以省略.主要是方便调用明确参数含义

// 默认情况 age 即是参数标签,又是形参
func test (age : Int, name : String) {
    print(name, age);
}

test(age: 10, name: "李晨光");

// 设置参数标签,其中 age ,name 为参数标签, a和n为参数名,函数内部使用n和a表示参数
func test2 (age a : Int, name n : String) {
    print(n, a);
}

test2(age: 10, name: "李晨光");

// 设置忽略参数标签
func test3(_ age : Int, _ name : String) {
    print(name, age);
}

test3(10, "李晨光");

参数默认值

swift允许设置参数默认值,和c++中默认参数值不同的时,c++设置默认参数必须是从右到左设置,不允许间隔,swift允许单独设置特定的参数的值,原因是swift支持参数标签,不会由于设置默认值导致函数传参导致歧义

func test (age : Int, name : String, location : String = "石家庄") {
    print(name, age, location);
}

test(age: 10, name: "李晨光");

// 只设置中间也是允许的
func test (age : Int, name : String = "李晨光", location : String) {
    print(name, age, location);
}

test(age: 10, location: "石家庄");

可变参数

swift允许设置数量可变的参数,但是 只允许设置最多一个,访问数据使用下标,类似c++中的数组参数

// prift函数的定义 Any表示任意类型的参数, ...表示数量可变
public func print(_ items: Any..., separator: String = " ", terminator: String = "\n")

// 表示多个Int类型参数
func test(params:Int...) {
    for i in params {
        print("参数" + "\(i)");
    }
}

test(params: 1,2,3,4,5);

prift()函数查看源码就是使用可变参数实现的

输入输出参数(inout)

作用类似于地址传递(指针传递),实现原理就是地址传递,不同之处是使用 inout关键字标识.

func test6 (age : inout Int) {
    age += 10;
}

test6(age: &age);

函数重载(overload)

函数返回值不同不能成为重载,并且默认参数值和函数重载一起使用时,容易造成二义性,c++会编译报错,swift不会,但是不建议这样使用

内联函数

swift中的内联函数是编译器优化时实现的,不建议主动设置内联函数.c++中也有内联函数的概念,使用inline标识,

swift 通过 **@inline(never)/@inline(__always)**控制是否内联

// 
@inline(never) func test() {
}

// 开启编译器优化后,@inline(__always) 可以设置使用内联
@inline(__always) func test() {
  
}

swift中是不建议开发者直接使用@inline标识修改内联函数的.

函数类型

函数类型是有函数返回值类型参数值类型构成的.

常用于函数作为函数参数,或者定义函数变量时使用

// 该函数的函数类型为 (Int->Int)
func test(age : Int) -> Int {
  
}

// 可以表示成()->Int/void -> Int
func test1(age : Int) -> void {
}

typealias

用于定义别名,例如函数别名,变量别名

枚举

基本使用

相比其他语言中的枚举,Swift的枚举功能更加强大,支持String Int float,Boolean等类型的值,需要在定义时声明类型即可.不要求必须提供值

enum name {
  case xxx
  case xxx
}

enum Score : String {
  case A
  case B
}

枚举的遍历

如果想获取包含枚举所有成员的一个集合,需要遵循CaseIterable协议,生成allCases属性,例如

enum Beverage : CaseIterable {
    case coffer, tea, juice
}

for i in Beverage.allCases {
    print(i);
}

关联值

枚举成员可以指定任意类型的关联值存储到枚举成员中,例如

// Score 为定义的枚举类型
enum Score {
  // num 和 grade是定义的成员名,关联值类型分别是Int和String
  case num(score : Int);
  case grade(grade : String)
}

// 都可以作为成绩使用
let myscore = Score.num(score: 100);
let myGrade = Score.grade(grade: "A");

// 第二个例子
// 定义一个二维码枚举
enum Barcode {
  // upc和qrcode是成员值,关联类型分别是元组和字符串
    case upc(Int, Int, Int, Int)
    case qrCode(String)
}

// 和值绑定一起使用,
switch productBarcode {
case .upc(let numberSystem, let manufacturer, let product, let check):
    print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
case .qrCode(let productCode):
    print("QR code: \(productCode).")
}

// 定义所有的值绑定类型都是let, 可以将常量关键字前提,表是整个值绑定的是常量值
switch productBarcode {
case let .upc(numberSystem, manufacturer, product, check):
    print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
case let .qrCode(productCode):
    print("QR code: \(productCode).")
}

关联值经常和值绑定属性一起使用

原始值

枚举成员可以被默认值(称为原始值)预填充,这些原始值的类型必须相同。.rawValue()可以获取原始值

// 设置的默认值称为原始值,
enum ASCIIControlCharacter : Character {
  case tab = "\t"
  case lineFeed = "\n"
  case carriageReturn = "\r"
}

隐式原始值:

Int和String会自动分配默认值,String以case 枚举值的名称作为值,Int类型值是从0开始递增

使用原始值初始化枚举值:

如果在定义枚举类型的时候使用了原始值,那么将会自动获得一个初始化方法,这个方法接收一个叫做 rawValue 的参数,参数类型即为原始值类型,返回值则是枚举成员或 nil。你可以使用这个初始化方法来创建一个新的枚举实例。由于可能返回nil, 返回类型是可选类型

enum Planet: Int {
    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}

let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet 类型为 Planet? 值为 Planet.uranus

let possiblePlanet = Planet(rawValue: 12)// 返回nil

关联值和原始值,在枚举值内存分配上的差异

数据存储位置不同,关联值的值存储在枚举的内存中,原始值不存储在枚举内存中, 使用关联值的枚举类型,内存分配大小和关联的对象有关 使用原始值的枚举类型,内存只分配一字节,和成员值类型无关,因为原始值无论占据多大内存空间,都不是直接存储在枚举内存中的,只是在.rawValue时再获取

// Score 为定义的枚举类型
enum Score {
  // num 和 grade是定义的成员名,关联值类型分别是Int和String
    case num(chinese : Int, math: Int, english: Int); // 3 * Int64(8) = 24字节
    case grade(grade : String) // 1字节
}

let myScore = Score.num(chinese: 100, math: 100, english: 100);
MemoryLayout.stride(ofValue: Int()); // 8字节

MemoryLayout.stride(ofValue: myScore); // 32字节 使用25字节,由于内存对齐格式是8字节,所以分配32字节
MemoryLayout.size(ofValue: myScore); // 25字节 3 * Int + 1字节(标识case 是哪个类型)
MemoryLayout.alignment(ofValue: myScore); // 8字节


enum Score2 : String {
  // num 和 grade是定义的成员名,关联值类型分别是Int和String
    case num 
    case grade 
}

enum Score3 : Int {
  // num 和 grade是定义的成员名,关联值类型分别是Int和String
    case num 
    case grade
}

let myScore2 = Score2.num;

MemoryLayout.stride(ofValue: myScore2); // 1
MemoryLayout.size(ofValue: myScore2); // 1
MemoryLayout.alignment(ofValue: myScore2); // 1

let myScore3 = Score3.num;

MemoryLayout.stride(ofValue: myScore3); // 1
MemoryLayout.size(ofValue: myScore3); // 1
MemoryLayout.alignment(ofValue: myScore3); // 1

递归枚举

递归枚举是一种枚举类型,它有一个或多个枚举成员使用该枚举类型的实例作为关联值。使用递归枚举时,编译器会插入一个间接层。你可以在枚举成员前加上 indirect 来表示该成员可递归。

enum ArithmeticExpression {
    case number(Int)
  
  // 可递归成员
    indirect case addition(ArithmeticExpression, ArithmeticExpression)
    indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}

// 所有成员可递归
indirect enum ArithmeticExpression {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
    case multiplication(ArithmeticExpression, ArithmeticExpression)
}

let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))


func evaluate(_ expression: ArithmeticExpression) -> Int {
    switch expression {
    case let .number(value):
        return value
    case let .addition(left, right):
        return evaluate(left) + evaluate(right)
    case let .multiplication(left, right):
        return evaluate(left) * evaluate(right)
    }
}

print(evaluate(product))
// 打印“18”

嵌套枚举

枚举类型是可嵌套的,例如

// 以RPG游戏中的每个角色为例,每个角色能够拥有武器,因此所有角色都可以获取同一个武器集合。而游戏中的其他实例则无法获取这些武器(比如食人魔,它们仅使用棍棒)。
enum Character {
  enum Weapon {
    case Bow
    case Sword
    case Lance
    case Dagger
  }
  enum Helmet {
    case Wooden
    case Iron
    case Diamond
  }
  case Thief
  case Warrior
  case Knight
}

// 现在,你可以通过层级结构来描述角色允许访问的项目条。
let character = Character.Thief
let weapon = Character.Weapon.Bow
let helmet = Character.Helmet.Iron

参考文章:

swiftGG翻译组

官方文档中文翻译

可选项

基本定义

可选类型是一种数据类型用于处理值缺失的情况,swift中数据类型的变量是不允许设置nil的,使用可选类型后可以设置

可选类型使用 "?"标识,在数据类型后面添加,表示该数据类型是可选类型,默认值为nil

let a : Int? = 10; // 合法
let a : Int? = 10; // 合法
let a : Int? = nil; // 合法
let a : Int = nil; // 不合法,

let a : Int? = 10; // 等价于
let a: Optional<Int> = 10; // 证明可选类型是一种数据类型

Optional 是一个含有两种情况的枚举,None 和 Some(T),可以理解是通过枚举关联值实现的

源码如下:

enum Optional<T> : Reflectable, NilLiteralConvertible {
    case None
    case Some(T)
    init()
    init(_ some: T)

    /// Haskell's fmap, which was mis-named
    func map<U>(f: (T) -> U) -> U?
    func getMirror() -> MirrorType
    static func convertFromNilLiteral() -> T?
}

强制解包

由于可选类型是一种对象类型,内部存储的变量值不能直接使用,所以需要解包获取数据. 使用!强制解包可选项包装的对象,解包语法为:可选对象值后添加"!";不允许对nil做解包操作,崩溃

let a : Int? = 10; // a是Optional(10) 可选类型,直接使用会报错
let b = a + 10; // 报错,需要解包
let b = a! + 10; // 解包后获取到值再操作,

自动解析(不常用)

在声明可选变量时使用感叹号(!)替换问号(?)。这样可选变量在使用时就不需要再加一个感叹号(!)来获取值,它会自动解析。但是需要处理为nil的情况

var myString:String!

myString = "Hello, Swift!"

if myString != nil {
   print(myString)
}else{
   print("myString 值为 nil")
}

可选绑定(Optional binding)

使用可选绑定(optional binding)来判断可选类型是否包含值, 如果包含就把值赋给一个临时常量或者变量 并返回ture, 否则返回false,

常用于条件判断语句if/while中

let name : String? = nil;
if let constantName = name {
  print(constantName); // 可以打印可选类型包含的值,条件语句判断时,隐藏了一层解包操作,就是可选绑定
}
else {
  // name 为nil 执行
}

可选链

**定义:**通过在想调用的属性、方法,或下标的可选值后面放一个问号(?),可以定义一个可选链

**作用:**可选链式调用是一种可以在当前值可能为 nil 的可选值上请求和调用属性、方法及下标的方法。如果可选值有值,那么调用就会成功;如果可选值是 nil,那么调用将返回 nil

// 定义类
class Person {
    var residence: Residence?
}

class Residence {
    var numberOfRooms = 1
}

let john = Person(); // 实例化

let roomCount = john.residence!.numberOfRooms
// 如果residence为nil,会出发运行时错误,

// 如果通过?调用可选链,当为nil时终端调用并返回nil,
if let roomCount = john.residence?.numberOfRooms {
    print("John's residence has \(roomCount) room(s).")
} else {
    print("Unable to retrieve the number of rooms.")
}

swiftGG 可选链

空合并运算符 ??

形式如 a ?? b 的称为空合并运算符, 语句含义是:如果a==nil,返回b; 否则返回a

要求:

  1. a必须是可选类型,b类型不限制

  2. a和b的数据类型必须相同,例如都是Int或者String

  3. 如果b是可选类型,则返回的a也是可选类型,否则返回的a会自动解包,例如

    let a : Int? = nil;
    let b : Int = 10;
    print(a ?? b); // 10 
    
    let a : Int? = 20;
    let b : Int = 10;
    print(a ?? b); // 20 
    
    
    let a : Int? = 20;
    let b : Int? = 10;
    print(a ?? b); // Optional(20) 
    

    总结

    初学swift第二天,以笔记整理的方式加深学习过程中的印象.如有理论性的知识错误,欢迎各位多多指教