Swift基础语法(二)

2020-06-02 17:24:19 浏览数 (1)

函数

Swift中的函数,其实就相当于Objective-C中的方法。函数的格式如下:

代码语言:javascript复制
func 函数名(参数,列表)  -> 返回值类型 {
    代码块
    return 返回值
}

有以下几点说明:

1,func 是定义函数的关键字

2,参数列表中的多个参数之间,可以使用英文逗号 , 分割,也可以没有参数

3,使用 -> 指向返回值类型

4,如果函数没有返回值,则 -> 返回值类型 部分可以省略

常见的函数类型

没有参数,没有返回值:

代码语言:javascript复制
//写法一:官方标准写法
func drinkWater() -> Void {
    print("drink water 111")
}

//写法二:如果没有返回值,Void可以写成()
func drinkWater1() -> () {
    print("drink water 222")
}

//写法三:如果没有返回值,后面的内容可以都不写
func drinkWater2() {
    print("drink water 333")
}

//调用函数
drinkWater()
drinkWater1()
drinkWater2()

有参数,没有返回值:

代码语言:javascript复制
func call(phoneNumber : String) {
    print("打电话给(phoneNumber)")
}
call(phoneNumber: "18868876045")

没有参数,有返回值:

代码语言:javascript复制
func myName() -> String {
    return "norman"
}
let name = myName()

有参数,有返回值:

代码语言:javascript复制
func plus(a : Int, b : Int) -> Int {
    return a   b;
}
let result = plus(a: 3, b: 3)

返回值为复杂类型(这里以元组为例):

代码语言:javascript复制
func triple(info : String) -> (name : String, age : Int) {
    let infos = info.components(separatedBy: ",")
    let name = infos[0]
    let age = Int(infos[1])
    return (name, age!)
}

let result = triple(info: "norman,20")
print(result) // (name: "norman", age: 20)

函数的使用注意

1,函数的参数虽然没有使用let或者var修饰,但是它是常量,不能在函数内修改

代码语言:javascript复制
func saySomething(content : String) {
    //报错:Cannot assign to value: 'content' is a 'let' constant
    content = "666"  
    print(content)
}

2,每一个函数的形式参数都包含形式参数标签和形式参数名两部分

  • 形式参数标签用在调用函数的时候
  • 形式参数名用在函数的实现当中
  • 在调用函数的时候,每一个形式参数前边都会有一个形式参数标签
  • 默认情况下,形式参数使用它们的形式参数名作为形式参数标签
  • 如果不想要形式参数标签,可以在形式参数名称前加上 _
代码语言:javascript复制
//这里的info1和info2就是形式参数标签
//name和age是形式参数名称
func personalInfo(info1 name : String, info2 age : Int) {
    //在函数的实现中使用形式参数名称
    print("姓名:(name),年龄:(age)")
}
//在函数调用的时候使用形式参数标签
personalInfo(info1: "norman", info2: 23)


//下面是默认写法
//此时,name和age既是形式参数标签,也是形式参数名称
func personalInfo(name : String, age : Int) {
    //在函数内部实现的时候,name和age是形式参数名称
    print("姓名:(name),年龄:(age)")
}
//在函数调用的时候,name和age是形式参数标签
personalInfo(name: "norman", age: 24)


//如果不想要形式参数标签,可以在形式参数名称前面加 _
func personalInfo(_ name : String, _ age : Int) {
    print("姓名:(name),年龄:(age)")
}
//在函数调用的时候,没有形式参数标签
personalInfo("norman", 24)

3,如果需要在没有传入具体的参数的时候设置参数的默认值,可以使用默认参数

代码语言:javascript复制
//如果不想要形式参数标签,可以在形式参数名称前面加 _
func personalInfo(_ name : String = "lavie", _ age : Int = 28) {
    print("姓名:(name),年龄:(age)")
}

personalInfo() // 姓名:lavie,年龄:28
personalInfo("norman", 24) // 姓名:norman,年龄:24

4,可变参数

  • 在Swift中,函数参数是可以变化的,可以接受不确定数量的参数
  • 可变参数必须具备相同的类型
  • 可以通过在参数类型后面加入 ... 的方式来指示可变参数
