08 面向对象程序设计_第1页
08 面向对象程序设计_第2页
08 面向对象程序设计_第3页
08 面向对象程序设计_第4页
08 面向对象程序设计_第5页
已阅读5页,还剩55页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

面向对象程序设计08本章内容面向对象程序设计的基本特点类与对象构造函数类成员的访问权限析构函数类的组合多态性与运算符重载继承与派生应用实例面向对象程序设计(ObjectOrientedProgramming,OOP)主要针对大型软件设计提出,使得软件设计更加灵活,能够很好地支持代码复用和设计复用,并且使得代码具有更好的可读性和可扩展性。面向对象程序设计的一条基本原则是将多个能够起到子程序作用的单元或对象组合成计算机程序,这样大大地降低了软件开发的难度,使得编程就像搭积木一样简单。面向对象程序设计的一个关键观念是将数据以及对数据的操作封装在一起,组成相互依存、不可分割的整体,通过对相同事物进行抽象,得出共同的特征并形成类,面向对象程序设计的关键就是如何合理地定义和组织这些类以及类之间的关系。8.1面向对象程序设计的基本特点抽象

面向对象方法中的抽象,是对具体的事物与其静态及动态状态进行分析,抽象出这一类事物的共有属性,并加以描述。抽象的过程是对问题从分析到认知的过程。一般而言,抽象包括两个部分,一个是对数据进行抽象,即获取静态属性;另一个是对行为进行抽象,即获取动态属性。如果要对学生的简单情况进行登记,首先要对学生的共性进行抽象,如果只需要登记学生的简单信息,静态属性有:ID,Name,Age,Phone_Namber,分别表示学生的学号、姓名、年龄、电话号码等信息。这些数据能够反映学生的静态信息。但学生是可以运动的,不是山、房子等这类静止不动的物体,学生具有活动的能力,所以除了静态属性外,还可以抽象出一些动态属性,如run、walk、eat等,分别对应学生的跑、走、吃等能力,用代码表示就是后面加上圆括号,说明这些动态属性是函数,这些函数是学生行为的体现。一个事物能够抽象出来的静态属性和动态属性有许多,但一个任务往往并不需要全部的属性,要根据任务来选择属性,同一个事物任务不同往往选择的属性不同。8.1面向对象程序设计的基本特点封装封装是将抽象得到静态属性(数据)和动态属性(行为或方法)相结合,形成一个整体。从代码上来说就是将数据与对数据处理的函数结合起来,形成“类”,而数据和对数据处理的函数则为类的成员,分别称为类的成员数据和成员函数。一般情况下类外的代码不能直接访问类中的成员数据,如果类外的代码要访问类中的成员数据,只能通过类的成员函数,也就是说类的成员函数是类与类外代码联系的接口。将数据与行为封装为一个可重用的代码块,可以有效的降低代码复杂度。8.1面向对象程序设计的基本特点继承现实世界中的事物具有特殊与一般的关系。例如,动物有名称、年龄、体重等属性,并具有吃、排泄等行为。动物按生活环境可以分为:陆生动物、水生动物和两栖动物。陆生动物具有在陆地生活的特性,大多数都呼吸空气,狮子、松鼠、老鼠等都是陆生动物。狮子是肉食性动物、体型大、攻击性强、奔跑速度快等特点。松鼠居住于树上,个头小,是草食性动物。老鼠是杂食性动物,个头小,钻洞能力强。动物类、陆生动物类与狮子类、松鼠类、老鼠类就属于一般与特殊的关系,狮子类、松鼠类、老鼠类继承了陆生动物的特性,但又有各自的特性。陆生动物继承了动物类的特点,但又有呼吸空气等特性。继承就是解决一般与特殊的关系,描述特殊类之间的一些共享的共性。Python语言中提供了类的继承机制,允许程序员在保持原有类特性的基础上,派生出新的属性与行为。8.1面向对象程序设计的基本特点多态多态是与我们生活中的人类思维方式很类似,如生活中我们常说的“驾驶”,“驾驶”是个行为,但对于不同的事物“驾驶”的行为和规则是完全不一样的,如“驾驶小汽车”、“驾驶直升机”、“驾驶坦克”等等,都是“驾驶”行为,但操作步骤完全不同。广义上来说,多态性是一段程序能够处理多种类型对象的能力。Python程序中,表示不同类型的对象接收同样的消息时出现了不同的行为,即同样的运算被不同的对象调用时产生不一样的行为。比如+运算,对于字符串表示字符串连接,对于数字类型表示求和。8.1面向对象程序设计的基本特点8.2类与对象类的定义对象类的成员函数类的成员数据面向对象的编程思想是将事物抽象为类,以类为程序模块,这样程序模块的独立性、数据的安全性就有良好的保障。类是面向对象程序设计方法的核心,利用类可以实现对数据的封装和隐藏。Python中,可以使用class关键字定义类,然后通过定义的类创建实例对象。类定义的语法格式如下:class类名:'''类说明'''

