python数据结构之面向对象

x33g5p2x  于12个月前 转载在 Python  
字(4.7k)|赞(0)|评价(0)|浏览(132)

💫在python数据结构的上一章节我们讲述了python的输入输出、控制、异常,希望大家重点掌握输出和控制,这些都是比较简单的啦,多看看就好了,对以前课程感兴趣的小伙伴可以看这里👇:

  • python数据类型: python数据结构之数据类型.
  • python的输入输出: python数据结构之输入输出、控制和异常.

💖今天我们来学习面向对象编程,面向对象这种编程方式非常重要,我们以后学习到的栈、队列、链表都是通过面向对象的方式实现的。

1. 面向对象编程

定义:面向对象是按照人们客观世界的系统思维方式,采用基于对象(实体)的概念建立模型 ,模拟客观世界分析,设计,实现软件的办法。通过面向对象的理念使计算机软件系统能与现实世界中的系统的一一对应。

听到这很多同学应该会很蒙,我解释一下几个关键词的概念:

  • 对象:一切事物都可以是对象,更准确的说,对象是一个实体,用一组可识别的特征和行为来标识,可以说对象不同的地方就是具有不同的特征与行为。
  • 类:是具有相同属性和功能的对象抽象集合
  • 实例:就是创建具体对象的过程

举一个面向对象的例子:我们需要造一辆车,我们可能并不需要知道车的原理,我们只需要找到车的轮子,车的车架,车的发动机,然后将它们拼装在一起即可。

在前面我们已经使用了一些內建的类来展示数据和控制结构的例子。面向对象编程语言最强大的一项特性是允许程序员(问题求解者) 创建全新的类来对求解问题所需的数据进行建模。

我们之前使用了抽象数据类型来对数据对象的状态及行为进行逻辑描述。通过构建能实现抽象数据类型的类,可以利用抽象过程,同时为真正在程序中运用抽象提供必要的细节。每当需要实现抽象数据类型时,就可以创建新类。

2. 构建类

我们来构建一个分数类fraction类,样子长啥样x y \frac{x}{y}yx​,3 5 \frac{3}{5}53​,9 16 \frac{9}{16}169​这类的,可能有人会说,你这不能用除法?其实是可以,但是除法的方式python已经有自己的类了,我这里需要自己创建一个分数类。

所有类都应该首先提供构造方法。构造方法定义了数据对象的创建方式。要创建 Fraction 类的实例,必须调用构造方法,要创建一个 Fraction 对象,需要提供分子和分母两部分数据。

class fraction: #定义一个类
    def __init__(self,top,bottom):#定义构造方法 __init__
        self.num=top
        self.den=bottom

这里解释一下,self 是一个总是指向对象本身的特殊参数,它必须是第一 个形式参数。然而,在调用方法时,从来不需要提供相应的实际参数。如前所述,分数需要分子与分母两部分状态数据。构造方法中的 self.num 定义了 Fraction 对象有一个叫作 num 的内部数据对象作为其状态的一部分。同理,self.den 定义了分母。这两个实际参数的值在初始时,赋给了状态,使得新创建的 Fraction 对象能够知道其初始值。

class fraction: #定义一个类
    def __init__(self,top,bottom):#定义构造方法 __init__
        self.num=top
        self.den=bottom
        
myfraction = fraction(3,5)#使用类创建一个对象。

我们可以用下面这个图来描述,我们构造类一个类,然后通过类创建了一个对象。

一般情况下,我们定义了一个类,里面会有这个类的一些方法,例如,我要查看这个分数对象的样子,我要计算这中类型的分数,等等方法,这些都需要我们去定义。

class fraction: #定义一个类
    def __init__(self,top,bottom):#定义构造方法 __init__
        self.num=top
        self.den=bottom
        
    def show(self): #定义输出方法
        print(self.num,"/",self.den)
        
    def add(self,otherf): #定义分数加法
        bot=otherf.den * self.den
        top=self.num * otherf.den +self.den * otherf.num
        print(top,"/",bot)

#定义两个分数
myfraction=fraction(9,16)
a=fraction(1,16) 

#试试加法
myfraction.add(a)

结果如下:

这里我们放上我们写好的分数类定义方法。

