面向对象编程中的多态与递归
1. 多态的概念
多态是面向对象编程中一个强大的特性,它允许子类拥有与父类同名的方法。多态的核心在于,程序能够根据调用方法的对象类型,调用正确的方法版本。多态行为主要包含以下两个关键要素:
-
方法重写
:可以在父类中定义一个方法,然后在子类中定义一个同名的方法。当子类方法与父类方法同名时,通常称子类方法重写了父类方法。例如,每个子类的
__init__
方法通常会重写父类的
__init__
方法。
-
正确调用重写方法
:根据调用方法的对象类型,调用正确版本的重写方法。如果使用子类对象调用重写方法,则执行子类的方法版本;如果使用父类对象调用重写方法,则执行父类的方法版本。
1.1 示例代码
下面通过一个简单的例子来演示多态。首先定义一个
Mammal
类:
# The Mammal class represents a generic mammal.
class Mammal:
# The __init__ method accepts an argument for
# the mammal's species.
def __init__(self, species):
self._species = species
# The show_species method displays a message
# indicating the mammal's species.
def show_species(self):
print('I am a', self._species)
# The make_sound method is the mammal's
# way of making a generic sound.
def make_sound(self):
print('Grrrrr')
创建
Mammal
类的实例并调用其方法的示例代码如下:
import animals
mammal = animals.Mammal('regular mammal')
mammal.show_species()
mammal.make_sound()
上述代码的输出结果为:
I am a regular mammal
Grrrrr
1.2 子类示例
接下来定义
Mammal
类的子类
Dog
和
Cat
:
# The Dog class is a subclass of the Mammal class.
class Dog(Mammal):
# The __init__ method calls the superclass's
# __init__ method passing 'Dog' as the species.
def __init__(self):
Mammal.__init__(self, 'Dog')
# The make_sound method overrides the superclass's
# make_sound method.
def make_sound(self):
print('Woof! Woof!')
# The Cat class is a subclass of the Mammal class.
class Cat(Mammal):
# The __init__ method calls the superclass's
# __init__ method passing 'Cat' as the species.
def __init__(self):
Mammal.__init__(self, 'Cat')
# The make_sound method overrides the superclass's
# make_sound method.
def make_sound(self):
print('Meow')
创建
Dog
和
Cat
类的实例并调用其方法的示例代码如下:
import animals
dog = animals.Dog()
dog.show_species()
dog.make_sound()
cat = animals.Cat()
cat.show_species()
cat.make_sound()
上述代码的输出结果为:
I am a Dog
Woof! Woof!
I am a Cat
Meow
1.3 isinstance 函数的使用
多态在程序设计中提供了很大的灵活性。例如,定义一个函数
show_mammal_info
:
def show_mammal_info(creature):
creature.show_species()
creature.make_sound()
只要传递给该函数的对象具有
show_species
和
make_sound
方法,函数就会调用这些方法。但如果传递的对象不是
Mammal
类或其子类的实例,就会引发
AttributeError
异常。可以使用内置函数
isinstance
来避免这种异常的发生。
isinstance
函数的一般调用格式为:
isinstance(object, ClassName)
以下是使用
isinstance
函数改进后的
show_mammal_info
函数示例:
# This program demonstrates polymorphism.
import animals
def main():
# Create a Mammal object, a Dog object, and
# a Cat object.
mammal = animals.Mammal('regular animal')
dog = animals.Dog()
cat = animals.Cat()
# Display information about each one.
print('Here are some animals and')
print('the sounds they make.')
print('--------------------------')
show_mammal_info(mammal)
print()
show_mammal_info(dog)
print()
show_mammal_info(cat)
print()
show_mammal_info('I am a string')
# The show_mammal_info function accepts an object
# as an argument and calls its show_species
# and make_sound methods.
def show_mammal_info(creature):
if isinstance(creature, animals.Mammal):
creature.show_species()
creature.make_sound()
else:
print('That is not a Mammal!')
# Call the main function.
main()
上述代码的输出结果为:
Here are some animals and
the sounds they make.
--------------------------
I am a regular animal
Grrrrr
I am a Dog
Woof! Woof!
I am a Cat
Meow
That is not a Mammal!
1.4 多态的流程图
graph TD;
A[开始] --> B[创建对象];
B --> C{对象是否为 Mammal 或子类};
C -- 是 --> D[调用 show_species 和 make_sound 方法];
C -- 否 --> E[输出错误信息];
D --> F[结束];
E --> F;
1.5 多态的检查点与练习
检查点
给定以下类定义:
class Vegetable:
def __init__(self, vegtype):
self.__vegtype = vegtype
def message(self):
print("I'm a vegetable.")
class Potato(Vegetable):
def __init__(self):
Vegetable.__init__(self, 'potato')
def message(self):
print("I'm a potato.")
执行以下语句:
v = Vegetable('veggie')
p = Potato()
v.message()
p.message()
输出结果为:
I'm a vegetable.
I'm a potato.
复习问题
-
选择题
-
在继承关系中,
_
___ 是通用类。
A. 子类
B. 父类
C. 从属类
D. 子类 -
在继承关系中,
_
___ 是专用类。
A. 父类
B. 主类
C. 子类
D. 父类 -
假设一个程序使用两个类:
Airplane和JumboJet。哪个更有可能是子类?
A.Airplane
B.JumboJet
C. 两者都是
D. 两者都不是 -
面向对象编程的这个特性允许在使用子类实例调用重写方法时,调用正确的方法版本。
A. 多态
B. 继承
C. 泛化
D. 特化 -
可以使用这个来确定一个对象是否是某个类的实例。
A.in运算符
B.is_object_of函数
C.isinstance函数
D. 程序崩溃时显示的错误信息
-
在继承关系中,
_
___ 是通用类。
-
判断题
- 多态允许在子类中编写与父类同名的方法。
-
不能从子类的
__init__方法中调用父类的__init__方法。 - 子类可以有与父类同名的方法。
-
只有
__init__方法可以被重写。 -
不能使用
isinstance函数来确定一个对象是否是某个类的子类的实例。
-
简答题
- 子类从父类继承了什么?
- 看以下类定义。父类的名称是什么?子类的名称是什么?
class Hammer(Tool):
pass
3. 什么是重写方法?
-
算法工作台
-
编写
Trout类定义的第一行。该类应继承Fish类。 - 给定以下类定义:
-
编写
class Art:
def __init__(self, art_type):
self.__art_type = art_type
def message(self):
print("I'm a piece of art.")
class Painting(Art):
def __init__(self):
Art.__init__(self, 'painting')
def message(self):
print("I'm a painting.")
执行以下语句:
a = Art('sculpture')
p = Painting()
a.message()
p.message()
输出结果是什么?
3. 看以下类定义:
class Bird:
def __init__(self, bird_type):
self._bird_type = bird_type
编写一个名为
Duck
的类,它是
Bird
类的子类。
Duck
类的
__init__
方法应调用
Bird
类的
__init__
方法,并传递
'duck'
作为参数。
编程练习
-
员工和生产工人类
:编写一个
Employee类,包含员工姓名和员工编号的数据属性。然后编写一个ProductionWorker类,它是Employee类的子类,包含班次编号(整数,如 1、2 或 3)和每小时工资率的数据属性。工作日分为白班和夜班,班次属性存储员工工作的班次。白班为 1 班,夜班为 2 班。为每个类编写适当的访问器和修改器方法。编写一个程序,创建ProductionWorker类的对象,并提示用户输入对象的每个数据属性。将数据存储在对象中,然后使用对象的访问器方法检索并显示在屏幕上。 -
班次主管类
:在一个特定的工厂中,班次主管是一名领取薪水的员工,负责监督一个班次。除了薪水外,当他或她的班次达到生产目标时,班次主管还会获得年度奖金。编写一个
ShiftSupervisor类,它是Employee类的子类。ShiftSupervisor类应包含年度薪水和年度生产奖金的数据属性。编写一个程序,使用ShiftSupervisor对象来演示该类。 -
人和客户类
:编写一个名为
Person的类,包含人的姓名、地址和电话号码的数据属性。然后编写一个名为Customer的类,它是Person类的子类。Customer类应包含客户编号的数据属性和一个布尔型数据属性,指示客户是否希望加入邮件列表。在一个简单的程序中演示Customer类的实例。
2. 递归的概念
递归是一种函数调用自身的编程技术。在程序中,通常是一个函数调用另一个函数,但递归函数会调用自身。例如下面的
message
函数:
# This program has a recursive function.
def main():
message()
def message():
print('This is a recursive function.')
message()
# Call the main function.
main()
这个
message
函数会不断地显示字符串
'This is a recursive function'
并调用自身,由于没有停止递归调用的代码,它会像一个无限循环一样不断重复,运行这个程序时,需要按
Ctrl+C
来中断执行。
为了控制递归函数的重复次数,需要添加控制条件。下面是修改后的
message
函数:
# This program has a recursive function.
def main():
# By passing the argument 5 to the message
# function we are telling it to display the
# message five times.
message(5)
def message(times):
if times > 0:
print('This is a recursive function.')
message(times - 1)
# Call the main function.
main()
2.1 递归函数的执行流程
上述程序的输出结果为:
This is a recursive function.
This is a recursive function.
This is a recursive function.
This is a recursive function.
This is a recursive function.
在这个程序中,
message
函数包含一个
if
语句来控制重复。只要
times
参数大于零,就会显示消息并以较小的参数再次调用自身。
main
函数调用
message
函数并传入参数 5,函数会被调用 6 次,第一次从
main
函数调用,其余 5 次是递归调用。递归的深度是 5,当
times
参数为 0 时,
if
语句条件为假,函数返回。控制从第六次调用返回到第五次调用中递归调用之后的位置,以此类推,直到所有实例的函数都返回。递归调用的流程如下表所示:
| 调用次数 |
times
参数值 |
| ---- | ---- |
| 第一次 | 5 |
| 第二次 | 4 |
| 第三次 | 3 |
| 第四次 | 2 |
| 第五次 | 1 |
| 第六次 | 0 |
2.2 递归函数的流程图
graph TD;
A[开始] --> B[调用 message 函数,传入参数 5];
B --> C{times > 0};
C -- 是 --> D[显示消息];
D --> E[递归调用 message 函数,传入参数 times - 1];
E --> C;
C -- 否 --> F[返回];
F --> G[结束];
3. 用递归解决问题
3.1 递归解决问题的思路
递归虽然不是解决问题的必需方法,任何可以用递归解决的问题也可以用循环解决,而且递归算法通常比迭代算法效率低,因为函数调用需要计算机执行一些操作,如为参数和局部变量分配内存、存储返回地址等,这些操作会带来额外的开销,而循环则不需要这些开销。但有些重复问题用递归比用循环更容易解决,程序员可能能更快地设计出递归算法。
一般来说,递归函数的工作方式如下:
- 如果问题现在可以不使用递归解决,那么函数就解决它并返回结果。
- 如果问题现在不能解决,那么函数将其简化为一个更小但结构相似的问题,并调用自身来解决这个更小的问题。
为了应用这种方法,首先要确定至少一种可以不使用递归解决问题的情况,这称为基本情况;其次,要确定在其他所有情况下使用递归解决问题的方法,这称为递归情况。在递归情况中,必须将问题简化为原始问题的较小版本,通过每次递归调用减少问题规模,最终会达到基本情况,递归停止。
3.2 递归计算阶乘的示例
在数学中,
n!
表示
n
的阶乘,非负整数的阶乘可以定义如下规则:
- 如果
n = 0
,则
n! = 1
。
- 如果
n > 0
,则
n! = 1 * 2 * 3 * ... * n
。
用递归函数计算阶乘的规则可以表示为:
- 如果
n = 0
,则
factorial(n) = 1
。
- 如果
n > 0
,则
factorial(n) = n * factorial(n - 1)
。
以下是用 Python 实现的阶乘递归函数:
# This program uses recursion to calculate
# the factorial of a number.
def main():
# Get a number from the user.
number = int(input('Enter a nonnegative integer: '))
# Get the factorial of the number.
fact = factorial(number)
# Display the factorial.
print('The factorial of', number, 'is', fact)
# The factorial function uses recursion to
# calculate the factorial of its argument,
# which is assumed to be nonnegative.
def factorial(num):
if num == 0:
return 1
else:
return num * factorial(num - 1)
# Call the main function.
main()
3.3 阶乘递归函数的执行过程
假设输入的数字是 4,
factorial
函数的执行过程如下表所示:
| 调用次数 |
num
参数值 | 返回值 |
| ---- | ---- | ---- |
| 第一次 | 4 | 24 |
| 第二次 | 3 | 6 |
| 第三次 | 2 | 2 |
| 第四次 | 1 | 1 |
| 第五次 | 0 | 1 |
当
num
不等于 0 时,
if
语句的
else
子句执行
return num * factorial(num - 1)
,但在返回值确定之前,需要先确定
factorial(num - 1)
的值。递归调用会一直进行,直到
num
参数为 0,此时函数返回 1,然后逐步计算并返回最终结果。
3.4 阶乘递归函数的流程图
graph TD;
A[开始] --> B[输入非负整数 n];
B --> C{num == 0};
C -- 是 --> D[返回 1];
C -- 否 --> E[计算 num * factorial(num - 1)];
E --> F[返回结果];
D --> F;
F --> G[结束];
4. 总结
多态和递归是编程中非常重要的概念。多态通过方法重写和根据对象类型调用正确方法版本,为程序设计带来了很大的灵活性,同时可以使用
isinstance
函数避免异常。递归则是一种函数调用自身的技术,适用于解决一些可以分解为相似小问题的重复问题,虽然有额外的开销,但在某些情况下能让程序员更高效地设计算法。在实际编程中,需要根据具体问题选择合适的技术来解决问题。
超级会员免费看
1611

被折叠的 条评论
为什么被折叠?