代码语言:javascript复制
func plus(numbers : Int...) -> Int {
    var sum = 0
    for i in numbers {
        sum  = i
    }
    return sum
}

plus()
plus(numbers: 1)
plus(numbers: 1, 2)
plus(numbers: 1, 2, 3)

5,指针的传递

默认情况下,函数的参数是值传递。如果想改变外面的变量,则需要传递变量的地址。

代码语言:javascript复制
//交换值
func swapInt(a : inout Int, b : inout Int) {
    let temp = a
    a = b
    b = temp
}

var a = 6
var b = 8
print("a=(a), b=(b)") // a=6, b=8
swap(&a, &b) // 将地址传递进来
print("a=(a), b=(b)") // a=8, b=6

函数的类型

我们之前介绍的数组Array、字典Dictionary等,都是值类型,而函数是引用类型

每个函数都有属于自己的类型,一个函数的类型是由该函数的参数类型返回类型决定的。

有了函数类型以后,就可以把函数类型像Int、Array一样来使用了。

下面的例子定义了additionMethod和multiplicationMethod两个函数,这两个函数都传入了两个Int类型的参数,返回一个Int类型值,因此这两个函数的类型都是(Int, Int) -> Int

代码语言:javascript复制
func additionMethod(a : Int, b : Int) -> Int {
    return a   b;
}

func multiplicationMethod(a : Int, b : Int) -> Int {
    return a * b;
}

接下来我们来看一下函数的简单赋值:

代码语言:javascript复制
//初始化一个函数类型变量,并赋初始值
var mathMethod = additionMethod
mathMethod(2, 3) // 5
//给函数类型变量赋其他值
mathMethod = multiplicationMethod
mathMethod(2, 3) // 6

函数也可以作为一个函数的参数:

代码语言:javascript复制
func additionMethod(a : Int, b : Int) -> Int {
    return a   b;
}

func multiplicationMethod(a : Int, b : Int) -> Int {
    return a * b;
}

//函数作为参数
func printResult(a : Int, b : Int, mathMethod : (Int, Int) -> Int) {
    print(mathMethod(a, b))
}

printResult(a: 3, b: 4, mathMethod: additionMethod) // 7
printResult(a: 3, b: 4, mathMethod: multiplicationMethod) // 12

函数还可以作为一个函数的返回值:

代码语言:javascript复制
func additionMethod(a : Int, b : Int) -> Int {
    return a   b;
}

func multiplicationMethod(a : Int, b : Int) -> Int {
    return a * b;
}

//函数作为返回值
func mathMethod(a : Int) -> (Int, Int)->Int {
    if a > 10 {
        return additionMethod
    }
    return multiplicationMethod
}

var resultMethod = mathMethod(a: 11)
resultMethod(6, 8) // 14

闭包

首先来看一段示例:

代码语言:javascript复制
//计算一个数的平方

//函数写法
func square(a : Int) -> Int {
    return a * a
}
square(a: 6)

//闭包写法
let sq = {
    (a : Int) -> Int in
    return a * a
}
sq(6)

闭包的写法模式:

代码语言:javascript复制
let 闭包名 = {
    函数类型 in
    需要执行的代码块
}

闭包能够捕获和存储定义在其上下文中的任何常量和变量,即闭合并包裹那些常量和变量,因此被称为“闭包”。

闭包是可以被传递和引用的一个独立模块

闭包分为以下三种:

  • 全局函数是一个有名字但不会捕获任何值的闭包
  • 内嵌函数(即外层函数中嵌套了该函数)是一个有名字且能从上层函数捕获值的闭包
  • 闭包表达式是一个轻量级语法,它是一个可以捕获其上下文中常量或者变量值的没有名字的闭包。

闭包跟函数一样,也是引用类型

闭包表达式

闭包表达式的语法有如下的一般形式:

代码语言:javascript复制
{
    (参数罗列) -> (返回值类型) in
    需要执行的代码语句
}

说明如下:

1,闭包表达式由一对花括号 {} 开始与结束

2,由 in 关键字将闭包分割成两部分:参数与返回值(可以理解成函数的类型)与闭包体

3,闭包中的参数不同于函数中的参数的一点是,闭包中的参数不能提供默认值,而函数中的参数可以提供默认值。其他都是一样的。

闭包的简写