类体类名的首字母一般大写,类名的命名风格在整个系统中最好统一,这对于团队合作开发的系统十分重要。类体就是类的内部实现,类的实现语句必须在左方留有空格并靠左对齐。定义一个类,可以理解为在程序中增加了一个新的特殊数据类型。8.2.1类的定义上面代码创建了一个Circle类,类实现中只有一条pass语句,pass类似于空语句,也就是这个Circle类体实现中不做任何操作。上面代码创建了一个Point类,类体中有个print输出命令。8.2.1类的定义classCircle:'''圆类'''passclassPoint:'''点类'''

print("Thisisapoint")可以把一个类理解为一个特殊的数据类型。int、float等是Python的基础数据类型,可以通过赋值来创建相应的变量,用来存储数据。与基础类型一样,如果定义了一个类,则可以创建这个类的特殊变量,这个特殊变量称为该类的对象,创建对象的过程称为类的实例化。如果说变量是基础数据类型的实例,则对象就是类的实例。创建对象(类的实例)的语法形式如下:8.2.2对象对象名=类名()下面代码创建并使用了一个Circle类:第4行代码创建了一个名为c的Circle对象。由于Circle类体中只有注释语句和一个空语句pass,因此Circle类体中没有做任何操作,这样其创建的对象c内容也是空。8.2.2对象1234classCircle:'''圆类'''passc=Circle()上面代码创建了一个Point类,并在第4、5行创建了2个Point类的对象p1和p2。程序从第1行代码开始执行,首先运行的是类Point的声明,程序进入类Point的类体,执行第3行print("Thisisapoint"),在终端输出了"Thisisapoint"。然后依次执行第4行、第5行,实例化Point类,创建p1和p2对象,p1、p2都是空对象。所以终端输出"Thisisapoint"并不是指p1、p2对象内含print语句并执行了,而是程序在扫描类Point定义的时候执行了print输出语句,p1和p2对象与第3行print语句没有联系。8.2.2对象12345classPoint:'''点类'''print("Thisisapoint")p1=Point()p2=Point()>>>

Thisisapoint类的第一个特性就是将客观事物的静态属性(数据)和动态属性(行为或方法)提取出来并组合为一个不可分割的整体,实现面向对象程序设计的基础。成员函数的实现成员函数的定义与普通函数定义的语法形式类似,区别是成员函数的形参列表前面多了个self参数。如果是无参成员函数,则在定义成员函数时参数列表为空,但self仍然保留。8.2.3类的成员函数def函数名(self[,参数列表])函数体[return表达式]【例8-1】成员函数定义举例:程序执行第1行,创建一个Point类,并在Point类中定义了一个函数m_print。第4行和第5行是创建Point类的对象p1和p2。类中只是定义了成员函数,而执行成员函数则要靠类的实例(对象)来调用,调用成员函数的语法为:在定义类成员函数时,如果需要在该成员函数中调用类的另外一个成员函数,调用语法为:8.2.3类的成员函数12345classPoint:defm_print(self):print("Thisisapoint")p1=Point()p2=Point()对象名.成员函数名(参数列表)self.成员函数名(参数列表)>>>p1.m_print()Thisisapoint>>>p2.m_print()Thisisapoint【例8-2】成员函数调用举例。第4行代码定义了一个Point类的成员函数m_fun,该函数体中调用了m_print成员函数。所以在第10行对象p2调用成员函数m_fun时,会先执行第5行代码调用m_print函数,输出"Thisisapoint",然后执行第6行代码输出"Test"。8.2.3类的成员函数12345678910classPoint:defm_print(self):print("Thisisapoint")defm_fun(self):

self.m_print()#调用其它成员函数print("Test")p1=Point()p2=Point()p1.m_print()p2.m_fun()>>>

ThisisapointThisisapointTest类的成员函数与普通类外函数的使用相差不大,也支持默认参数值。【例8-3】带默认值参数的成员函数举例。8.2.3类的成员函数12345678910classPoint:defm_print(self):print("Thisisapoint")defm_fun(self,s="Test"):self.m_print()print(s)p1=Point()p1.m_print()p1.m_fun()p1.m_fun("Message")>>>

