Swift笔记10:函数、方法、下标、inout、多态

函数

func execFunc() {
    print()
    print(TempFunc(x: nil, 2.0))
    print(testTuple().firstName, testTuple().lastName) // 张 三
    print(testTuple1(firstNum: 3, sencondNum: 5).0, testTuple1(firstNum: 3, sencondNum: 5).1) // 8 -2
    print(sum(3, 5, 7, 9, 11)) // 35
    var m = 200
    tempInout(num: &m)
    print(m) // 300
    print(getLeft("今天很不错", number: 1)) // 今天
}

// 定义函数
// 无返回 ->void也可不写,无return
// Float = 10,可以给参数添加默认值
func TempFunc(x: Int?, _: Float = 10) -> Bool {
    guard let i = x else {
        print("x参数没有值")
        return false
    }
    print("x参数有值")
    return true
}

// 返回元组
func testTuple() -> (firstName: String, lastName: String) {
    return ("张", "三")
}

// 标签名,firstNum外面易读,不关心m内部使用
func testTuple1(firstNum m: Int, sencondNum n: Int) -> (Int, Int) {
    return (m + n, m - n)
}

// 多参数,外部无标签可是一个数组,内部number使用
func sum(_ numbers: Int...) -> Int {
    var sum = 0
    print(type(of: numbers)) // Array<Int>
    numbers.forEach {
        sum += $0
    }
    return sum
}

// inout 注意传入变量的地址,
// 参数的必须是变量,不能是常量或字面量
// 不可以一个变量同时传递给2个输入输出参数
// 安全性:在函数结束之前,传入的变量的值不能被其他操作影响。
func tempInout(num: inout Int) {
    num += 100
}

// 左截函数
func getLeft(_ inString: String, number num: Int) -> String {
    var s = inString[inString.startIndex ... inString.index(inString.startIndex, offsetBy: num)]
    return String(s) // Substring转化成String
}

方法

枚举、结构体、类都可以定义实例方法、类型方法
1 实例方法(Instance Method):通过实例对象调用
2 类型方法(Type Method):通过类型调用,用static或者class关键字定义

class CountNum {
    nonisolated(unsafe) static var count = 0
    init() {
        CountNum.count += 1
    }

    static func getCount() -> Int {
        count
    }
}
let c0 = CountNum()
let c1 = CountNum()
let c2 = CountNum()
print(CountNum.getCount()) // 3
self

在实例方法中代表实例对象
在类型方法中代表类型
在类型方法static func getCount() 中
count等价于self.count、CountNum.self.count、CountNum.count

mutating

1 结构体和枚举是值类型,默认情况下,值类型的属性不能被自身的实例方法修改
类是不存在这个问题
2 在func关键字前加mutating可以允许这种修改行为
在下例中我们不添加mutating,会报错:Left side of mutating operator isn't mutable: 'self' is immutable

struct PointT {
    var x = 0.0, y = 0.0
    mutating func moveBy(deltaX: Double, deltaY: Double) {
        x += deltaX
        y += deltaY
        // self = Point(x: x + deltaX, y: y + deltaY)
    }

    var detaX: Double {
        set { x += newValue }
        get { x }
    }

    var deltaY: Double {
        set { y += newValue }
        get { y }
    }
}

我们想直接修改x属性方法一

var a = PointT()
a.moveBy(deltaX: 10, deltaY: 10) // 10.0 10.0
print(a.x, a.y)
a.moveBy(deltaX: 20, deltaY: 30) // 30.0 40.0
print(a.x, a.y)

方法二

var b = PointT()
b.detaX = 10
b.deltaY = 10
print(b.x, b.y) // 10.0 10.0
b.detaX = 20
b.deltaY = 30
print(b.x, b.y) // 30.0 40.0

枚举也是这样

enum Positon {
    case pointX, pointY
    mutating func change() {
        switch self {
        case .pointX:
            self = .pointY
        case .pointY:
            self = .pointX
        }
    }
}
@discardableResult