首先我们来看一个例子,从一个数组中筛选出合适的数据组成新的数组:

代码语言:javascript复制
//首先定义一个函数,函数有两个参数,第一个参数是需要筛选的数组,第二个参数是筛选条件函数
func getNewList(scores : [Int], checkMethod : (Int)->Bool) -> [Int] {
    var newList = [Int]()
    for num in scores {
        if checkMethod(num) {
            newList.append(num)
        }
    }
    return newList
}

//函数是一中特殊的闭包,所以上面的筛选条件函数可以写成如下闭包形式
let newList = getNewList(scores: [55, 60, 71, 86, 98, 100], checkMethod: {(numInt : Int) -> Bool in return numInt > 80})
print(newList) // [86, 98, 100]
代码语言:javascript复制
let newList = getNewList(scores: [55, 60, 71, 86, 98, 100], checkMethod: {(numInt : Int) -> Bool in return numInt > 80})

上面的这行代码,是闭包的完整写法。下面来看一些简写的写法。

第一层简写:省略掉闭包类型中的 -> 与 返回值类型 ,因为根据后面的表达式可以推断出返回值是一个Bool

代码语言:javascript复制
let newList = getNewList(scores: [55, 60, 71, 86, 98, 100], checkMethod: {(numInt : Int) in return numInt > 80})

第二层简写:省略掉参数的类型和小括号,因为参数的类型可以根据定义闭包时的函数类型进行推断

代码语言:javascript复制
let newList = getNewList(scores: [55, 60, 71, 86, 98, 100], checkMethod: {numInt in return numInt > 80})

第三层简写:省略掉 return 关键字,在单行闭包的时候,return关键字可以省略

代码语言:javascript复制
let newList = getNewList(scores: [55, 60, 71, 86, 98, 100], checkMethod: {numInt in numInt > 80})

第四层简写:省略掉参数的声明和 in 关键字,使用参数名称缩写$0

代码语言:javascript复制
let newList = getNewList(scores: [55, 60, 71, 86, 98, 100], checkMethod: {$0 > 80})

Swift 提供了参数名称的缩写功能,直接通过0、1、

三种常见闭包类型

1,尾随闭包

尾随闭包是最常见的一种闭包类型。

尾随闭包是一个在函数调用的时候,书写在函数括号之后的闭包表达式。当函数中最后一个参数是闭包表达式的时候,在调用该函数的时候,就可以将作为最后一个参数的闭包表达式写成尾随闭包

代码语言:javascript复制
func getNewList(scores : [Int], checkMethod : (Int)->Bool) -> [Int] {
    var newScoreList = [Int]()
    for score in scores {
        if checkMethod(score) {
            newScoreList.append(score)
        }
    }
    return newScoreList
}

//调用函数的时候,不使用尾随闭包
getNewList(scores: [41,62,83], checkMethod: {
    (score : Int) -> Bool in
    return score > 60
})

//调用函数的时候,使用尾随闭包
getNewList(scores: [41, 62, 83]) { (score) -> Bool in
    return score > 60
}

2,逃逸闭包

传入函数的闭包如果在函数结束之后才会被调用,那么这个闭包就叫做逃逸闭包。

声明一个接收闭包作为形式参数的函数时,可以在形式参数的类型之前写上 @escaping 来明确闭包是允许逃逸的。

逃逸闭包会在函数结束之后才执行

代码语言:javascript复制
//逃逸闭包:闭包可以超出函数的范围来调用,也就是说可以在函数执行结束之后才会被调用

//定义一个数组来存放没有参数没有返回值的闭包
var closureArray = [()->Void]()

//定义一个函数,接收非逃逸闭包作为参数
func nonEscapeClosure(closure : () -> Void) {
    closure()
}

//定义一个函数,接收逃逸闭包作为参数
func escapeClosure(closure : @escaping () -> Void) {
    //将闭包存储在一个数组里面,并不去调用该闭包
    closureArray.append(closure)
}

var x = 10
print(x) // 10

nonEscapeClosure {
    x = 100
}
print(x) // 100,闭包在nonEscapeClosure函数里面执行了

escapeClosure {
    x = 200
}
print(x) // 100,闭包逃逸了,不会在escapeClosure函数里面执行