ThisisapointThisisapointTestThisisapointMessage类的成员除了动态属性外还有静态属性,动态属性用成员函数来实现,静态属性则用成员数据来实现。Python的成员数据包括类属性和实例(对象)属性两种。类属性是定义类时在成员函数外定义的变量,也可以是在类成员函数内或类外用“类名.属性名”创建的变量。该变量不属于某个对象,属于这个类,类属性则是所有对象共享。类属性可以用对象名.类属性名或类名.类属性名在类的定义外部进行调用。实例属性是在成员函数内定义的变量,或在类外用“对象名.属性名”创建的变量。每个对象的实例属性都是相互独立的。实例属性可以用“self.属性名”在成员函数中创建,在类定义外也可以用“对象名.属性名”为该对象增加实例属性。8.2.4类的成员数据【例8-4】类属性举例。8.2.4类的成员数据1234567891011121314classPoint:x=0#定义Point类属性z=1#定义Point类属性#Point.k=10#报错,因为Point还没有定义defsetXY(self,x,y):Point.x=x#修改Point类属性x的值Point.y=y#定义Point类属性Point.w=2#定义Point类属性defgetX(self):returnPoint.xdefgetY(self):returnPoint.ydefgetZ(self):returnPoint.z151617181920212223242526p1=Point()p2=Point()p1.setXY(5,6)Point.v=22#类外创建Point类属性print("p1.x=",p1.getX(),",p1.y=",p1.y)print("p1.z=",p1.getZ(),",p1.w=",p1.w,",p1.v=",p1.v)print("p2.x=",p2.getX(),",p2.y=",p2.y)print("p2.z=",p2.getZ(),",p2.w=",p2.w,",p2.v=",p2.v)#print("Point.x=",Point.getX())#报错,getX是成员函数#print("Point.z=",Point.getZ())#报错,getZ是成员函数print("Point.y=",Point.y)print("Point.w=",Point.w,",Point.v=",Point.v)>>>

p1.x=5,p1.y=6p1.z=1,p1.w=2,p1.v=22p2.x=5,p2.y=6p2.z=1,p2.w=2,p2.v=22Point.y=6Point.w=2,Point.v=22【例8-5】实例属性举例。8.2.4类的成员数据1234567891011classPoint:#self.z=1#报错,实例属性必须在函数内定义defsetXY(self,x,y):self.x=xu=y#创建局部变量udefgetX(self):returnself.xdefeditX(self,x):self.x=xp1=Point()p2=Point()12131415161718192021p1.setXY(5,9)p2.setXY(44,77)Point.v=22#添加类属性print("p1.x=",p1.getX(),",p2.x=",p2.getX())p1.editX(10)print(p1.x,p2.x)#print(Point.x)#报错,x不是类属性p1.w=2#给对象p1添加实例属性print(p1.w)#print(p2.w)#报错,p2中没有w成员变量>>>

p1.x=5,p2.x=4410442关于类属性和实例属性,总计如下:类属性属于类本身,可以通过类名进行访问或修改,如Point.x类属性也可以被类的所有实例对象访问或修改,如p1.x在类定义之后,可以通过类名动态添加类属性,新增的类属性被类和所有实例共有类的实例属性只能通过类名的对象访问在类的对象生成后,可以动态添加实例属性,但这些添加的实例属性只属于该对象8.2.4类的成员数据Python允许在类的定义外为类和对象增加新的数据和行为,这称为混入机制。这种混入机制在大型项目的开发中会非常方便和实用。在类定义外为Point类增加类属性v:Point.v=22为Point类对象p1增加实例属性w:p1.w=2,而同样的Point类对象p2却没有增加w这个实例属性。这两种动态增加成员数据的方法都属于混入机制。行为是一个动态属性,而函数是行为的代码实现,在Python中还可以把函数增加到实例里面,可以采用type.MethodType()把独立定义的外部函数变为一个对象的成员函数,但需要导入types模块。8.2.4类的成员数据【例8-6】成员函数的混入机制举例。8.2.4类的成员数据123456789101112131415classPoint:defsetX(self,x):self.x=xp1=Point()p2=Point()p1.setX(5)p2.setX(10)importtypes#导入types模块deffun(self,y):self.y=yp1.setY=types.MethodType(fun,p1)p1.setY(6)print(p1.x,p1.y)#p2.setY(11)#报错#print(p2.x,p2.y)#报错>>>

