美文网首页
方法(二)

方法(二)

作者: 小橘子成长记 | 来源:发表于2018-05-21 14:38 被阅读0次

mutating 方法

结构中的方法不改变实例的值,就不需要标记为mutating。你可以想象一个方法,在SimpleDate的结构中,更改day的值:

mutating func advance() {
  day += 1 
}

注意:上面的实现是编写advance()的一种简单的方法,因为它不考虑一个月后会发生什么。在本章末尾的一个挑战中,你将创建一个更健壮的版本。

mutating关键字标记了一种改变结构值的方法。由于结构是一个值类型,所以系统每次在应用程序中传递时都会复制它。如果一个方法改变了其中一个属性的值,那么原始实例和复制的实例将不再是等价的。

通过将方法标记为mutating,你还告诉Swift编译器这个方法不能被调用在常量上。这是Swift知道在编译时允许和拒绝哪些方法。如果在一个结构的常量实例上调用一个mutating方法,编译器会报错,你必须在运行程序之前纠正错误。

对于正常的方法,swift秘密地传递self。但是对于mutating的方法,秘密的self被标记为一个inout参数。

类型的方法

与类型属性一样,你可以使用类型方法在所有实例中访问数据。你可以在类型本身上调用类型方法,而不是在实例上调用。要定义类型方法,可以使用静态修饰符 static对其进行前缀。

类型方法对于一般的类型是有用的,而不是特定的实例。

例如,你可以使用类型方法将类似的方法分组到一个结构中:

struct Math {
  // 1
  static func factorial(of number: Int) -> Int {
    // 2
    return (1...number).reduce(1, *)
  }
}
// 3
Math.factorial(of: 6) // 720

你可能会对诸如factorial这样的东西进行自定义计算。你可以将相关函数组合为一个结构中的类型方法,而不是一堆独立的函数。该结构被称为名称空间namespace。

发生了什么:
1 你使用静态来声明类型方法,它接受一个整数并返回一个整数。
2 该实现使用了一个名为reduce(::)的高阶函数。它有效地遵循了计算阶乘的公式:“从1到n的所有整数的乘积”。你可以用for循环来写这个,但是高阶函数用更简洁的方式表达了你的意图。
3 你在Math上调用类型方法,而不是在类型的实例上调用。

在一个结构中收集的类型方法将有利于在Xcode中写代码。在本例中,你可以通过键入Math来查看所有可用的数学实用方法。

QQ20180521-134619@2x.png

向现有结构添加扩展

有时候,你希望将功能添加到一个结构中,但不希望将原来的定义弄得一团糟。有时你不能添加功能,因为你无法访问源代码。可以打开一个现有的结构(即使你没有源代码)并添加方法、初始化器和计算属性。这对组织代码很有用。使用关键extension就可以实现。

在你的playground的底部,在Math的定义之外,添加这种类型的方法,名为primeFactors(of:),使用一个扩展extension:

extension Math {
  static func primeFactors(of value: Int) -> [Int] {
  // 1
    var remainingValue = value
    // 2
    var testFactor = 2
    var primes: [Int] = []
    // 3
    while testFactor * testFactor <= remainingValue {
      if remainingValue % testFactor == 0 {
        primes.append(testFactor)
        remainingValue /= testFactor
      }
      else {
        testFactor += 1
      }
    }
    if remainingValue > 1 {
      primes.append(remainingValue)
    }
    return primes
  }
}

该方法找到给定数字的因子。例如,81返回[3,3,3,3]。下面是代码中发生的事情:

  1. 作为参数传递的值被分配给可变变量,remainingValue,以便在计算运行时更改它。
  2. testFactor以2开始 ,并将其分为remainingValue。
  3. 逻辑运行一个循环,直到remainingValue耗尽为止。如果它平均地分裂,意味着没有余数,那么testFactor的值就被放在一边作为一个主要因子。如果它不均匀地划分,那么testFactor将在下一个循环递增。

这个算法是蛮力的,但确实包含一个优化:testFactor的平方永远不应该大于remainingValue。如果是,剩下的值本身是质数,所以它可以被添加到primes列表中。

你现在已经在Math中添加了一个方法,而不改变它的原始定义。现在验证一下extension与此代码一起工作:

Math.primeFactors(of: 81) // [3, 3, 3, 3]

注意:在扩展中,不能将存储属性添加到现有结构中,因为这会改变结构的大小和内存布局,并破坏现有代码。

使用扩展(extension)使编译器生成初始化器

在你的SimpleDate结构中,你可以看到,一旦添加了自己的init(),编译器生成的成员初始化器就会消失。事实证明,如果你将自己的init()添加为SimpleDate的扩展,你可以保留这两种方法:

struct SimpleDate {
  var month: String
  var day: Int
  func monthsUntilWinterBreak() -> Int {
    return months.index(of: "December")! - months.index(of: month)!
  }
  mutating func advance() {
    day += 1 
  }
}
extension SimpleDate {
  init() {
    month = "January"
    day = 1 
  }
}

init()在不牺牲自动初始化器的情况下被添加到SimpleDate。完美!

关键点

•方法就是扩展类型的功能。
•方法是在类型内部定义的函数。
•通过使用关键字self来访问实例的值。
•初始化器是创建新实例的方法。
•类型方法将方法添加到类型,而不是该类型的实例。要定义类型方法,可以使用静态修饰符static对其进行前缀。
•你可以使用扩展extension来打开现有的结构并添加方法、初始化器和计算属性。
•通过在扩展中添加自己的初始化器,可以保持编译器的初始化构造。

相关文章

网友评论

      本文标题:方法(二)

      本文链接:https://www.haomeiwen.com/subject/lsbwdftx.html