在func前面加个@discardableResult,可以消除:函数调用后返回值未被使用的警告⚠
在下例中如果不添加@discardableResult,返回的元组没有被用到会提示:Result of call to 'moveBy(deltaX:deltaY:)' is unused

struct PointM {
    var x = 0.0, y = 0.0
    @discardableResult mutating func moveBy(deltaX: Double, deltaY: Double) -> (Double, Double) {
        x += deltaX
        y += deltaY
        // self = Point(x: x + deltaX, y: y + deltaY)
        return (x, y)
    }
}
var p = PointM()
p.moveBy(deltaX: 10, deltaY: 10) //Result of call to 'moveBy(deltaX:deltaY:)' is unused

下标(subscript)

使用subscript可以给任意类型(枚举、结构体、类)增加下标功能,有些地方也翻译为:下标脚本
subscript的语法类似于实例方法、计算属性,本质就是方法\函数(set, get)

class Point {
    var x = 0.0, y = 0.0
    subscript(index: Int) -> Double {
        set {
            if index == 0 {
                x = newValue
            } else if index == 1 {
                y = newValue
            }
        }
        get {
            if index == 0 {
                return x
            } else if index == 1 {
                return y
            }
            return 0
        }
    }
}

var p = Point()
p[0] = 11.1  //调用下标,set方法,index = 0
p[1] = 22.2
print(p.x) // 11.1  
print(p.y) // 22.2
print(p[0]) // 11.1  //获取下标的get方法,index = 0
print(p[1]) // 22.2

注意:
subscript中定义的返回值类型决定了 (subscript(index: Int) -> Double中的Double)
1 get方法的返回值类型 (get方法中返回的类型是Double)
2 set方法中newValue的类型 (newValue的类型是Double)
下标参数不仅支持整型,也支持字符串等其他类型
subscript可以接受多个参数,并且类型任意

下标的细节

下面1和2两条细节和属性很像
1 subscript可以没有set方法,但必须要有get方法。否则会报错:Subscript with a setter must also have a getter
2 如果只有get方法,可以省略get
3 可以设置参数标签
subscript(index i: Int) -> Double { }
使用的时候也加上标签
p[index: 1]
4 下标可以是类型方法,并通过类型访问类方法

class Sum {
    static subscript(v1: Int, v2: Int) -> Int {
        return v1 + v2
    }
}
print(Sum[10, 20]) // 30
接收多个参数的下标
class Grid {
    var data = [
        [0, 1, 2],
        [3, 4, 5],
        [6, 7, 8]
    ]
    subscript(row: Int, column: Int) -> Int {
        set {
            guard row >= 0 && row < 3 && column >= 0 && column < 3 else {
                return
            }
            data[row][column] = newValue
        }
        get {
            guard row >= 0 && row < 3 && column >= 0 && column < 3 else {
                return 0
            }
            return data[row][column]
        }
    }
}
var grid = Grid()
grid[0, 1] = 77
grid[1, 2] = 88
grid[2, 0] = 99
print(grid.data) //[[0, 77, 2], [3, 4, 88], [99, 7, 8]]

inout

输入输出函数(In-Out 参数)允许函数对传入的变量进行修改,并且这些修改会在函数调用后反映在原始变量上。
使用 inout 关键字:
在函数定义时,将参数声明为 inout 类型,表示该参数的值可以在函数内部修改,并且修改会影响到原始变量。
调用时需要使用 &:
当你调用一个接受 inout 参数的函数时,需要在传递参数时使用 & 符号,表示传递的是变量的引用,而不是值。

 var n = 5 //这里需要用 必须用var 来声明 因为必须是变量 不能是常量 也不能是字面量
 func sum(_ num :inout Int) {//inout 关键字将参数num 声明为输入输出参数
     num = num + 1
 }
 sum(&n)  //& 用来明确标记这个方法的调用是输入输出参数的函数 里面会对这个n 进行修改
 print(n) //n 是 6