56类与对象的关系就相当于基本数据类型与变量的关系,是类型与实例的关系。同属一个类的多个对象之间的区别在两个方面一个是对象名不同,另一个是对象的成员数据值不同。Python中通过“=”直接创建变量并对变量赋初值,而在创建类的对象时,也可以在创建对象的同时用数据对对象赋初值。在定义对象的时候对其成员数据进行设置,称为对象的初始化。Python语言可以通过赋值来创建一个变量并对变量赋初值,创建一个变量意味着程序要为变量在内存中创建一定的空间,并写入变量的初始值。对象的创建,系统需要为对象创建一定的内存空间。由于对象远比基础类型的变量要复杂,它除了具有成员函数还有成员数据,在创建对象并对对象进行赋初值时,系统是不知道如何产生代码来实现对成员数据的赋值,这就需要程序员来自己撰写初始化程序。对象在创建时,利用特殊值对实例属性进行赋初值的过程,是通过构造函数实现的。8.3构造函数构造函数是类的一个成员函数,除了具有一般成员函数的特性外,还有一些特殊的性质。构造函数名为__init__,并且没有返回值。只要类中有了构造函数,在创建新对象时会自动调用并运行构造函数的代码实现实例属性赋值。一个类有且只有一个构造函数。调用时无需提供参数的构造函数称为默认构造函数。如果类中没有手动定义构造函数,Pyhon会自动生成一个隐含的默认构造函数,该构造函数的参数列表和函数体都为空。但如果类中定义了构造函数(无论是否有参数),Python便不会再为该类生成一个隐含的构造函数。定义构造函数的语法为:构造函数的使用8.3构造函数def__init__(self,[参数列表]):

函数体对象名=类名([参数列表])【例8-7】构造函数举例。8.3构造函数1234567891011classPoint:def__init__(self,x,y): self.x=x self.y=ydefgetX(self): returnself.xdefgetY(self): returnself.yp1=Point(5,10)print(p1.getX(),p1.getY())#p2=Point()#报错>>>510【例8-8】构造函数举例。8.3构造函数1234567891011classPoint:def__init__(self,x=0,y=0): self.x=x self.y=ydefsetXY(self,x,y): self.x=x self.y=ydefgetX(self): returnself.xdefgetY(self): returnself.y1213141516171819202122p1=Point()p2=Point(5,10)print("p1.x=",p1.getX(),end=',')print("p1.y=",p1.y)print("p2.x=",p2.getX(),end=',')print("p2.y=",p2.y)p1.setXY(25,30)print("p1.x=",p1.getX(),end=',')print("p1.y=",p1.getY())print(p2.x)print(p1.x)>>>p1.x=0,p1.y=0p2.x=5,p2.y=10p1.x=25,p1.y=30525【例8-9】创建一个矩阵类,矩形类有长、宽属性,有一个成员函数来求面积,创建矩阵类对象时,有构造函数给长、宽赋0值。现在有一个长、宽都为20的正方形和一个长、宽分别为3、5的矩形。现编程通过矩形类来求正方形和矩形的面积。8.3构造函数12345678910classRect:def__init__(self,l=0,w=0):self.l=lself.w=wdefgetArea(self):returnself.l*self.wrect1=Rect(20,20)print("rect1=",rect1.getArea())rect2=Rect(3,5)print("rect2=",rect2.getArea())>>>

rect1=400rect2=15类的成员包括成员数据和对成员数据操作的成员函数,它们分别描述事物的静态属性和动态行为,是不可分割。类成员的访问权限有三种:公有成员、私有成员、保护型成员。公有成员是类对外的接口,类中除了私有成员、保护型成员和系统定义的特殊成员外都是公有成员,一般将对数据的操作设为公有成员,公有成员可以通过对象名直接调用。私有成员是以双下划线开头的属性或方法,如:__hour、__minute、__second。正常情况下私有成员只允许在类定义中被本类的其它成员访问,类定义外对私有成员的访问都是非法的。但在Python中可以通过“类的对象名._类名__成员名”方式来强行访问私有成员“__成员名”,这种破坏类封装特性的方式不建议使用。私有成员被隐藏在类中,保护了数据的安全性,所以一般而言,类的数据成员都应该被设置为私有成员。保护型成员是以单下划线开头的属性或方法,如:_x、_y。保护型成员在类定义中可以被本类的其它成员访问,类定义外也可以被直接访问。8.4类成员的访问权限【例8-10】类成员的访问权限举例。8.4类成员的访问权限12345678910111213141516171819classPoint:def__init__(self,a=0,b=0,c=0):Point.__x=a#私有成员Point._y=b#保护成员Point.z=cdefgetX(self):returnPoint.__xdefgetY(self):returnPoint._yp1=Point()print(p1.getX(),p1.getY(),Point.z)p2=Point(3,9,15)#print(p1.__x)#报错,私有成员无法在类外访问#print(Point.__x)#报错,私有成员无法在类外访问print(p1.getX(),p1.getY(),Point.z)print(p2.getX(),p2.getY(),Point.z)print(p1._y,p2._y,Point._y)print(p2.z,p1.z,Point.z)#可以直接访问print(p2._Point__x)#强行访问类p2的私有成员__x>>>000391539159991515153

