跳到主要内容

Swift中的继承

提示
  1. 类继承的基本概念:Swift 支持面向对象编程中的类继承,允许从现有类(超类)创建新类(子类)。
  2. 方法重写与super关键字:子类可以重写超类的方法,使用override关键字标识;super关键字用于在子类中调用超类的方法。
  3. 继承的实际应用:继承提高代码复用性和直观性,例如通过继承一个通用类(如RegularPolygon)来计算不同形状(如正方形、三角形)的周长和面积。

像其他面向对象编程语言一样,Swift 也支持类继承的概念。

继承允许我们从一个现有的类创建一个新类。

被创建的新类被称为子类(child 或 derived class),而子类派生自的现有类被称为超类(parent 或 base class)。

Swift 继承语法

在 Swift 中,我们使用冒号 : 来继承另一个类。例如,

// 定义一个超类
class Animal {
// 属性和方法的定义
}

// 继承
class Dog: Animal {

// Animal 的属性和方法
// Dog 的属性和方法
}

这里,我们将 Dog 子类从 Animal 超类中继承。

示例:Swift 继承

class Animal {

// 父类的属性和方法
var name: String = ""

func eat() {
print("我能吃")
}
}

// 从 Animal 继承
class Dog: Animal {

// 子类中的新方法
func display() {

// 访问超类的 name 属性
print("我的名字是 ", name);
}
}

// 创建子类的对象
var labrador = Dog()

// 访问超类的属性和方法
labrador.name = "Rohu"
labrador.eat()

// 调用子类方法
labrador.display()

输出

我能吃
我的名字是 Rohu

在上面的示例中,我们从 Animal 超类派生了一个 Dog 子类。注意语句,

labrador.name = "Rohu"

labrador.eat()

这里,我们使用 labradorDog 的对象)来访问 Animal 类的 nameeat()。这是因为子类继承了超类的所有属性和方法。

此外,我们在 Dog 类的方法内部访问了 name 属性。

is-a 关系

在 Swift 中,继承是一种 is-a 关系。也就是说,我们只在两个类之间存在 is-a 关系时使用继承。例如,

  1. Car(汽车) is a Vehicle(交通工具)
  2. Apple(苹果) is a Fruit(水果)
  3. Cat(猫) is an Animal(动物)

这里,Car 可以从 Vehicle 继承,Apple 可以从 Fruit 继承,等等。

Swift 继承中的方法覆盖

在前面的示例中,我们看到子类的对象可以访问超类的方法。

然而,如果同一个方法同时存在于超类和子类中怎么办?

在这种情况下,子类中的方法将覆盖超类中的方法。这种概念在 Swift 中被称为方法覆盖。

我们使用 override 关键字来告诉编译器我们正在覆盖一个方法。

示例:方法重写

class Animal {

// 父类中的方法
func eat() {
print("我能吃")
}
}

// Dog 继承自 Animal
class Dog: Animal {

// 重写 eat() 方法
override func eat() {
print("我吃狗粮")
}
}

// 创建子类的对象
var labrador = Dog()

// 调用 eat() 方法
labrador.eat()

输出

我吃狗粮

在上面的例子中,eat() 方法在 Dog 类和 Animal 类中都存在。

现在,当我们使用 Dog 子类的对象调用 eat() 方法时,将调用 Dog 类中的方法。

这是因为 Dog 子类中的 eat() 方法重写了 Animal 父类中的同名方法。我们使用了 override 关键字来指定该方法被重写。

override func eat() {
print("我吃狗粮")
}

Swift 继承中的 super 关键字

之前我们看到,子类中的相同方法会重写父类中的方法。

然而,如果我们需要在子类中访问父类的方法,我们使用 super 关键字。例如,

class Animal {

// 在父类中创建方法
func eat() {
print("我能吃")
}
}

// Dog 继承自 Animal
class Dog: Animal {

// 重写 eat() 方法
override func eat() {

// 调用父类的方法
super.eat()
print("我吃狗粮")
}
}

// 创建子类的对象
var labrador = Dog()

// 调用 eat() 方法
labrador.eat()

输出

我能吃
我吃狗粮

在上面的例子中,Dog 子类的 eat() 方法重写了 Animal 父类中的同名方法。

Dog 类内部,我们使用了

// 调用父类的方法
super.eat()

来从 Dog 子类调用 Animal 父类中的 eat() 方法。

因此,当我们使用 labrador 对象调用 eat() 方法时,

// 调用 eat() 方法
labrador.eat()

当覆盖的方法和超类中的 eat() 方法都被执行时。

为什么使用继承?

为了理解继承的好处,让我们考虑一个情况。

假设我们正在处理正多边形,如正方形、五边形等,并且我们需要根据输入计算这些多边形的周长。

1. 由于计算周长的公式对所有正多边形都是通用的,我们可以创建一个 Polygon 类和一个 calculatePerimeter() 方法来计算周长。

class RegularPolygon {

func calculatePerimeter() {
// 计算周长的代码
}
}

2. 并从 RegularPolygon 类继承 SquarePentagon 类。这些类将具有存储边长和边数的属性,因为这些对于所有多边形都是不同的。

class Square: RegularPolygon {

var length = 0
var sides = 0
}

我们将 lengthsides 的值传递给 calculatePerimeter() 来计算周长。

这就是继承如何使我们的代码更具可重用性和直观性。

示例:继承的好处

import Foundation
class RegularPolygon {

func calculatePerimeter(length: Int, sides: Int) {
let result = length * sides
print("周长:", result )
}
}

// 从多边形继承正方形
class RegularSquare: RegularPolygon {
var length = 0
var sides = 0

func calculateArea() {
let area = length * length
print("正方形面积:", area)
}
}

// 从多边形继承三角形
class RegularTriangle: RegularPolygon {
var length = 0.0
var sides = 0.0

func calculateArea() {
let area = (sqrt(3)/4) * (length * length)
print("正三角形面积:", area)
}
}
var shape = RegularSquare()
shape.length = 4
shape.calculateArea()
shape.calculatePerimeter(length: 3,sides:4)

var shape2 = RegularTriangle()
shape2.length = 2
shape2.calculateArea()
shape2.calculatePerimeter(length: 2,sides:3)

输出

正方形面积: 16
周长: 12
正三角形面积: 1.7320508075688772
周长: 6

在上面的示例中,我们创建了一个 RegularPolygon 类来计算正多边形的周长。

这里,RegularSquareRegularTriangle 继承自 RegularPolygon

计算正多边形周长的公式对所有形状都是通用的,所以我们重用了超类中的 calculatePerimeter() 方法。

由于计算不同形状面积的公式不同,我们在子类中创建了一个单独的方法来计算面积。