注意事项:
In-Out 参数不能是常量:传递给 In-Out 参数的必须是变量,不能是常量或字面量。
In-Out 参数会改变原始值:这意味着函数内部对 In-Out 参数的修改会直接影响外部传递的变量。
安全性:在函数结束之前,传入的变量的值不能被其他操作影响。

适用场景:
交换两个变量的值:如上例所示,使用 In-Out 参数可以很方便地交换两个变量的值。
累加或减少某个值:在需要修改多个变量时可以使用 In-Out 参数。
更改传递进来的数据:某些场景下你可能需要传递一个对象或数据,并在函数内部修改它。

安全性的理解:

In-Out 参数的安全性问题主要与多线程环境下的访问、不可变性以及变量生命周期等有关。

  1. 防止数据竞争和不一致性
    In-Out 参数的机制是将变量的引用传递给函数,这意味着函数直接操作外部变量。如果在多线程环境下,多个线程同时访问或修改同一个变量,会引发数据竞争(Data Race)问题。为避免这种情况,Swift 对 In-Out 参数有一些限制,以提高安全性:
    值拷贝行为:在函数执行时,Swift 会对 In-Out 参数进行临时拷贝,函数执行完毕后再将修改后的值赋回原始变量。这种机制可以避免在函数内部处理参数时,对该参数的其他操作产生冲突。
    限制可变性:传递给 In-Out 参数的变量必须是变量(var),而不能是常量(let)。这确保了参数在传递前具有可变性。
  2. 禁止常量和字面量的修改
    In-Out 参数不能传递常量或字面量,这是 Swift 设计中对数据安全的一种保障。具体体现在:
    常量保护:如果你尝试传递常量或字面量给 In-Out 参数,编译器会报错。这防止了意外修改不应被修改的数据。
    例如,以下代码会导致编译错误,因为 a 是常量:
 let a = 10
 var b = 20
 swapTwoInts(&a, &b) //常量保护 编译错误:Cannot pass immutable value 'a' as inout argument

  func changeValue(_ value: inout Int) {
    value += 10
  }

   changeValue(&5) // 常量保护 编译错误:Cannot pass immutable value '5' as inout argument
  1. 控制变量的生命周期,避免并发访问问题
    In-Out 参数的使用还涉及到变量的生命周期安全:
    引用的时间限制:传递给 In-Out 参数的变量在函数调用时,会被临时锁定为不可访问的状态,直到函数执行完毕,防止函数内外对该变量进行并发访问。
    这保证了变量在传入函数时不会被其他代码修改,从而防止数据不一致问题。
  2. 显式修改,避免隐式副作用:关键符号 &
    In-Out 参数的修改是显式的,也就是说,在调用 In-Out 参数函数时,必须通过 & 符号来表明该参数是可修改的。这样设计可以提高代码的可读性和可维护性,因为开发者很清楚哪些参数会被修改,不写就会编译报错
  3. 编译器的静态检查,防止多个 In-Out 参数修改同一变量
 func changeValues(_ a: inout Int, _ b: inout Int) {
     a += 1
     b += 1
 }
 var num = 5

changeValues(&num, &num) //不可以一个变量同时传递给2个输入输出参数 错误:Cannot pass the same variable as inout argument multiple times
上面的代码会导致编译错误,因为不能将同一个变量传递给多个 In-Out 参数,防止变量在函数内部同时被多个地方修改,从而造成数据冲突。

警告,inout 参数的修改才会对外部变量生效 ,因此 不要用常量,字面量传递给输入输出参数

inout的本质总结

1 如果实参有物理内存地址(比如存储属性、全局变量),且没有设置属性观察器
直接将实参的内存地址传入函数(实参进行引用传递)
2 如果实参是计算属性 或者 设置了属性观察器
采取了Copy In Copy Out的做法
第一步调用该函数时,先复制实参的值,产生副本【get】
第二步将副本的内存地址传入函数(副本进行引用传递),在函数内部可以修改副本的值
第三步函数返回后,再将副本的值覆盖实参的值【set】