【例8-11】类成员的访问权限举例。8.4类成员的访问权限1234567891011121314151617classPoint:def__init__(self,a=0,b=0,c=0):self.__x=aself._y=bself.z=cdefgetX(self):returnself.__xdefgetY(self):returnself._yp1=Point()print(p1.getX(),p1.getY(),p1.z)p2=Point(3,9,15)print(p1.getX(),p1.getY(),p1.z)print(p2.getX(),p2.getY(),p2.z)#print(p1.__x,p2.__x)#报错print(p1._y,p2._y)#可以直接访问print(p2._Point__x)#强行访问类p2的私有成员__x>>>0000003915093【例8-12】类成员的访问权限举例。8.4类成员的访问权限123456789101112131415161718classA:def__init__(self,val1=0,val2=0):self._val1=val1#protectedself.__val2=val2#privatedefsetval(self,a,b):self._val1=aself.__val2=bdefaddval(self,val1,val2):self._val3=val1self.__val4=val2def__show(self):print(self._val1)print(self.__val2)defshow(self):print(self._val1)print(self.__val2)print(self._val3)print(self.__val4)192021222324252627a=A()b=A(1,2)print(a._val1)a.setval(2,3)a.addval(5,10)a.show()#b.show()#报错#a.__show()#报错print(a)>>>023510<__main__.Aobjectat0x000002A6E5804888>【例8-13】定义并实现一个平面点类Point,有x方向、y方向两个坐标值,有成员函数修改x、y坐标值。8.4类成员的访问权限12345678910111213classPoint:def__init__(self,x=0,y=0):self.__x=xself.__y=ydefset(self,x,y):self.__x=xself.__y=ydefgetX(self):returnself.__xdefgetY(self):returnself.__ydefshow(self):print('(',self.__x,',',self.__y,')')14151617181920p1=Point(3,4)p2=Point(5,6)p1.show()p2.show()p2.set(7,9)p2.show()print((p1.getX()-p2.getX())**2+(p1.getY()-p2.getY())**2)>>>(3,4)(5,6)(7,9)41【例8-14】定义并实现一个矩形类,有长、宽两个属性,有成员函数计算矩阵的面积。8.4类成员的访问权限12345678910111213classRect:def__init__(self,l=0,w=0):self.__l=lself.__w=wdefsetLW(self,l,w):self.__l=lself.__w=wdefgetL(self):returnself.__ldefgetW(self):returnself.__wdefcalArea(self):returnself.__w*self.__l>>>

Area_rect1=0Area_rect2=24Area_rect1=200141516171819rect1=Rect()print("Area_rect1=",rect1.calArea())rect2=Rect(4,6)print("Area_rect2=",rect2.calArea())rect1.setLW(10,20)print("Area_rect1=",rect1.calArea())【例8-15】定义复数类Comp,有实部和虚部两个属性,有成员函数set()修改复数实部和虚部的值,有成员函数mod计算复数的模平方,有成员函数add()、minus()实现与其它复数对象相加和相减,有成员函数show()输出该复数。8.4类成员的访问权限12345678910111213141516classComp:def__init__(self,r=0,i=0):self.__r=rself.__i=idefset(self,r,i):self.__r=rself.__i=idefmod(self):returnself.__r**2+self.__i**2defgetR(self):returnself.__rdefgetI(self):returnself.__idefadd(self,c):self.__r+=c.__rself.__i+=c.__i171819202122232425262728293031323334defminus(self,c):self.__r-=c.__rself.__i-=c.__idefshow(self):print(self.__r,'+',self.__i,'i')c1=Comp()c2=Comp()c3=Comp()c1.set(3,4)c2.set(5,10)c1.show()c2.show()print(c1.mod())c2.add(c1)c3=c2c3.show()c3.minus(c1)c3.show()>>>3+4i5+10i258+14i5+10iPython中类的析构函数是__del__(),用于实现销毁类的实例所需的操作,一般用来释放对象占用的资源(如打开的文件、网络连接等),在Python删除对象和收回对象空间时被自动调用和执行。如果用户没有编写析构函数,Python将提供一个默认的析构函数进行必要的清理工作。8.5析构函数【例8-16】析构函数举例。8.5析构函数123456789101112131415161718classPoint:def__init__(self,x=0,y=0):self.__x=xself.__y=ydefgetX(self):returnself.__xdefgetY(self):returnself.__ydefshow(self):print('(',self.__x,',',self.__y,')')def__del__(self):print("Destruction")p1=Point(3,4)p2=Point(5,6)p2.__del__()p2.show()delp1delp2>>>Destruction(5,6)DestructionDestruction类与对象的关系就如基础类型与变量的关系,可以把对象看作一个特殊的变量。基础类型的变量可以作为类的数据成员,类的对象也是可以作为另一个类的数据成员,这种类中的数据成员是另一个类的对象情况,我们称之为类的组合。类的组合可以在已有抽象的基础上实现更复杂的抽象。8.6类的组合【例8-17】类的组合举例。8.6类的组合1234567891011121314151617classPoint: def__init__(self,x,y): self.__x=x self.__y=y defgetX(self): returnself.__x defgetY(self): returnself.__yclassLine: def__init__(self,p1,p2): self.__p1=p1 self.__p2=p2 x=p1.getX()-p2.getX() y=p1.getY()-p2.getY() self.__len=(x*x+y*y)**(1/2) defgetLen(self): returnself.__len>>>5.018192021p1=Point(1,2)p2=Point(4,6)l=Line(p1,p2)print(l.getLen())面向对象程序设计中的封装特性通过类的成员变量和成员函数来实现,多态性是面向对象程序设计的第二个特性,多态是指不同类型的对象接收同样的消息时出现了不同的行为。也就是同样的运算被不同的对象调用时产生不一样的行为,最简单的例子就是算术运算符,同样的加号、减号,被不同的类型数据如整型、浮点型调用时,会产生不一样的结果整型相加、减得到的结果是整型浮点型相加、减得到的结果是有小数点的浮点型整型和浮点型相加则会先将整型隐式转为浮点型再求加、减,最后得到的结果是浮点型。这就是典型的多态性,同样的运算符被不同类型的数据调用,会根据不同的数据类型进行不一样操作过程,运算符的多态性我们称之为运算符重载。Python中运算符的操作对象一般都是基础数据类型,如整型、浮点型、字符串、列表等。而对于新创建的数据类型,这些运算符都是不支持的。8.7多态性与运算符重载