closureArray.first?()
print(x) // 200,在函数外面调用了闭包

逃逸闭包常用于异步回调。比如说网络请求数据完成之后的回调,就可以使用逃逸闭包来实现。

3,自动闭包

自动闭包是一种自动创建的闭包,用于包装函数参数的表达式。

自动闭包不接收任何的参数,被调用时会返回被包装在其中的表达式的值。

在形式参数的类型之前加上 @autoclosure 关键字标识这是一个逃逸闭包。

代码语言:javascript复制
func printBoolValue(condition : @autoclosure()->Bool) {
    if condition() {
        print("is true")
    } else {
        print("is false")
    }
}

/*
 * 这里,Swift会将 2 > 1 这个表达式自动转换成闭包 ()->Bool 
 */
printBoolValue(condition: 2 > 1) // is true

闭包在高阶函数中的应用

闭包是Swift中一个很重要的知识点,不仅在开发中能够帮助解决很多问题(比如逆向传值),而且在很多官方系统库方法中都能看到它的身影,尤其是在集合中提供了很多函数来对元素进行访问和操作,这些函数大量使用了闭包。

1,sort排序

代码语言:javascript复制
var names = ["norman", "lavie", "bruce", "lily"]

// 默认是升序
names.sort() // ["bruce", "lavie", "lily", "norman"]

// 降序
names.sort { (name1, name2) -> Bool in
    return name1 > name2
} // ["norman", "lily", "lavie", "bruce"]

说明如下:

