5.2 面向对象
面向过程与面向对象的区别
面向过程,面向对象,它俩都是编程思想,就是解决问题的思路有差别
举例:我要去吃饭 饭怎么来
面向过程:去买菜,回家洗菜,做饭,亲力亲为,事情是由自己来做的
-
事情自己做,费时间,费精力,执行者(自己做)
面向对象:下馆子,叫外卖,饭不是自己做的,而是由另外一种事物来做
-
事情别人做,省时间,省精力,指挥者(指挥别人做)
我们之前写到Python代码,大部分都是面向过程的!都是自己一步一步来实现问题的求解步骤的,只不过在某一些细节,我们用到了面向对象的知识,比如字符串对象内置的函数(s.upper()),比如列表对象内置的函数(l.append()),相当于把这些对象给他拟人化,我们相当于是指挥这些事物来给我们做事情,至于它们具体是怎么做的,我们没有必要操心,我们只关注操作后的结果即可!
我想吃饭-不想做-叫外卖(面向对象)-餐馆-接到了我的餐饮订单-餐馆就得开始做饭(面向过程)
面向过程的思想和面向对象的思想,是互斥的关系吗?不是的
所有的代码宏观上而言都是面向过程(一步一步来求解问题的),只不过在某些步骤中,会使用别的对象自带的功能来帮我们处理一些问题
# 输入10个数据并进行排序
# 输入10个数据并进行排序
arr = []
for i in range(0,10):
arr.append(input())
arr.sort()
for i in range(1,len(arr)):
....插入排序
arr = [] for i in range(0,10): arr.append(input()) arr.sort() for i in range(1,len(arr)): ....插入排序
代码是否完全由可以用面向对象的思路去做呢?
A-B-C-D-E.......依次往复下去,这个事始终没有被解决,只要这个事要解决,那么肯定是要一步一步实现的,所以这个时候用到的就是面向过程的思路!对象.方法()
代码可以完全面向过程,但是不能完全面向对象!完全自己实现功能
类与对象的区别
什么是对象?所谓的对象就是现实中真正存在的事物,在计算机领域中,对象就是在硬盘或内存中真实创建出来的数据。
什么是类?所谓的类就是多个具有相同属性和行为的对象的统称
-
我喜欢狗:狗类
-
我喜欢你家的狗:对象
那么在实际生活中,先有对象,还是先有类?先存在各种事物(对象),类主要是由人对世界上的事物进行划分的概念
那么在计算机中,先有对象,还是先有类?先有类,我们程序员是通过计算机对世界的仿真和模拟来实现编程,所以事先将相关类别的信息告诉计算机,然后计算机再通过这些类的描述再去创建相关的数据对象。
如何来描述一个类(应该具有哪些信息)?
-
她的高子很高:身高 - 属性 - 数据 - 数字
-
她的名字很好听:姓名 - 属性 - 数据 - 字符串
-
他的智商很高:智商 - 属性 - 数据 - 数字
-
他打篮球打的好:打篮球 - 行为(功能)- 函数
-
她唱歌很好听:唱歌 - 行为(功能) - 函数
综上说所属,描述一个类的时候,我们重点描述的是这个事物属性
和 行为
在编程当中,只有类先定义出来,才能去创建相关的对象
-
类是一个大楼的图纸,具体的一个大楼 就是该图纸的一个对象
如何创建类和对象
在计算机当中,先有类,后有对象,尤其是在Python语言当中:
class 类名: def __init__(self,...): # 构造函数 self.属性1 = 属性值 # 实例变量 self.属性2 = 属性值 def method(self,...): # 实例函数
-
类名:就是你要描述事物的名称
-
构造函数
__init__(self)
:就是在创建对象的过程中,对对象相关的属性进行初始化的,只能在创建对象的时候使用,但凡对象一旦创建完毕 ,则该函数不能在被对象调用 -
实例变量:就是指每一个对象的属性 对象的特有属性 每个对象都有 但是值会不一样
-
实例函数:就是指每一个对象的行为 从内存角度而言只有一份 在方法区中 类的二进制代码里 如果要被对象调用 用self关键字来进行区分即可 只有对象创建之后才能通过对象来调用
-
self:关键字 当前对象的引用
-
class:关键字 类定义
举例:创建圆这个类
-
属性:半径
-
行为:求面积,求边长
# 类的定义 class Circle: # 定义Circle这个类 def __init__(self, radius = 1): # 构造函数 radius = 1 默认参数 self.radius = radius # 创建实例变量self.radius 它的值等于传入的radius的值 def getArea(self): # 实例函数 用于计算面积 return self.radius ** 2 * 3.14 # 根据自身的半径,来计算面积 def getPermeter(self): # 实例函数 用于计算周长 return 2 * 3.14 * self.radius # 根据自身的半径,来计算周长
# 创建Circle对象 其实就是在调用__init__()函数 c1 = Circle() # 创建Circle对象 但是不指定radius的值 c2 = Circle(10) # 创建Circle对象 指定radius的值为10 # 对象.函数() 对象.属性 来调用对象的内容 print(c1.radius) print(c2.radius) print(c1.getArea()) print(c1.getPermeter()) print(c2.getArea()) print(c2.getPermeter())
-
相关内存
-
栈内存:用于执行函数的
-
堆内存:存放数据对象
-
方法区:存储的是编译好的二进制代码,主要存放的就是类的二进制代码
-
-
将Circle这个类的二进制代码加载进方法区中
-
执行
c1 = Circle()
这段代码 -
现在堆内存中开辟一个空间,该空间有其真实的物理内存地址
0x123
-
调用对应的构造函数进栈执行
__init__(self,radius = 1)
,当前对象是Circle
类的实例,所以从方法区中Circle
二进制类代码中调用 -
构造函数进栈之后,先区分是哪个对象调用的该构造函数,是
0x123
这个对象调用的,所以该构造函数中形式参数self
(局部变量)存储的就是0x123
这个值,然后外界并没有传入radius
的值,所以构造函数中形式参数(局部变量)radius
默认值为1 -
self.radius = radius
将局部变量radius
的值给对象当中的radius
,那么是给哪个对象呢?self = 0x123
,所以给的是0x123
对象中的radius
实例变量(由于对象该空间中开始没有radius
变量,所以第一次赋值即创建) -
c1 = Circle()
构造函数执行完毕之后,将对象的地址给c1
,c1
存储的是第一个Circle对象的地址0x123
-
同理
c2 = Circle(10)
,步骤和上述一样,只不过给构造函数传递了radius
的实际参数10
-
c1.getArea()
,先读取c1
变量中的地址0x123
,然后在堆内存中找0x123
对应的对象,找到之后,发现这个对象是Cilcle
类的一个对象,所以在调用getArea()
方法时,从方法区中Circle
二进制代码中调用的 -
getArea()
进栈执行,为了区分是哪个对象调用的getArea()
,此时self
表示为c1
所存储的地址,self = 0x123
,所以在getArea()
中,关于self
的操作都是找0x123
这个对象的,计算完毕之后返回值并弹栈
在类和对象中不同变量的作用域
-
实例变量:在对象的内存中,每个对象都有各自的实例变量,属于对象特有数据,在类中(类中函数里),调用实例变量的话,必须是
self.xxx
形式,在外通过对象调用,在类内部全局可访问 -
类变量:在方法区中存在,是每个对象的共享数据,每个对象都可以访问到该数据,通过
对象.xxx
形式,也可以通过类.xxx
形式访问,如果实例变量和类变量重名的话,对于对象.xxx
形式它调用的是实例变量,类.xxx
形式只能调用类变量,不能调用实例变量 -
形式参数变量:主要在函数的参数列表中体现,可以有默认值,也可以没有,具体的值由外界的实际参数来传入,它所存储的位置在其所属函数的内部,作用域在当前函数内部
-
函数内部局部变量:它并没有体现在函数的参数列表中,它是直接在函数内部中创建的变量,作用域在当前函数内部
-
全局变量:它是类之外的变量,主要在类的内部尤其是实例函数或者构造函数中调用,前提就是在当前函数的内部,并没有其他的局部变量和其重名的话,如果重名,调用的就是函数内部的局部变量,但是我们一般不去使用,有危险性的,
它压根就不属于类内部的数据!
class Demo: num = 1000 # num 类变量 全部对象共享的数据 可以使用对象调用 也可以使用类调用 def __init__(self,num): print(num) # num 是init函数的局部变量(形式参数) self.num = 666 def showA(self): print(num) # num 是全局变量(类之外的变量) def showB(self): print(self.num) # num 对象中的实例变量 def showC(self): num = 888 print(num) # num 函数中创建的局部变量 def showD(self): print(num) # num 是全局变量(类之外的变量) # main num = 100 d1 = Demo(10) d1.showA() d1.showB() d1.showC() print(num) d1.showD() print(Demo.num) print(d1.num)
数据安全性的问题 - 私有化问题
对于上述的内容,我们发现,外界可以通过对象.xxx
的形式来访问对象内部的实例变量,同时也可以在外部修改该对象的实例变量的值,但是从业务逻辑而言,这种修改是不可靠不安全的
对象的实例变量是属于对象的一种特有数据,外界如果要访问对象的特有数据,需要“征得”对象同意的,并且也不能随意更改对象的特有数据。
举例:小张,看一下你内裤的颜色
所以说明,有些对象的特有数据是不能随意向外界公开的,也就意味着外界不能调用对象的特有数据,此时我们就得需给这些实例变量加上私有权限,self.__xxx
对象的特有数据进行私有化(类的内部可以随意访问,但是在类的外部不能访问)
举例:小张,你把你的心脏给我看看
心脏这个数据不能随意给外界看的,所以心脏这个数据我们需要私有化,一旦私有化之后,外界就不能访问了。但是,如果小张出现了心脏病继续做手术,医生需要开膛,对心脏做手术,这个时候小张能拒绝吗?不能,医生还是要访问的。
综上所述,我想给大家表达的一个概念,对于对象的特有属性,它不是不能提供给外界,但就怕一旦提供给了外界,外界就可以随意修改,对象一点脾气也没有,那我们是不是应该将修改的主体意识转为对象,需要对象的同意你才能进行修改。外界建议对象修改数据,至于对象该不该,看对象自己!从代码的角度而言,如果实例变量一旦被私有化,它既不能被外界修改,同时也不能被外界访问。
相对而言修改的危害要大于访问的危害,你为了拒绝外界直接修改,一竿子打死,私有化,外界虽然修改不了,但也访问不了
处理的方式:
-
对没有必要向外界公开的数据进行私有化
-
向外界提供可以修改数据的方法,在方法的内部,对外界传入的修改建议进行甄别,由对象自身来决定是否修改
setXXX(data)
修改器 -
向外界提供可以访问数据的方法
getXXX()
访问器
class Student:
# name age id 位置参数 按顺序传递 位置参数必须要满足的 必须要传递的 数量必须是一致的
# sc1 sc2 sc3 默认参数 可传可不传(默认值) 但是必须在位置参数之后
def __init__(self, name, age, id, sc1 = 0 , sc2 = 0, sc3 = 0):
self.name = name
self.__age = age
self.id = id
self.sc1 = sc1
self.sc2 = sc2
self.sc3 = sc3
self.sc = self.sc1 + self.sc2 + self.sc3
def speak(self):
print("大家好,我%s,今年%d岁,学号是%s,语文%d分,数学%d分,英语%d分,总分%d" % (self.name,self.__age,self.id,self.sc1,self.sc2,self.sc3,self.sc))
def getSC(self):
return self.sc
def getAge(self):
return self.__age
def setAge(self, age):
if age > 0:
self.__age = age
# age已经私有化了 访问不了 修改不了
# print(s1.age)
# s1.age = -10
print(s1.getAge())
s1.setAge(-20)
s1.setAge(100)
s1.speak()
额外的补充,如果对象有一些实例函数也不想被外界访问的话,也可以将这些函数进行私有化
"""
定义点类
"""
class Point:
def __init__(self,x = 0,y = 0):
self.__x = x
self.__y = y
def distance(self, otherPoint):
x1 = self.__x
y1 = self.__y
x2 = otherPoint.getX()
y2 = otherPoint.getY()
return ((x1 - x2) ** 2 + (y1 - y2) ** 2) ** 0.5
def setX(self,x):
self.__x = x
def setY(self,y):
self.__y = y
def getX(self):
return self.__x
def getY(self):
return self.__y
def __show(self):
print(self.__x, self.__y)
p1 = Point()
p2 = Point(1,1)
print(p1.distance(p2))
p1.show() # 函数被私有化了 外界访问不了了