8.7多态性与运算符重载123456789classComplex: def__init__(self,r=0,i=0): self.__r=r self.__i=i defshow(self): print(self.__r,'+',self.__i,'i')c1=Complex(1,2)c2=Complex(3,6)print(c1+c2)运行结果:TypeError:unsupportedoperandtype(s)for+:'Complex'and'Complex'上面程序中由于c1和c2是新定义类Complex的两个对象,Python不清楚如何将一个Complex对象与另外一个Complex对象相加,也就无法生成实现c1+c2的代码,程序报错。如果创建了一个新类型,要想让Python的运算符支持新类型的对象,则用户必须自己手动定义相应的特殊函数来实现,这就是运算符重载。Python中除了构造函数和析构函数外,还有大量的特殊方法支持更多的功能,运算符重载就是通过在类中重新定义特殊函数来实现的。通过在自定义类中重写特殊函数能够增加类的基础类型运算功能。8.7多态性与运算符重载

8.7多态性与运算符重载函数功能说明__new__()类的静态方法,用于确定是否要创建对象__init__()构造方法,创建对象时自动调用__del__()析构方法,释放对象时自动调用__add__()、__radd__()左+、右+__sub__()-__mul__()*__truediv__()/__floordiv__()//__mod__()%__pow__()**__eq__()、__ne__()、__lt__()、__le__()、__gt__()、__ge__()==、!=、<、<=、>、>=__lshift__()、__rshift__()<<、>>__and__()、__or__()、__invert__()、__xor__()&、|、~、^__iadd__()、__isub__()+=、-=,很多其他运算符也有与之对应的复合赋值运算符

8.7多态性与运算符重载__pos__()一元运算符+,正号__neg__()一元运算符-,负号__contains__()与成员测试运算符in对应__radd__()、__rsub__反射加法、反射减法,一般与普通加法和减法具有相同的功能,但操作数的位置或顺序相反,很多其他运算符也有与之对应的反射运算符__abs__()与内置函数abs()对应__bool__()与内置函数bool()对应,要求该方法必须返回True或False__bytes__()与内置函数bytes()对应__complex__()与内置函数complex()对应,要求该方法必须返回复数__dir__()与内置函数dir()对应__divmod__()与内置函数divmod()对应__float__()与内置函数float()对应,要求该该方法必须返回实数__int__()与内置函数int()对应,要求该方法必须返回整数__len__()与内置函数len()对应__next__()与内置函数next()对应