总结:inout的本质就是引用传递(地址传递)

多态

多态:父类指针指向子类对象
多态的实现原理:
1 OC: Runtime
2 C++: 虚表(虚函数表)
3 Swift: 类信息查找函数表

struct调用函数和class调用函数的区别:
struct调用函数编译阶段就可以确定,直接去call 固定的函数地址。
class对象调用函数需要确定是父类还是自己的函数。首先根据前8个字节找到存放类信息的内存地址,类信息里面的方法列表中保存着dog.speak,dog.eat,amimal.speak等等方法。类信息编译完就确定的,查找函数列表的偏移量也是固定的。根据内存偏移量查找函数列表,找到对应的函数。
类信息的内存在全局区。
类信息编译完就确定的。
animal1和animal2的前8个字节是一样的。
var animal1: Animal = Animal()
var anima2l: Animal = Animal()

 class Animal {
    class func speak() {
        print("Animal speak")
    }
 }

class Dog: Animal {
    override func speak() {
        super.speak()
        print("Dog Speak")
    }
}

var animal: Animal = Animal()
animal = Dog()
animal.speak() //Animal speak   Dog Speak
将方法赋值给var\let

方法也可以像函数那样,赋值给一个let或者var
注意我们下面的注释,尤其是fn的类型。

struct Person {
    var age: Int
    func run(_ v: Int) { print("func run", age, v) }
    static func run(_ v: Int) { print("static func run", v) }
}

func execAccessControl() {
//    let fn = Person.run(T##self: Person##Person)
    // 首先我们先把run方法当成一个属性获取
    // fn1的类型: @sendable (Person) -> (@sendable int ->()),接收一个person,返回一个函数
    // 因为我们的静态run方法重名了,我们才需要给fn1加上类型,否则可以忽略类型
    let fn1: (Person) -> ((Int) -> ()) = Person.run
    // 然后我们获取类的实例对象fn2
    // fn2的类型: @sendable (Int)-> ()
    let fn2 = fn1(Person(age: 10))
    // 最后实例对象直接调用run方法
    fn2(20) // func run 10 20
    // 系统也有提示
//    let fn = Person.run(self:Person)
    let fn = Person.run(Person(age: 20))
    fn(17) // func run 20 17

    let fnT = Person.run
    fnT(99) // static func run 99
}
内联函数

内联函数在C++这个函数里是有的,那么在swift里面,怎么做的呢?swift内是不需要我们去声明这个函数为内联函数的。
如果开启了编译器优化(Realease 模式默认会开启优化),编译器会自动将某些函数变成内联函数。
我们打开项目。
Debug是没有优化的,Release是优化的.
选择target---> Build Settings ---> 输入optimization 如下图:

12.2.9.png

搜索一下,我们会看到有一个Optimization Level 优化级别,默认Debug情况下是NO Optimization(没有优化)。Release(打包的时候)是Optimization for Speed[-D]是有优化的。而且是speed是最快的,按照速度最快的方式去优化。如果我们开启了优化的话,它会自动将我们的某些函数变成内联函数。也就是说,Debug模式下,不会将你的函数,变成内联函数。Release就变成内联函数。Release发布版会自动将某些函数变成内联函数,也就意味这内联函数这种东西是有用的。肯定是可以优化我们程序的系统的。
如下例func test()优化后只有print("test")

func test() -> () {print("test")}        
print("test")

内联函数作用:内联函数会自动将函数调用展开成函数体代码。说白了,是一个怎么样的函数呢?如果你这个test是一个内联函数的话,它会之间将你的函数调用,展开成函数体print("test")。这样就是一种优化,这样可以减少函数的调用开销,就不用开辟栈空间,撤销栈空间。

推荐资料
函数底层分析 https://www.jianshu.com/p/a8f691cf5046

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,794评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,050评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,587评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,861评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,901评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,898评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,832评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,617评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,077评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,349评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,483评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,199评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,824评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,442评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,632评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,474评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,393评论 2 352

推荐阅读更多精彩内容