class Fraction:
    def __init__(self, top, bottom):
        self.num = top
        self.den = bottom
    def __str__(self):
        return str(self.num) + "/" + str(self.den)

    def show(self):        
        print(self.num, "/", self.den)

    def __add__(self, otherfraction): #加法 ,可以用+
        newnum = self.num * otherfraction.den + self.den * otherfraction.num
        newden = self.den * otherfraction.den
        common = gcd(newnum, newden)
        return Fraction(newnum//common, newden//common)

    def __eq__(self, other): #深相等,值相等就相等
        firstnum = self.num * other.den
        secondnum = other.num * self.den
        return firstnum == secondnum

3. 继承

继承是面向对象编程的一个重要特征:使一个类与另一个类相关联,就像人们相互联系一样。孩子从父母那里继承了特征,python的子类可以从父类那继承数据和行为,父类也称为超类。

就拿我们前面学习过的基础数据结构举例。

列表、字符串和元组都是有序集合。它们都继承了共同的数据组织和操作。不过,根据数据是否同类以及集合是否可修改,它们彼此又有区别。子类从父类继承共同的特征,但是通过额外的特征彼此区分。

3.1 继承案例

我们这里通过一个小的案例来实现继承的案例,我们来构建一个模拟程序,用于模拟数字电路。逻辑门是这个模拟程序的基本构造单元,它们代表其输入和输出之间的布尔代数关系。

通过不同的模式将这些逻辑门组合起来并提供一系列输入值,可以构建具有逻辑功能的电 路。图 1-10 展示了一个包含两个与门、一个或门和一个非门的电路。两个与门的输出直接作为 输入传给或门,然后其输出又输入给非门。如果在 4 个输入处(每个与门有两个输入)提供一系列值,那么非门就会输出结果。

为了实现电路,首先需要构建逻辑门的表示。可以轻松地将逻辑门组织成类的继承层次结构, 如图所示。顶部的 LogicGate 类代表逻辑门的通用特性:逻辑门的标签和一个输出。下面 一层子类将逻辑门分成两种:有一个输入的逻辑门和有两个输入的逻辑门。再往下,就是具体的逻辑门。

  1. 首先定义父类LogicGate
class LogicGate:
    def __init__(self, n):#构造函数,label代表的是自己的名字
        self.label = n
        self.output = None
    def getLabel(self):#输出名字的函数
        return self.label
    def getOutput(self):#输出的结果
        self.output = self.performGateLogic() 
        return self.output

目前还不用实现 performGateLogic 函数。原因在于,我们不知道每一种逻辑门将如何进 行自己的逻辑运算。这些细节会交由继承层次结构中的每一个逻辑门来实现。这是一种在面向对象编程中非常强大的思想——我们创建了一个方法,而其代码还不存在。参数 self 是指向实际调用方法的逻辑门对象的引用。任何添加到继承层次结构中的新逻辑门都仅需要实现之后会被调用的 performGateLogic 函数。一旦实现完成,逻辑门就可以提供运算结果。扩展已有的继承层次结构并提供使用新类所需的特定函数,这种能力对于重用代码来说非常重要。

  1. 定义子类BinaryGate类、UnaryGate类

两个类中的构造方法首先使用 super 函数 来调用其父类的构造方法。当创建 BinaryGate 类的实例时,首先要初始化所有从LogicGate 中继承来的数据项,在这里就是逻辑门的标签。接着,构造方法添加两个输入(pinA 和 pinB)。 这是在构建类继承层次结构时常用的模式。子类的构造方法需要先调用父类的构造方法,然后再 初始化自己独有的数据。

  • BinaryGate类主要是与门和或门(有两个输入)
class BinaryGate(LogicGate):
    def __init__(self, n):
        super().__init__(n)#继承父类的构造方法
        self.pinA = None #添加自己的参数
        self.pinB = None
            
    def getPinA(self):#自己的方法
        return int(input("Enter Pin A input for gate "+self.getLabel() + "-->"))

    def getPinB(self):#自己的方法
        return int(input("Enter Pin B input for gate "+self.getLabel() + "-->"))
  • UnaryGate类主要是非门(只有一个输入)。
class UnaryGate(LogicGate):
    def __init__(self, n):
        super().__init__(n)#继承父类的构造方法
        self.pin = None#添加自己的参数
    def getPin(self):#自己的方法
        return int(input("Enter Pin input for gate " + self.getLabel() + "-->"))
  1. 构造孙类(与门、或门、非门)
  • 与门 AndGate类
#构造与门
class AndGate(BinaryGate): 
    def __init__(self, n):
        super().__init__(n)#继承父类方法
    def performGateLogic(self):#自己的运算逻辑
        a = self.getPinA()
        b = self.getPinB()
        if a==1 and b==1:
            return 1
        else:
            return 0
  • 或门 OrGate
class OrGate(BinaryGate): 
    def __init__(self, n):
        super().__init__(n)#继承父类方法
    def performGateLogic(self):
        a = self.getPinA()
        b = self.getPinB()
        if a==1 or b==1:
            return 1
        else:
            return 0
  • 非门 NotGate
class NotGate(UnaryGate): 
    def __init__(self, n):
        super().__init__(n)
    def performGateLogic(self):
        a = self.getPin()
        if a==1:
            return 0
        else:
            return 1

测试一下结果

  • 与门

  • 或门

  • 非门

参考资料

《python数据结构与算法》
《大话数据结构》

相关文章