面向对象编程
King
转:https://www.cnblogs.com/zhaof/p/5818905.html
1、面向对象介绍:
世界万物,皆可分类
世界万物,皆为对象
只要是对象,就肯定属于某种类
只要是对象,就肯定有属性
2、 面向对象的几个特性:
class类:
一个类即对一类拥有相同属性的对象的抽象,蓝图、原型。在这个类中定义了这些对象都具备的属性,共同的方法。
object对象:
一个对象即是一个类的实例化,一个类必须经过实例化之后才能在程序中调用
三大特性:封装、继承和多态
封装:在类中对数据的赋值,内部调用对外部用户是透明的,这使类变成了一个胶囊或容器,里面包含着类的数据和方法
继承:一个类可以派生出子类,在这个父类里定义的属性,方法自动被子类继承
多态:
多态是面向对象的重要特性,简单说:一个接口,多种实现 ,只一个基类中派生出了不同的子类,且每个子类在继承了同样的方法名的同时对父类的方法做了不同的体现买这就是同一种事物表现出的多种形态
例子:老板说开始工作,不需要单独对公司的不同部门
如对商务部门说开始工作,对技术部门说开始工作…而是只需要说一句开始工作,这样各个部门就开始工作这就是一种多态的体现
多态允许将子类的对象当做父类的对象使用,某父类的引用指向其子类型的对象,调用的方法是该子类型的方法
3、面向对象编程OOP
无论是面向对象还是面向过程编程,或者其他方式编程,都要明确记住以下原则:
a. 写重复代码是非常不好的低级行为
b. 你写的代码需要经常变更
开发正规的程序跟那种写个运行一次就扔了的脚本有很大的不同,你的代码总是需要不断的更改,不是修改bug就是添加新功能等,所以为了日后方便程序的修改及扩展,你写的代码一定要遵循易读、易改的原则(可读性好、易扩展)
首先是一个简单的例子:用于理解面向对象
1 class Dog:
2 def bulk(self):
3 print (" 小A:wangwangwang.... " )
4 d1 = Dog()
5 d2 = Dog()
6 d3 = Dog()
7 d1.bulk()
8 d2.bulk()
9 d3.bulk()
知识兔 运行结果如下:
1 D:\python35\python.exe D:/python培训/s14/day6/dog.py
2 小A:wangwangwang....
3 小A:wangwangwang....
4 小A:wangwangwang....
知识兔 从运行结果也发现三个小狗都在叫了,但是显示都是小A,如何让传入不同的值,体现让不懂的小狗在叫:
代码例子如下:
1 class Dog:
2 def __init__ (self,name):
3 self.name = name
4
5 def bulk(self):
6 print (" %s:wangwangwang.... " %self.name)
7 d1 = Dog(" 小A " )
8 d2 = Dog(" 小B " )
9 d3 = Dog(" 小C " )
10 d1.bulk()
11 d2.bulk()
12 d3.bulk()
知识兔 运行结果如下:
1 D:\python35\python.exe D:/python培训/s14/day6/dog.py
2 小A:wangwangwang....
3 小B:wangwangwang....
4 小C:wangwangwang....
知识兔 下面从经常玩的cs中人物角色中体现:
角色很简单,就俩个,恐怖份子、警察,他们除了角色不同,其它基本都 一样,每个人都有生命值、武器等。如果不用OPP面向对象编程,方法如下:
1 # role 1
2 name = ' Dean '
3 role = ' terrorist '
4 weapon = ' AK47 '
5 life_value = 100
6
7 # rolw 2
8 name2 = ' Jack '
9 role2 = ' police '
10 weapon2 = ' B22 '
11 life_value2 = 100
知识兔 上面定义了一个恐怖分子Dean和一个警察jack,但是只有2个人肯定不行,我们再建一个恐怖分子和警察
1 # role 1
2 name = ' Alex '
3 role = ' terrorist '
4 weapon = ' AK47 '
5 life_value = 100
6 money = 10000
7
8 # rolw 2
9 name2 = ' Jack '
10 role2 = ' police '
11 weapon2 = ' B22 '
12 life_value2 = 100
13 money2 = 10000
14
15 # role 3
16 name3 = ' Rain '
17 role3 = ' terrorist '
18 weapon3 = ' C33 '
19 life_value3 = 100
20 money3 = 10000
21
22 # rolw 4
23 name4 = ' Eric '
24 role4 = ' police '
25 weapon4 = ' B51 '
26 life_value4 = 100
27 money4 = 10000
知识兔 4个角色虽然创建好了,但是有个问题就是,每创建一个角色,我都要单独命名,name1,name2,name3,name4…,后面的调用的时候这个变量名你还都得记着,要是再让多加几个角色,估计调用时就很容易弄混啦,所以我们想一想,能否所有的角色的变量名都是一样的,但调用的时候又能区分开分别是谁?
当然可以,我们只需要把上面的变量改成字典的格式就可以啦。
1 roles = {
2 1:{' name ' :' Alex ' ,
3 ' role ' :' terrorist ' ,
4 ' weapon ' :' AK47 ' ,
5 ' life_value ' : 100,
6 ' money ' : 15000,
7 },
8 2:{' name ' :' Jack ' ,
9 ' role ' :' police ' ,
10 ' weapon ' :' B22 ' ,
11 ' life_value ' : 100,
12 ' money ' : 15000,
13 },
14 3:{' name ' :' Rain ' ,
15 ' role ' :' terrorist ' ,
16 ' weapon ' :' C33 ' ,
17 ' life_value ' : 100,
18 ' money ' : 15000,
19 },
20 4:{' name ' :' Eirc ' ,
21 ' role ' :' police ' ,
22 ' weapon ' :' B51 ' ,
23 ' life_value ' : 100,
24 ' money ' : 15000,
25 },
26 }
27
28 print (roles[1]) # Alex
29 print (roles[2]) # Jack
知识兔 很好,这个以后调用这些角色时只需要roles[1],roles[2]就可以啦,角色的基本属性设计完了后,我们接下来为每个角色开发以下几个功能
被打中后就会掉血的功能
开枪功能
换子弹
买枪
跑、走、跳、下蹲等动作
保护人质(仅适用于警察)
不能杀同伴
。。。
我们可以把每个功能写成一个函数,类似如下:
1 def shot(by_who):
2 # 开了枪后要减子弹数
3 pass
4 def got_shot(who):
5 # 中枪后要减血
6 who[‘life_value’] -= 10
7 pass
8 def buy_gun(who,gun_name):
9 # 检查钱够不够,买了枪后要扣钱
10 pass
11 ...
知识兔 上述代码存在的问题:
每个角色定义的属性名称是一样的,但这种命名规则是我们自己约定的,从程序上来讲,并没有进行属性合法性检测,也就是说role 1定义的代表武器的属性是weapon, role 2 ,3,4也是一样的,不过如果我在新增一个角色时不小心把weapon 写成了wepon , 这个程序本身是检测 不到的 terrorist 和police这2个角色有些功能是不同的,比如police是不能杀人质的,但是terrorist可能,随着这个游戏开发的更复杂,我们会发现这2个角色后续有更多的不同之处, 但现在的这种写法,我们是没办法 把这2个角色适用的功能区分开来的,也就是说,每个角色都可以直接调用任意功能,没有任何限制。 我们在上面定义了got_shot()后要减血,也就是说减血这个动作是应该通过被击中这个事件来引起的,我们调用get_shot(),got_shot()这个函数再调用每个角色里的life_value变量来减血。 但其实我不通过got_shot(),直接调用角色roles[role_id][‘life_value’] 减血也可以呀,但是如果这样调用的话,那可以就是简单粗暴啦,因为减血之前其它还应该判断此角色是否穿了防弹衣等,如果穿了的话,伤害值肯定要减少,got_shot()函数里就做了这样的检测,你这里直接绕过的话,程序就乱了。 因此这里应该设计 成除了通过got_shot(),其它的方式是没有办法给角色减血的,不过在上面的程序设计里,是没有办法实现的。 现在需要给所有角色添加一个可以穿防弹衣的功能,那很显然你得在每个角色里放一个属性来存储此角色是否穿 了防弹衣,那就要更改每个角色的代码,给添加一个新属性,这样太low了,不符合代码可复用的原则 如果将代码改为面型对象编程的方法:
1 class Role(object): # 定义一个类,Role是类名,注意首字母大写,(object)是新式类的写法,必须这样写
2 def __init__ (self,name,role,weapon,life_value=100,money=15000):# 这一部分是构造函数,#初始化函数,在生成一个角色时要初始化的一些属性需要填写在这里。实例变量(静态属性),作用域就是实例本身
3 # 下面是在实例化时做一些类的初始化工作
4 self.name = name
5 self.role = role
6 self.weapon = weapon
7 self.life_value = life_value
8 self.money = money
9
10 def shot(self): # 类的方法,功能(动态属性)
11 print (" shooting... " )
12
13 def got_shot(self):
14 print (" ah...,I got shot... " )
15
16 def buy_gun(self,gun_name):
17 print (" just bought %s " %gun_name)
18
19 r1 = Role(' Alex ' ,' police ' ,' AK47’) #生成一个角色 实例化,也叫作初始化
20 r2 = Role(' Jack ' ,' terrorist ' ,' B22’) #生成一个角色 实例化,也叫作初始化
知识兔 从面向对象的代码可以看出比期初的代码少了一半
角色和它具备的功能可以一目了然
上面代码中__init__()叫做初始化(或构造方法),在类被调用时,这个方法(虽然它是函数形式,但在类中就不叫函数了,叫方法)会自动执行,进行一些初始化的动作,所以上面写的__init__(self,name,role,weapon,life_value=100,money = 15000)就是在创建一个角色时候,给它设置这些属性
初始化一个角色,就需要调用这个类一次:
1 r1 = Role(' Alex ' ,' police ' ,' AK47’) #生成一个角色 , 会自动把参数传给Role下面的__init__(...)方法
2 r2 = Role(' Jack ' ,' terrorist ' ,' B22’) #生成一个角色
知识兔 也就是创建角色时,并没有给__init__传值,程序也没有报错,这是因为类在调用他自己的__init__(…)时帮你给self参数赋值了,并且传参的时候其实r1也被传入的r1 = Role('Alex','police','AK47’)其实是r1 = Role(r1,'Alex','police','AK47’),所以在类中的self就相当于传入的r1
1 r1 = Role(' Alex ' ,' police ' ,' AK47’) #此时self 相当于 r1 , Role(r1, ' Alex' , ' police' , ' AK47’)
2 r2 = Role(' Jack ' ,' terrorist ' ,' B22’)#此时self 相当于 r2, Role(r2, ' Jack' , ' terrorist' , ' B22’)
知识兔 当你执行r1 = Role('Alex','police','AK47’)时python解释器干了两件事:
在内存中开辟一块空间指向r1这个变量名 调用Role这个类并执行其中的__init__(…)方法,相当于Role.__init__(r1,'Alex','police',’AK47’),这么做是为什么呢? 是为了把'Alex','police',’AK47’这3个值跟刚开辟的r1关联起来,是为了把'Alex','police',’AK47’这3个值跟刚开辟的r1关联起来,是为了把'Alex','police',’AK47’这3个值跟刚开辟的r1关联起来,重要的事情说3次, 因为关联起来后,你就可以直接r1.name, r1.weapon 这样来调用啦。所以,为实现这种关联,在调用__init__方法时,就必须把r1这个变量也传进去,否则__init__不知道要把那3个参数跟谁关联呀。 所以这个__init__(…)方法里的,self.name = name , self.role = role 等等的意思就是要把这几个值 存到r1的内存空间里。 但是在实例化的时候类里面的方法并没有被拷贝一份到实例中,而是所有的实例都是调用类里面的方法,所以调用类中的方法是r1.buy_gun()相当于Role.buy_gun(r1)这也是为什么类里面的方法后面总是加了(self),这样从而区别出了不同的实例调用方法,下面是体现的代码例子: 1 def got_shot(self):
2 print (" %s ah...,I got shot... " %self.name)
3 实例化的时候:
4 r1 = Role(' Alex ' , ' police ' , ' AK47 ' ) # 生成一个角色 实例化,也叫作初始化
5 r1.got_shot()
6 运行结果:
7 D:\python35\python.exe D:/python培训/s14/day6/cs.py
8 Alex ah...,I got shot...
9
10 Process finished with exit code 0
知识兔 所以从这里也可以看出来r1.buy_gun()相当于Role.buy_gun(r1)
对上述的总结:
上面的这个r1 = Role('Alex','police','AK47’)动作,叫做类的“实例化”, 就是把一个虚拟的抽象的类,通过这个动作,变成了一个具体的对象了, 这个对象就叫做实例
刚才定义的这个类体现了面向对象的第一个基本特性,封装,其实就是使用构造方法将内容封装到某个具体对象中,然后通过对象直接或者self间接获取被封装的内容
上面__init__代码中:
self.name = name 表示是实例变量
实例变量的作用域是实例本身
1 self.money = money # 实例的变量(静态属性),作用域就是实例本身
2 def shot(self): # 类的方法,功能(动态属性)
3 print (" shooting... " )
知识兔 动态属性就是方法,静态属性就是变量
同样的也存在类变量
1 class Role(object):
2 n =123 # 类变量,不用实例化也能打印出来
知识兔 实例化之后打印:
1 r1 = Role(' Alex ' , ' police ' , ' AK47 ' ) # 生成一个角色 实例化,也叫作初始化
2 print (r1.n,r1.name)
3 运行结果:
4 D:\python35\python.exe D:/python培训/s14/day6/cs.py
5 123
6 123 Alex
7
8 Process finished with exit code 0
知识兔 如果实例化之后对n进行更改,但是更改实例里面的n并不会影响类变量,因为在实例化的时候同样拷贝了一份到实例中 。
1 r1 = Role(' Alex ' , ' police ' , ' AK47 ' ) # 生成一个角色 实例化,也叫作初始化
2 r1.n = 456
3 print (r1.n,r1.name)
4 print (Role.n)
5 运行结果如下:
6 D:\python35\python.exe D:/python培训/s14/day6/cs.py
7 123
8 456 Alex
9
10 Process finished with exit code 0
知识兔 实例化找一个变量的时候会先从实例里面找,如果实例化里面没有这个变量,会再从全局变量里找
实例化之后添加属性:
1 r1 = Role(' Alex ' , ' police ' , ' AK47 ' ) # 生成一个角色 实例化,也叫作初始化
2 r1.bullet_prove = True
3 print (r1.name,r1.bullet_prove)
4 运行结果:
5 D:\python35\python.exe D:/python培训/s14/day6/cs.py
6 123
7 Alex True
8
9 Process finished with exit code 0
知识兔 类变量的用途:大家公用的属性
4、 析构函数
在实例释放,销毁的时候执行的,通常用于一些收尾工作,如关闭数据库连接,打开的临时文件
代码例子在类中写入如下代码:
1 def __del__ (self):
2 print (" %s 彻底死了..... " %self.name)
知识兔 并且是做实例换不做任何其他操作,运行结果:
1 D:\python35\python.exe D:/python培训/s14/day6/cs.py
2 Alex 彻底死了.....
3
4 Process finished with exit code 0
知识兔 但是同样执行了
当我实例化之后调用类中的方法的时候:
1 r1 = Role(' Alex ' , ' police ' , ' AK47 ' ) # 生成一个角色 实例化,也叫作初始化
2
3 r1.got_shot()
知识兔 运行结果:
1 D:\python35\python.exe D:/python培训/s14/day6/cs.py
2 Alex ah...,I got shot...
3 Alex 彻底死了.....
4
5 Process finished with exit code 0
知识兔 这个时候在原有的基础上实例化r2
1 r2 = Role(' Jack ' , ' terrorist ' , ' B22 ' )
2 r2.got_shot()
知识兔 运行结果如下:
1 D:\python35\python.exe D:/python培训/s14/day6/cs.py
2 Alex ah...,I got shot...
3 Jack ah...,I got shot...
4 Alex 彻底死了.....
5 Jack 彻底死了.....
6
7 Process finished with exit code 0
知识兔 将上述的代码进行修改:
1 r1 = Role(' Alex ' , ' police ' , ' AK47 ' ) # 生成一个角色 实例化,也叫作初始化
2
3 r1.got_shot()
4 del r1
5
6 r2 = Role(' Jack ' , ' terrorist ' , ' B22 ' )
7 r2.got_shot()
知识兔 这个时候的运行效果如下:
1 D:\python35\python.exe D:/python培训/s14/day6/cs.py
2 Alex ah...,I got shot...
3 Alex 彻底死了.....
4 Jack ah...,I got shot...
5 Jack 彻底死了.....
6
7 Process finished with exit code 0
知识兔 从上述可以总结出:只要实例没有了,就会执行析构函数
5、私有方法,私有属性
在初始化的时候代码如下:
1 self.__life_value = life_value
知识兔 这个时候实例化时候是不能直接访问的如果想要访问需要在类里面添加一个方法:
1 def show_status(self):
2 print (" %s %s %s " %(self.name,self.weapon,self.__life_value ))
知识兔 打印结果如下:
1 D:\python35\python.exe D:/python培训/s14/day6/cs.py
2 Alex ah...,I got shot...
3 Alex AK47 100
4
5 Process finished with exit code 0
知识兔 6、继承
继承是指:它可以使用现有类的所有功能,并无需重新编写原来的类的情况下对这些功能进行扩展
通过继承创建的新类称为“子类”或“派生类”
被继承的类称为“基类”、”父类”或者“超类”
一个简单的继承代码:
1 class People(object): # 新式类
2 def __init__ (self,name,age):
3 self.name = name
4 self.age = age
5 def eat(self):
6 print (" %s is eating。。。。 " %self.name)
7 def sleep(self):
8 print (" %s is sleeping。。。。 " %self.name)
9 def talk(self):
10 print (" %s is talking。。。。 " %self.name)
11
12 class Man(People):
13 pass
14
15 m1 = Man(" dean " ,22)
16 m1.eat()
知识兔 运行效果如下:
1 D:\python35\python.exe D:/python培训/s14/day6/继承1.py
2 dean is eating。。。。
3
4 Process finished with exit code 0
知识兔 同时也可以在自己的里面写方法:
1 class Man(People):
2 def song(self):
3 print (" %s is sing。。。。 " %self.name)
知识兔 运行效果如下:
1 D:\python35\python.exe D:/python培训/s14/day6/继承1.py
2 dean is eating。。。。
3 dean is sing。。。。
4
5 Process finished with exit code 0
知识兔 将代码再次修改
1 class Man(People):
2 def song(self):
3 print (" %s is sing。。。。 " %self.name)
4 def sleep(self):
5 People.sleep(self)
6 print (" Man is sleeping " )
7 m1 = Man(" dean " ,22)
8 m1.eat()
9 m1.song()
10 m1.sleep()
知识兔 运行结果如下:
1 D:\python35\python.exe D:/python培训/s14/day6/继承1.py
2 dean is eating。。。。
3 dean is sing。。。。
4 dean is sleeping。。。。
5 Man is sleeping
6
7 Process finished with exit code 0
知识兔 增加一个新的类:
1 class Woman(People):
2 def get_birth(self):
3 print (" %s is born a baby。。。 " %self.name)
4
5 m1 = Man(" dean " ,22)
6 m1.eat()
7 m1.song()
8 m1.sleep()
9 w1 = Woman(" jack " ,23)
10 w1.get_birth()
知识兔 运行结果如下:
1 D:\python35\python.exe D:/python培训/s14/day6/继承1.py
2 dean is eating。。。。
3 dean is sing。。。。
4 dean is sleeping。。。。
5 Man is sleeping
6 jack is born a baby。。。
7
8 Process finished with exit code 0
知识兔 在Man类下面添加如下代码:
1 class Man(People):
2 def __init__ (self,name,age,money):
3 People.__init__ (self,name,age)
4 self.money = money
5 print (" %s 一出生就有%s钱 " %(self.name,self.money))
6
7 m1 = Man(" dean " ,22,10000)
8 m1.eat()
9 m1.song()
10 m1.sleep()
11
12 w1 = Woman(" jack " ,23)
13 w1.get_birth()
知识兔 或
1 class Man(People):
2 def __init__ (self,name,age,money):
3 super(Man, self).__init__ (name,age)
4 self.money = money
5 print (" %s 一出生就有%s钱 " %(self.name,self.money))
知识兔 运行结果如下:
1 D:\python35\python.exe D:/python培训/s14/day6/继承1.py
2 dean 一出生就有10000钱
3 dean is eating。。。。
4 dean is sing。。。。
5 dean is sleeping。。。。
6 Man is sleeping
7 jack is born a baby。。。
8
9 Process finished with exit code 0
知识兔 关于类的写法:
1 class People: # 经典类
2 class People(object): # 新式类
知识兔 关于多继承
在上述代码的基础上添加一个新类:
1 class Relation(object):
2
3 def make_friends(self,obj):
4 print (" %s is making friends with %s " %(self.name,obj.name))
知识兔 并在Man类和Woman类上都添加上继承:
1 class Man(People,Relation):
2 class Woman(People,Relation):
知识兔 下面是调用:
1 m1 = Man(" dean " ,22,10000)
2 w1 = Woman(" jack " ,23)
3 m1.make_friends(w1)
知识兔 运行结果如下:
1 D:\python35\python.exe D:/python培训/s14/day6/继承1.py
2 dean is making friends with jack
3
4 Process finished with exit code 0
知识兔 用上面的方法就是先了多继承
如果我将继承的顺序换一下:
class Man(Relation,People):
因为查找的时候先查找自己的构造函数,如果自己有了就用自己的,如果没有了才会找父类的
7、 关于继承中的广度优先和深度优先:
1 class A:
2 def __init__ (self):
3 print (" A " )
4
5 class B(A):
6 def __init__ (self):
7 print (" B " )
8
9 class C(A):
10 def __init__ (self):
11 print (" C " )
12
13 class D(B,C):
14 pass
15 obj = D()
知识兔 针对上述代码:(B继承A,C继承A,D同时继承B,C)
运行结果如下:(由于当有多个继承的时候是从左往右继承所以打印了B,也就是继承了B)
1 D:\python35\python.exe D:/python培训/s14/day6/多继承的区别.py
2 B
3
4 Process finished with exit code 0
知识兔 将代码进行更改:
1 class A:
2 def __init__ (self):
3 print (" A " )
4
5 class B(A):
6 pass
7 # def __init__(self):
8 # print("B")
9
10 class C(A):
11 def __init__ (self):
12 print (" C " )
13
14 class D(B,C):
15 pass
16 obj = D()
知识兔 运行结果如下:
1 D:\python35\python.exe D:/python培训/s14/day6/多继承的区别.py
2 C
3
4 Process finished with exit code 0
知识兔 可以看出当B没有的时候,继承了C
将代码再次更改:
1 class A:
2 def __init__ (self):
3 print (" A " )
4
5 class B(A):
6 pass
7 # def __init__(self):
8 # print("B")
9
10 class C(A):
11 pass
12 # def __init__(self):
13 # print("C")
14
15 class D(B,C):
16 pass
17 obj = D()
知识兔 运行结果如下:
1 D:\python35\python.exe D:/python培训/s14/day6/多继承的区别.py
2 A
3
4 Process finished with exit code 0
知识兔 从这里看出最终才继承了A
上述的表现就是广度优先
在python3中只存在广度优先
即如下图所示:
总结:关于继承
在python2 经典类是按深度优先来继承,新式类是按广度优先来继承
在python3 经典类和新式类都是按广度优先来继承
计算机
2022年2月04日