8.7多态性与运算符重载__reduce__()提供对reduce()函数的支持__reversed__()与内置函数reversed()对应__round__()对内置函数round()对应__str__()与内置函数str()对应,要求该方法必须返回str类型的数据。__repr__()打印、转换,要求该方法必须返回str类型的数据。直接使用该类对象作为表达式来查看对象的值__getitem__()按照索引获取值__setitem__()按照索引赋值__delattr__()删除对象的指定属性__getattr__()获取对象指定属性的值,对应成员访问运算符“.”__getattribute__()获取对象指定属性的值,如果同时定义了该方法与__getattr__(),那么__getattr__()将不会被调用,除非在__getattribute__()中显式调用__getattr__()或者抛出AttributeError异常__setattr__()设置对象指定属性的值__base__该类的基类__class__返回对象所属的类__dict__对象所包含的属性与值的字典__subclasses__()返回该类的所有子类__call__()包含该特殊方法的类的实例可以像函数一样调用__get__()定义了这三个特殊方法中任何一个的类称作描述符(descriptor),描述符对象一般作为其他类的属性来使用,这三个方法分别在获取属性、修改属性值或删除属性时被调用__set__()__delete__()如果用print函数输出一个对象,会显示这个对象的类名及内存所在地址。如果希望print函数输出一个对象,能够显示对象的内容,就需要在定义类时重写__str__()或__repr__()函数。【例8-18】重写__str__()函数。8.7多态性与运算符重载1234567891011121314classItem:def__init__(self,name,price):self.__name=nameself.__price=priceim=Item('鼠标',29.8)print(im)classItem:def__init__(self,name,price):self.__name=nameself.__price=pricedef__str__(self):returnself.__name+str(self.__price)im=Item('显示器',999)print(im)>>><__main__.Itemobjectat0x0000020131024C88>显示器999代码第6行用print(im)显示的是im的类型及所在的内存地址。当代码第7行重新定义Item类时,重写了特殊方法__str__(),第14行再次执行print(im)显示则是成员函数__str__()函数的返回值。【例8-19】定义一个Point类,有__x,__y两个实例属性。重载实现Point对象与Point对象(或数字)之间的+、-,对象与数字之间的*、/。8.7多态性与运算符重载1234567891011121314classPoint:def__isNumber(self,n):ifisinstance(n,(int,float)):returnTrueelse:returnFalsedef__init__(self,v1=0,v2=0):self.__x=v1self.__y=v2defsetval(self,v1,v2):self.__x=v1self.__y=v2def__str__(self):returnf'({self.__x},{self.__y})'151617181920212223242526272829303132

def__add__(self,c):t=Point()ifself.__isNumber(c):t.__x=self.__x+ct.__y=self.__y+celse:t.__x=self.__x+c.__xt.__y=self.__y+c.__yreturntdef__sub__(self,c):t=Point()ifself.__isNumber(c):t.__x-=c#t.__x=self.__x-c

t.__y-=c#t.__y=self.__y-celse:t.__x=self.__x-c.__xt.__y=self.__y-c.__yreturnt【例8-19】定义一个Point类,有__x,__y两个实例属性。重载实现Point对象与Point对象(或数字)之间的+、-,对象与数字之间的*、/。8.7多态性与运算符重载3334353637383940414243444546

