什么是面向对象编程?
面向对象编程(Object-oriented Programming,简称 OOP),“面向对象”是一种编程思想,它与“面向过程”的编程思想有很大不同,在解决不同类型的问题时采用不同的编程思想可以大幅度提升代码效率,两种思想并没有优劣之分。
面向过程:分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
面向对象:把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
举例说明:
例1:实现把小狗关进笼子
面向过程:
(1)打开笼子
(2)装进小狗
(3)关上笼子
面向对象:
创建一个对象,这个对象能够完成“把小狗装进笼子”的行为,调用对象完成这一系列行为
从面向过程——面向对象,程序员实现了从执行者到指挥者的转变
例2:实现五子棋游戏
面向过程:
(1)开始游戏——(2)黑子先行——(3)绘制棋盘——(4)判断输赢——(5)白子放置——(6)绘制棋盘——(7)判断输赢——(8)返回步骤(2)——(9)输出结果
面向对象:
整个五子棋游戏系统可以分为三部分:
(1)玩家系统:黑白双方,其行为模式相同
(2)棋盘系统:负责绘制实时棋盘
(3)规则系统:对实时棋局做出判断,犯规,失败,胜利,平局等
第一类对象(玩家系统)负责接收用户输入,并把输入结果传递给第二类对象(棋盘系统),告知其棋局变化;第二类对象(棋盘系统)负责绘制棋盘,并将棋局的动态变化输出在屏幕上,同时利用第三类对象(规则系统)对比赛情况进行判定。
可以看出,面向过程是以具体步骤来解决问题,而面向对象则是根据功能来划分问题。
例3:实现输出以下内容:
小郭,20岁,徒步去旅行
小郭,30岁,养了一条狗
小郭,40岁,爱看动画片
老刘,20岁,徒步去旅行
老刘,30岁,依然没结婚
老刘,40岁,爱看动画片
面向过程:
面向对象:
输出结果:
如果要输入小郭和老刘20岁——90岁所有的人生状态呢?采用面向过程,在执行函数时就需要输入很多遍参数{(“小郭”,20),(“老刘”,30)},但采用面向对象,则只需在创建对象时将所需参数封装在该对象内,当再次使用参数时,通过self间接去当前对象中调用即可
创建类和对象
面向对象的编程方式需要用“类”和“对象”来实现。
简单来说,类就是一个模板,模板里可以包含多个函数,函数里实现一些功能;对象则是根据模板创建的实例,通过实例对象可以执行类中的函数。
什么是__init__()?
称其为构造方法或构造函数
首先要明确:类就是一种对象类型, 跟数值、字符串、列表等等类型一样。比如这里构建的类名字叫做Person,那么就是我们要试图建立一种对象类型,这种类型被称之为Person,就如同有一种对象类型是list一样。
在构建Person类的时候,首先要做的就是对这个类进行初始化,也就是要说明这种类型的基本结构,一旦这个类型的对象被调用了,第一件事情就是要运行这个类型的基本结构,也就是类Person的基本结构。
由于类是我们自己构造的,那么基本结构也是我们自己手动构造的。在类中,基本结构是写在__init__()这个函数里面。故这个函数称为构造函数,担负着对类进行初始化的任务。
但这并不意味着一旦创建好类,就会运行构造函数,而是需要“将类实例化”,也就是说,我们必须要根据类创建一个具体的对象,才会运行__init__()函数。
fir = Person("小郭",20,"女“)
上述语句就是将Person类实例化,也就是在内存中创建了一个对象,这个对象的类型是Person类型,通过赋值语句,与变量fir建立引用关系。
如果不写__init__()会怎样?
注意,如果编写时没有为该类定义任何构造方法,那么 Python 会自动为该类创建一个只包含 self 参数的默认的构造方法。
运行结果
可以看到instance没有本应作为长方形而有的“长”“宽”属性,它的属性表是空的。
而且instance不能直接调用类中的方法,会报错。
什么是self?
此处的self,是个对象(Object),是当前类的实例。
fir = Person("小郭“,20,“女”)就相当于Person(fir,“小郭“,20,“女”)。
在Person实例化的过程中,数据"小郭”,20,"女"通过构造函数的参数已经存入到内存中,并且这些数据以Person类型的面貌存在组成一个对象,这个对象和变量 fir 建立引用关系。这个过程也可说成这些数据附加到一个实例上。这样就能够以:object.attribute的形式,在程序中任何地方调用某个数据,例如上面的程序中以fir.name得到"小郭"这个数据。这种调用方式,在类和实例中经常使用,点号“.”后面的称之为类或者实例的属性。
这是在程序中,并且是在类的外面。如果在类的里面,想在某个地方使用传入的数据,怎么办?
在类内部,我们会写很多不同功能的函数,这些函数在类里面有另外一个名称——方法。那么,通过类的构造函数中的参数传入的这些数据也想在各个方法中被使用,就需要在类中长久保存并能随时调用这些数据。为了解决这个问题,在类中,所有传入的数据都赋给一个变量,通常这个变量的名字是self。
在构造函数中的第一个参数self,就是起到了这个作用——接收实例化过程中传入的所有数据,这些数据是通过构造函数后面的参数导入的。显然,self应该就是一个实例(准确说法是应用实例),因为它所对应的就是具体数据。
面向对象的特性
属性用来描述对象的静态特征,用object.attribute 的语法来调用。
方法用来描述对象的动态特征,用object.function()的语法来调用。
比如一头家猪,它的属性(静态特征)是:粉色的,胖的,四条腿,一根尾巴等,它的方法(动态特征)是:会跑,会吃,会睡觉等。
面向对象有三大特性,分别为:封装性,继承性,多态性。下面分别对其做出简单介绍。
封装性
封装,顾名思义就是将内容封装到某个地方,以后再去调用被封装在某处的内容。
所以,在使用面向对象的封装特性时,需要:
1.将内容封装到某处
2.从某处调用被封装的内容
这个”某处“既可以指在类的内部,也可以指在类的外部。
在类的外部调用被封装的内容,此时已经完成对类的实例化(也就是创建了一个对象),调用时直接object.attribute或object.function();如果是在类的内部,则利用self间接调用。
封装使数据和加工该数据的方法(函数)封装为一个整体,以实现独立性很强的模块,使得用户只能见到对象的外特性(对象能接受哪些消息,具有那些处理能力),而对象的内特性(保存内部状态的私有数据和实现加工能力的算法)对用户是隐蔽的。
继承性
继承是类与类之间的关系,是一种创建新的类的方式,新创建的叫子类,继承的叫父类(超类、基类)。子类可以使用父类的属性,方法。通过使用面向对象的继承性,可以减少代码冗余、提高重用性。
语法是:class 类名(父类) 如果定义时并未显式指定这个类的直接父类,则这个类默认继承 object 类。
多态性
对象根据所接收的消息而做出动作。同一消息为不同的对象接受时可产生完全不同的行动,这种现象称为多态性。利用多态性用户可发送一个通用的信息,而将所有的实现细节都留给接受消息的对象自行决定,如是,同一消息即可调用不同的方法。例如:Print消息被发送给一图或表时调用的打印方法与将同样的Print消息发送给一正文文件而调用的打印方法会完全不同。多态性的实现受到继承性的支持,利用类继承的层次关系,把具有通用功能的协议存放在类层次中尽可能高的地方,而将实现这一功能的不同方法置于较低层次,这样,在这些低层次上生成的对象就能给通用消息以不同的响应。