代码语言:javascript复制
names.sort(by: <#T##(String, String) throws -> Bool#>)

可以看到,闭包 by 在 names.sort 函数中是最后一个参数,所以,闭包 by 是一个尾随闭包。

2,for-each遍历

代码语言:javascript复制
var names = ["norman", "lavie", "bruce", "lily"]

names.forEach { (name) in
    print(name)
}

3,filter筛选

代码语言:javascript复制
var names = ["norman", "lavie", "bruce", "lily"]

let filterNames = names.filter { (name) -> Bool in
    return name.starts(with: "l") // 首字母是l
}

print(filterNames) // ["lavie", "lily"]

4,map变换

闭包会返回一个变换后的元素,然后将所有变换后的元素组成一个新的数组。

代码语言:javascript复制
var names = ["norman", "lavie", "bruce", "lily"]

names.map { (name) -> String in
    return "My name is "   name
}.forEach { (newName) in
    print(newName)
}

打印结果为:

代码语言:javascript复制
My name is norman
My name is lavie
My name is bruce
My name is lily

5,reduce操作

map和filter方法都是通过一个已经存在的数组,生成一个新的、经过修改的数组;然而有时候我们需要把所有元素的值取出来进行一个融合操作,并返回一个融合后的结果值,此时就需要使用reduce函数。

代码语言:javascript复制
var names = ["norman", "lavie", "bruce", "lily"]

//reduce函数的第一个参数是返回值的初始化值,result是中间结果,name是遍历集合每次传进来的值
let resultString = names.reduce("") { (result, name) -> String in
    return result   name   ","
}
print(resultString)

6,allSatisfy检查所有元素是否符合条件

注意区分filter函数和allSatisfy函数:filter函数是筛选出符合条件的元素组成一个新的数组;而allSatisfy函数则是检查所有元素是否满足某个条件,全部满足则返回true,否则返回false。

代码语言:javascript复制
var ages = [11, 22, 33, 44, 55, 66]

// 判断数组中的所有元素是否全部大于18
let isAllAdult = ages.allSatisfy { (age) -> Bool in
    return age > 18
}

// 可以简写为下面的形式
let isAllAdult = ages.allSatisfy { $0 > 18 }

print(isAllAdult) // false

7,compactMap

代码语言:javascript复制
var ages = [11, 22, 33, 44, 55, 66]

//检查数组中的每个元素是否是偶数
let a = ages.compactMap { (age) -> Bool? in
    return age % 2 == 0
}
print(a) // [false, true, false, true, false, true]

compactMap函数既可用于数组,也可用于字典。

compactMap函数的作用不是进行筛选,而是对原集合中的每个元素进行操作之后组成一个新的集合

需要注意的是,该函数闭包中的返回值是一个可选型,也就是说,如果对原集合中的元素进行操作之后,元素变为了nil,那么就会过滤掉这个元素

8,mapValues

代码语言:javascript复制
var dic = [
    "age1" : 1,
    "age2" : 2,
    "age3" : 3,
    "age4" : 4,
    "age5" : 5,
    "age6" : 6
]

let newDic = dic.mapValues { (value) -> String in
    "(value)岁"
}
print(newDic) // ["age6": "6岁", "age1": "1岁", "age2": "2岁", "age4": "4岁", "age3": "3岁", "age5": "5岁"]

mapValues 函数是字典中的一个函数。该函数可以遍历字典中的每一个value值,并对其执行操作进行重新组装,然后返回一个改变value值之后的新的字典

9,compactMapValues

代码语言:javascript复制
var dic = [
    "age1" : 1,
    "age2" : 2,
    "age3" : 3,
    "age4" : 4,
    "age5" : 5,
    "age6" : 6
]

let newDic = dic.compactMapValues { (value) -> String? in
    value > 3 ? "(value)岁" : nil
}
print(newDic) // ["age5": "5岁", "age6": "6岁", "age4": "4岁"]

compactMapValues 函数仅用于字典类型,是将compactMap函数和mapValues 函数融合在了一起。

也就是说,compactMapValues 函数会遍历字典中的每一个value值,并对其执行操作进行重新组装,如果组装之后value值变为了nil,那么将会过滤掉这个键值对,最终会返回一个改变value值之后的新的字典

10,first(where:) 筛选出第一个符合某种条件的元素

代码语言:javascript复制
var names = ["norman", "lavie", "liwei", "lily", "bruce", "lee"]
//找到以字母l开头的第一个元素
let element = names.first { (name) -> Bool in
    name.hasPrefix("l")
}
print(element) // Optional("lavie")
print(element!) // lavie

11,last(where:) 筛选出最后一个符合条件的元素

代码语言:javascript复制
var names = ["norman", "lavie", "liwei", "lily", "bruce", "lee"]

let element2 = names.last { (name) -> Bool in
    name.hasPrefix("l")
}
print(element2) // Optional("lee")
print(element2!) // lee

枚举

在C和OC中,枚举成员在被创建的时候会被赋予一个默认的整数值,枚举的本质就是一组整型值。

而在Swift中,枚举是更加灵活的,第一,你不必给每一个枚举成员提供一个值;第二,如果需要给枚举成员提供值,那么可以提供的值类型包括字符、字符串、整型值、浮点值等。

枚举的定义

C和OC中,枚举成员在被创建的时候会被赋予一个默认的整数值,枚举的本质就是一组整型值。

而在Swift中,枚举是更加灵活的,第一,你不必给每一个枚举成员提供一个值;第二,如果需要给枚举成员提供值,那么可以提供的值类型包括字符、字符串、整型值、浮点值等。

定义方式一:

代码语言:javascript复制
enum CompassPoint {
    case East
    case West
    case North
    case South
}

这里的 case 关键词表明新的一行成员值将被定义。

与C/OC不同的一点是,Swift的枚举成员在被创建的时候不会被赋予一个默认的整数值。比如上面的这个例子中,East、West、North和South不是隐式的等于0、1、2、3。

定义方式二:

代码语言:javascript复制
enum CompassPoint {
    case East, West, North, South
}

枚举的多个成员值可以出现在同一行上,此时只需要只用一个case 关键词即可。

枚举的赋值

枚举类型赋值可以是字符、字符串、整型、浮点型。

如果要给枚举类型赋值,则必须要在枚举类型后面明确说明值的具体类型

代码语言:javascript复制
enum CompassPoint : Int{
    case East = 1
    case West = 2
    case North = 3
    case South = 4
}

enum CompassPoint : Double{
    case East = 1.0
    case West = 2.0
    case North = 3.0
    case South = 4.0
}

enum CompassPoint : String{
    case East = "East"
    case West = "West"
    case North = "North"
    case South = "South"
}

枚举的类型推断

首先给变量赋一个枚举值:

代码语言:javascript复制
enum CompassPoint : String{
    case East = "East"
    case West = "West"
    case North = "North"
    case South = "South"
}

var a = CompassPoint.East
print(a) // East

然后对该变量进行穷举:

代码语言:javascript复制
switch a {
case CompassPoint.East:
    print("东")
case CompassPoint.West:
    print("西")
case CompassPoint.North:
    print("北")
case CompassPoint.South:
    print("南")
}

上面的这个穷举可以简写为如下(将枚举类型移除):

代码语言:javascript复制
switch a {
case .East:
    print("东")
case .West:
    print("西")
case .North:
    print("北")
case .South:
    print("南")
}

之所以可以将枚举类型给简化掉,是因为根据上下文,系统可以检测到变量a 匹配的值是 CompassPoint 这个枚举类型下面的值。这就是Swift中的枚举类型推断

枚举的原始值

在C/OC中,枚举的本质就是整数。所以C/OC的枚举是有原始值的,并且默认是从0开始。

Swift中的枚举是没有原始值的,但是可以在定义的时候告诉系统让枚举有原始值。

关于设置Swift中枚举的原始值,需要注意以下几点:

  1. 如果原始值是String类型,则原始值是区分大小写的
  2. 通过 rawValue 可以获取原始值
  3. 通过 rawValue 返回的枚举是一个可选型,因为原始值对应的枚举值不一定存在
  4. 如果想指定第一个元素的原始值之后,后面的元素的原始值能够默认 1,则枚举一定是Int类型
代码语言:javascript复制
enum CompassPoint : Int {
    case East = 1
    case West
    case North
    case South
}

let a = CompassPoint.North
print(a) // North
print(a.rawValue) // 3

这个例子中,枚举的原始值设置的是Int类型,并且设置了第一个枚举值的原始值是1,所以North的原始值就是3。

代码语言:javascript复制
enum CompassPoint : Int {
    case East = 1
    case West
    case North
    case South
}

//通过原始值来获取对应的枚举值
//这里获取的枚举值是一个可选型,因为原始值对应的枚举值不一定存在
let b = CompassPoint(rawValue: 4)
let c = CompassPoint(rawValue: 5)

枚举的遍历

代码语言:javascript复制
enum CompassPoint : CaseIterable {
    case East
    case West
    case North
    case South
}

for item in CompassPoint.allCases {
    print(item)
}

/*
打印结果为
East
West
North
South
*/

1,如果想要枚举可以被遍历,那么就需要在枚举名字后面写上 : CaseIterable 来允许枚举被遍历。

2,Swift会暴露一个包含对应枚举类型所有情形的集合allCases,遍历枚举实际就是遍历这个集合。

结构体

结构体(struct)是由一系列具有相同类型或者不同类型的数据构成的数据集合。

结构体既可以定义属性(变量、常量),也可以定义方法(函数)。

Swift中的结构体是值类型

结构体的定义语法

代码语言:javascript复制
struct 结构体名称 {
    // 属性和方法
}

举例如下:

代码语言:javascript复制
struct Person {
    var name = "norman"
    var age = 18
    var gentle = "man"
    func singASong() {
        print("《海阔天空》")
    }
}

该例中,定义了一个名叫Person的结构体。这个结构体拥有三个属性(name、age、gentle)和一个方法(singASong)。

结构体实例

实例化结构体的方式一:

实例化结构体最简单的方式就是在结构体名字后面加上(),此时,任何属性都被初始化为默认值。

代码语言:javascript复制
struct Person {
    var name = "norman"
    var age = 18
    var gentle = "man"
    func singASong() {
        print("《海阔天空》")
    }
}

let person1 = Person()
print("我叫(person1.name),我今年(person1.age)岁,性别(person1.gentle)")
//打印结果:我叫norman,我今年18岁,性别man

实例化结构体的方式二:

所有的结构体都会有一个自动生成的成员构造函数来实例化结构体,可以使用它来初始化所有的成员属性。

代码语言:javascript复制
struct Person {
    var name = "norman"
    var age = 18
    var gentle = "man"
    func singASong() {
        print("《海阔天空》")
    }
}

let person2 = Person(name: "Lavie", age: 20, gentle: "male")
print("我叫(person2.name),我今年(person2.age)岁,性别(person2.gentle)")
//打印结果:我叫Lavie,我今年20岁,性别male

访问结构体实例的属性和方法

我们可以使用点语法来访问一个结构体实例的属性和方法。如下:

代码语言:javascript复制
struct Person {
    var name = "norman"
    var age = 18
    var gentle = "man"
    func singASong() {
        print("《海阔天空》")
    }
}

let person1 = Person()
print("我叫(person1.name),我今年(person1.age)岁,性别(person1.gentle)") // 我叫norman,我今年18岁,性别man
person1.singASong() // 《海阔天空》

如果点语法是在等号后面,或者没有等号,那么就是访问;如果点语法在等号前面,就是赋值。如下:

代码语言:javascript复制
struct Person {
    var name = "norman"
    var age = 18
    var gentle = "man"
    func singASong() {
        print("《海阔天空》")
    }
}

var person1 = Person()
print("我叫(person1.name)") // 我叫norman
person1.name = "lavie" // 赋值
print("我叫(person1.name)") // 我叫lavie
person1.singASong() // 《海阔天空》

结构体是值类型

值类型是一种当它被赋值给一个常量或者变量,或者被传递给函数时,会被拷贝的类型。

Swift中的枚举、结构体等都是值类型,它在代码传递中总是会被拷贝

代码语言:javascript复制
struct Person {
    var name = "norman"
}

var person1 = Person()
// 值类型拷贝
var person2 = person1

//此时改变person2,并不会改变person1的值
person2.name = "lavie"

print(person1.name) // norman
print(person2.name) // lavie

Swift中的字符串String、字典Dictionary、数组Array类型,是作为结构体来实现的,这意味着,当String、Dictionary、Array类型的实例被赋值到一个新的常量或者变量,或被传递到一个函数中的时候,其实传递的是拷贝后的值

OC中的NSString、NSArray和NSDictionary,他们是作为类来实现的,所以NSString、NSArray和NSDictionary的实例对象总是作为一个引用,而不是拷贝来实现赋值和传递。

常用的结构体

CGPoint 坐标

代码语言:javascript复制
/* Points. */

public struct CGPoint {

    public var x: CGFloat

    public var y: CGFloat

    public init()

    public init(x: CGFloat, y: CGFloat)
}

CGSize 大小

代码语言:javascript复制
/* Sizes. */

public struct CGSize {

    public var width: CGFloat

    public var height: CGFloat

    public init()

    public init(width: CGFloat, height: CGFloat)
}

CGRect 矩形

代码语言:javascript复制
/* Rectangles. */

public struct CGRect {

    public var origin: CGPoint

    public var size: CGSize

    public init()

    public init(origin: CGPoint, size: CGSize)
}

Swift虽然推荐是面向协议编程,但其也是一门面向对象的语言。

面向对象的语言中很重要的两个概念是:类和对象。对象是类的实例。

Swift中用class关键字来定义类。

定义语法

代码语言:javascript复制
class 类名 { 
    // 定义属性和方法
}

举例:

代码语言:javascript复制
class Person {
    var name = "norman"
    var age = 20
    
    func play() {
        print("play")
    }
}

该例中定义了一个名为Person的类,该类有两个属性和一个方法。

类的实例

类的实例化与结构体的实例化一样,最简单的就是在名称后面加一个小括号(),但是类默认没有成员构造函数。

代码语言:javascript复制
var person = Person()

访问属性和方法

类属性方法的访问和结构体的属性方法的访问是一模一样的。

我们可以使用点语法来访问一个类的实例的属性和方法。如下:

代码语言:javascript复制
class Person {
    var name = "norman"
    var age = 18
    var gentle = "man"
    func singASong() {
        print("《海阔天空》")
    }
}

let person1 = Person()
print("我叫(person1.name),我今年(person1.age)岁,性别(person1.gentle)") // 我叫norman,我今年18岁,性别man
person1.singASong() // 《海阔天空》

如果点语法是在等号后面,或者没有等号,那么就是访问;如果点语法在等号前面,就是赋值。如下:

代码语言:javascript复制
class Person {
    var name = "norman"
    var age = 18
    var gentle = "man"
    func singASong() {
        print("《海阔天空》")
    }
}

var person1 = Person()
print("我叫(person1.name)") // 我叫norman
person1.name = "lavie" // 赋值
print("我叫(person1.name)") // 我叫lavie
person1.singASong() // 《海阔天空》

类是引用类型

与值类型不同,引用类型被赋值到一个常量或者变量,或者被传递到一个函数中的时候,它是不会被拷贝的,而是使用的同一个对某实例的引用。

代码语言:javascript复制
class Person {
    var name = "norman"
}

var person1 = Person()
// 引用类型
var person2 = person1

//此时改变person2,会影响person1的值,因为它俩引用的是同一个对象
person2.name = "lavie"

print(person1.name) // lavie
print(person2.name) // lavie

特征运算符

由于类是引用类型,可能有很多常量或者变量都是引用到了同一个类的实例。有时候需要找出两个常量或者变量是否引用自同一个实例,Swift中提供了两个特征运算符来检查两个常量或者变量是否引用自相同的实例。

  • === 引用自类的同一个实例
  • !== 没有引用自类的同一个实例
代码语言:javascript复制
class Person {
    var name = "norman"
}

var person1 = Person()
var person2 = person1
var person3 = Person()

// 特征运算符
print(person1 === person2) // true
print(person2 === person3) // false

继承

一个类是可以从另外一个类那继承方法、属性和其他的特性的。

当一个类从另外一个类那里继承的时候,继承的类就是子类,被继承的类就是父类。

继承的目的是为了代码复用。

代码语言:javascript复制
//父类
class Person {
    var name = "norman"
    func eat() {
        print("eat")
    }
}

//子类
class Child : Person {
    var school = "杭州市第一小学"
}

//子类
class Adult : Person {
    var company = "杭州魔筷"
}

var person = Person()
var child = Child()
var adult = Adult()

//Child和Adult都继承了其父类Person的方法
person.eat()
child.eat()
adult.eat()

重写(覆写)

所谓重写,就是子类可以对继承自父类的实例方法、类型方法、实例属性、类型属性进行覆盖。

重写需要在前面加上override关键字。

override关键字执行的时候,Swift编译器会检查重写的类的父类(或者父类的父类)是否有与之匹配的声明来提供重写。

代码语言:javascript复制
//父类
class Person {
    var name = "norman"
    func eat() {
        print("eat")
    }
}

//子类
class Child : Person {
    var school = "杭州市第一小学"
    //重写父类的方法
    override func eat() {
        print("child eat")
    }
}

//子类
class Adult : Person {
    var company = "杭州魔筷"
    //重写父类的方法
    override func eat() {
        print("adult eat")
    }
}

var person = Person()
var child = Child()
var adult = Adult()

person.eat() // eat
child.eat() // child eat
adult.eat() // adult eat

如果不想父类的属性或者方法被重写,那么可以通过final关键字来阻止子类的重写行为:

代码语言:javascript复制
//父类
class Person {
    var name = "norman"
    //通过final关键字阻止被重写
    final func eat() {
        print("eat")
    }
}

//子类
class Child : Person {
    var school = "杭州市第一小学"
    //重写父类的方法
    // 此时会报错error: instance method overrides a 'final' instance method
    override func eat() {
        print("child eat")
    }
}

类与结构体的对比

相同点:

  • 都可以定义属性
  • 都可以定义方法
  • 都可以被扩展
  • 都可以遵循协议

类有,而结构体没有的额外功能:

  • 类可以继承,结构体不能
  • 类可以进行类型转换(只能子类类型转成父类类型,不能父类类型转成子类类型)
  • 类有析构函数
  • 类有引用计数的概念

开发中类与结构体的使用场景

类与结构体都可以用来定义自定义的数据类型,结构体实例总是通过值来传递,而类实例总是通过引用来传递

按照通用的准则,当符合以下一条或者多条特殊情形的时候,应考虑结构体。其他大部分自定义的数据结构,都是使用类。

  • 要描述的数据类型中只有少量的简单数据类型的属性
  • 要描述的数据类型在传递时要以复制的方式进行
  • 要描述的数据类型中所有的属性在进行传递时需要以复制的方式进行
  • 不需要继承另一个数据类型

比如如下场景下,就需要使用结构体:

  • 定义几何形状的大小,封装了一个width属性和一个height属性,两者均为double类型
  • 定义一定范围的路径,封装了start属性和length属性,两者为Int类型
  • 定义三维坐标系的一个点,封装了x、y、z属性,它们是Double类型

0 人点赞