def__mul__(self,t):ifself.__isNumber(t):self.__x*=tself.__y*=treturnselfdef__truediv__(self,t):ifself.__isNumber(t):self.__x/=tself.__y/=treturnselfp1=Point()print(f'p1={p1}')p1.setval(5,-7)print(f'p1={p1}')4748495051525354555657585960p2=Point(10,20)print(f'p2={p2}')p3=p1+p2print(f'p3={p3}')p4=p1-p2print(f'p4={p4}’)p5=p1*2print(f'p1={p1}')print(f'p5={p5}')p6=p2/5print(f'p6={p6}')print(f'p2={p2}')print(f'p1+2={p1+2}')print(f‘p2-3={p2-3}’)#调用第24行的语句>>>p1=(0,0)p1=(5,-7)p2=(10,20)p3=(15,13)p4=(-5,-27)p1=(10,-14)p5=(10,-14)p6=(2.0,4.0)p2=(2.0,4.0)p1+2=(12,-12)p2-3=(-3,-3)【例8-20】定义复数类Comp,有实部和虚部两个属性。通过函数重载实现Comp类对象之间的+、-、+=、-=运算。8.7多态性与运算符重载1234567891011121314classComp:def__isNumber(self,n):ifisinstance(n,(int,float)):returnTrueelse:returnFalsedef__init__(self,v1=0,v2=0):self.__r=v1self.__i=v2defsetval(self,v1,v2):self.__r=v1self.__i=v2def__str__(self):returnf'{self.__r}+{self.__i}i'1516171819202122232425262728293031

def__add__(self,c):if~self.__isNumber(c):t=Comp()t.__r=self.__r+c.__rt.__i=self.__i+c.__ireturntdef__sub__(self,c):if~self.__isNumber(c):t=Comp()t.__r=self.__r-c.__rt.__i=self.__i-c.__ireturntdef__iadd__(self,c):if~self.__isNumber(c):self.__r+=c.__rself.__i+=c.__ireturnself323334353637383940414243444546

def__isub__(self,c):if~self.__isNumber(c):self.__r-=c.__rself.__i-=c.__ireturnselfa=Comp()print(a)a.setval(10,20)print(a)b=Comp(77,88)print(b)c=a+bprint("c=a+b:",c)a+=cprint("a+=c的结果:",a)>>>0+0i10+20i77+88ic=a+b:87+108ia+=c的结果:97+128i继承是用来实现代码复用和设计复用的机制,是面向对象程序设计的三大特性之一。继承机制允许程序员在保持原有类特性的基础上,进行更具体、更详细的说明。设计一个新类时,如果可以继承一个已有的设计良好的类,然后进行二次开发,会大幅度减少开发工作量。从不同的角度来看,保持已有类的特性而构造新类的过程称为继承。在继承已有类的特性基础上,新增自己的特性而产生新类的过程称为派生。被继承的已有类称为基类或父类,派生出的新类称为派生类或子类。8.8继承与派生

8.8继承与派生派生类主要有3个部分吸收基类成员。派生类实际上就包含了它的全部基类中除构造函数之外的所有成员。改造基类成员。如果派生类声明了一个和某基类成员同名的新成员,派生的新成员就屏蔽掉外层基类的同名成员。添加新的成员。派生类新成员的加入是继承与派生机制的核心,是保证派生类在功能上有所发展。创建派生类对象时调用派生类的构造函数,在构造函数中会优先调用基类的构造函数来创建与初始化从基类继承来的成员,再创建派生类自己的新成员。8.8继承与派生类继承的语法形式如下:在基类Point的基础上派生出类PointXYZ,即在平面点类的基础上派生出空间点类。类PointXYZ在继承类Point的x、y轴坐标属性和一切成员函数的基础上,增加z轴坐标属性,以及增加对z属性操作的函数,如getZ()函数,来获取z属性的值。于是在定义类PointXYZ时,就不用重复定义x、y属性和对x、y属性操作的成员函数,很好地实现了代码复用。8.8继承与派生class类名(基类列表):'''类说明'''

类体classPointXYZ(Point):pass定义派生类时,派生类可以继承基类的公有成员,但不能直接访问基类的私有成员,必须通过基类公有成员函数实现对基类私有成员的访问。派生类可以直接访问从基类继承来的公有成员,就如访问自己的成员一样,使用“self.成员名”格式来访问。但如果在派生类中创建了与基类同名的成员函数,派生类会屏蔽掉同名的外层基类成员函数,这时候“self.函数名”只调用派生类的同名函数。如果想访问外层基类的同名成员函数,则要通过“super().基类函数名(实参列表)”,或指定基类名的方式“super(派生类名,self).基类函数名(实参列表)”、“基类名.基类函数名(self,实参列表)”来实现。如果基类手动定义了构造函数,则在派生类的构造函数中一定要显示调用基类的构造函数。Python支持多继承,如果基类中有相同的函数名,而在子类使用时没有指定是哪个基类,则Python解释器将从左向右按顺序进行搜索。8.8继承与派生【例8-21】类继承实例。8.8继承与派生123456789101112

1314classPoint:def__init__(self,x=0,y=0):self.__x=xself.y=ydefmove(self,offX,offY):self.__x+=offXself.y+=offYdefgetX(self):returnself.__xclassPointXYZ(Point):def__init__(self,x=0,y=0,z=0):super().__init__(x,y)#或者Point.__init__(self,x,y)self.y=y#本条语句可省self.__z=z1516

1718192021

22232425

defmove(self,offX,offY,offZ):super().move(offX,offY)#或者Point.move(self,offX,offY)self.__z+=offZdefgetZ(self):returnself.__zdef__str__(self):returnf'({self.getX()},{self.y},{self.__z})’p=PointXYZ(2,3,4)p.move(4,-5,-6)print(p.getX(),p.y,p.getZ())print(p)>>>6-2-2(6,-2,-2)【例8-22】类继承实例。8.8继承与派生12345678910111213classA:def__init__(self):self.__private()self.public()def__private(self):print('__private()methodinA')defpublic(self):print('public()methodinA')classB(A):def__private(self):print('__private()methodinB')defpublic(self):print('public()methodinB')14151617181920212223classC(A):def__init__(self):#显式定义构造函数self.__private()self.public()def__private(self):print('__private()methodinC')defpublic(self):print('public()methodinC')b=B()c=C()>>>__private()methodinApublic()methodinB__private()methodinCpublic()methodinC代码第22行创建对象b,会调用从类A继承来的构造函数__init__,即程序进入代码第3行,由于__init__是类A的函数,调用的私有函数成员__private也应该是类A的,第4行调用的public函数也应该是类A的公有成员函数,但由于从类A继承来的公有成员函数public被类B自己创建的公有成员函数public屏蔽了,就无法执行类A的public函数,只会调用类B的public函数。【例8-23】类继承实例。8.8继承与派生1234567891011121314151617181920classPerson(object):#若某个类中没有指定基类,默认基类为object类def__init__(self,ID='',name='',gender='m'):self.setID(ID)self.setName(name)self.setGender(gender)defsetID(self,ID):ifnotisinstance(ID,str):print('IDmustbestring.')returnself.__ID=IDdefsetName(self,name):ifnotisinstance(name,str):print('name

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论