首页 - 新闻 - Python学习笔记-基础【第6周】-面向对象

Python学习笔记-基础【第6周】-面向对象

2023-10-07 19:44
-->

Python 之路,第 6 天 - 面向对象学习

本节内容:
面向对象编程简介
为什么要使用面向对象开发?
面向对象的特性:封装、继承、多态
类、方法、
您也可以参考以下链接:
http://www.gsm-guard.net/wupeiqi/p/4493506.html
面向对象编程(面向对象编程)简介
对于编程语言的初学者来说,OOP并不是一种容易理解的编程方法。虽然按照老师说的大家都知道OOP的三大特性是继承、封装、多态,大家也都知道。如何定义常见的面向对象语法,例如类和方法。不过,到了实际写程序的时候,很多人还是喜欢用函数式编程来写代码,尤其是初学者。很容易陷入一个困境:“我知道了面向对象,我也可以写类,但是我仍然没有发现使用面向对象后对我们的程序开发效率或者其他方面有什么好处,因为我可以减少重复使用函数式编程来编写代码并使程序具有可扩展性。为什么仍然使用面向对象?”。对此,我个人认为原因应该是因为你还没有完全理解面向对象能够带来的好处。今天写一篇关于面向对象的入门文章,希望能帮助大家更好地理解和使用面向对象编程。​
无论我们使用什么形式的编程,我们都要清楚地记住以下原则:
  1. 编写重复代码是一种非常糟糕且低级的行为
  2. 你写的代码需要经常更改
开发常规程序和编写运行一次就扔掉的小脚本的一个很大的区别是你的代码总是需要不断地改变,无论是修改bug还是添加新功能等等,所以为了方便以后程序的修改和扩展,你写的代码一定要遵循易读易变的原则(专业资料上叫可读性好,易扩展)。
如果你把同一段代码复制粘贴到程序中的多个地方来在程序中的各个地方调用这个函数,那么以后修改这个函数的时候,就需要在程序中的多个地方调用它该程序。再改一下。这种写程序的方式是有问题的,因为如果不小心漏掉了一个地方,不改的话,可能会导致整个程序的运行出现问题。因此,我们知道,在开发过程中一定要努力避免编写重复的代码,否则就等于给自己又挖了一个坑。
还好,函数的出现可以帮助我们轻松解决重复代码的问题。对于需要重复调​​用的函数,我们只需要把它写成一个函数,然后在程序中的各个地方直接调用这个函数名即可。 ,而当需要修改这个功能时,只需要改变功能代码,整个程序就会更新。
其实OOP编程的主要作用就是让你的代码修改和扩展变得更加容易。那么新手想问,既然函数可以满足这个要求,为什么还要使用OOP呢?哈哈,这么说,就像古代打仗的时候,人们用刀杀人一样。后来枪就出来了。它们的主要作用和刀一样,都是杀人。然后小白就问,既然刀可以杀人,为什么还要用枪呢?该死的,哈哈,显然,因为枪杀人更好、更快、更容易。函数式编程和OOP的主要区别在于OOP可以使程序更容易扩展和更改。
小白说,我学习不好,你别骗我。只要用你的话来证明就可以了。好吧,我们用下面的例子来向小白证明一下。​
相信大家都玩过CS游戏,所以我们就自己开发一个简单版的CS来玩。
开发地点等复杂的事情暂时先不管,先从人物开始吧。人物非常简单。只有两个人,恐怖分子和警察。除了角色不同之外,它们的作用基本相同。每个人都有健康价值。武器等。我们先用非OOP的方式写一下游戏的基本角色
#角色1
姓名 = '亚历克斯'
角色=“恐怖分子”
武器='AK47'
生命值 = 100 #rolw 2
name2 = '杰克'
角色2 =“警察”
武器2 = 'B22'
生命值2 = 100

上面定义了一个恐怖分子亚历克斯和一个警察杰克,但是只有两个人并不好玩。他们一这么做就会死。这很无聊。那我们把恐怖分子和警察分开吧

#角色1
姓名 = '亚历克斯'
角色=“恐怖分子”
武器='AK47'
生命值 = 100
钱=10000 #rolw 2
name2 = '杰克'
角色2 =“警察”
武器2 = 'B22'
生命值2 = 100
钱2 = 10000 #角色3
name3 = '雨'
角色3 =“恐怖分子”
武器3 = 'C33'
生命值3 = 100
钱3 = 10000 #rolw 4
name4 = '埃里克'
角色4 =“警察”
武器4 = 'B51'
生命值4 = 100
钱4 = 10000
四个角色虽然已经创造出来了,但是有一个问题。每次创建角色都要单独命名,name1、name2、name3、name4……,而且以后调用的时候还是要记住这个变量名。好吧,如果我们多加几个字符的话,可能调用的时候就很容易混淆,那么我们就想一下是不是所有字符的变量名都可以一样,但是我们调用的时候可以区分是谁。 ?​
当然,我们只需要把上面的变量改成字典格式就可以了。
角色={
1:{'名字':'亚历克斯',
'角色':'恐怖分子',
'武器':'AK47',
“生命值”:100,
‘钱’:15000,
},
2:{'名字':'杰克',
'角色':'警察',
'武器':'B22',
“生命值”:100,
‘钱’:15000,
},
3:{'name':'雨',
'角色':'恐怖分子',
'武器':'C33',
“生命值”:100,
‘钱’:15000,
},
4:{'name':'Eirc',
'角色':'警察',
'武器':'B51',
“生命值”:100,
‘钱’:15000,
},
} 打印(角色[1])#Alex
打印(角色[2])#Jack
非常好。以后调用这些角色时,只需要roles[1]和roles[2]即可。角色的基本属性设计完成后,我们将为每个角色开发以下功能。
  1. 被击中后失血的功能
  2. 拍摄功能
  3. 更换子弹
  4. 买把枪
  5. 跑、走、跳、蹲等动作
  6. 保护人质(仅限警察)
  7. 不能杀死同伴
  8. 。 。 。
我们可以将每个函数写成一个函数,类似如下:
Def shot(by_who):
#减少发射后的子弹数量
通过
def got_shot(谁):
#中弹后减少血量
谁[‘生命值’] -= 10
通过
def buy_gun(谁,gun_name):
#检查你是否有足够的钱,买枪后会扣钱
通过
...
到目前为止一切顺利,继续按照这个思路设计,完善代码,简单版的游戏就出来了。不过在往下看之前,我们先看看上面的代码编写方式有没有问题,至少从一开始在上面的代码设计中,我看到了以下缺陷:
  1. 每个角色定义的属性名称都是一样的,但是这个命名规则是我们自己约定的。从程序上看,没有进行属性合法性检测。也就是说,角色1定义的代表武器的属性和武器一样,角色2、3、4也是如此,但是如果我在添加新角色的时候不小心把武器写成了wepon,那么程序本身就无法检测它
  2. 恐怖分子和警察有一些不同的职能。例如,警察不能杀害人质,但恐怖分子可以。随着这款游戏的开发变得更加复杂,我们会发现这两个角色在未来会有更多的差异。然而,以目前的写法,我们无法区分这两个字符适用的功能。也就是说,每个字符都可以直接调用任何函数,没有任何限制。
  3. 上面我们定义了got_shot()之后应该减少血量,也就是说减少血量的动作应该是由被击中的事件引起的。我们调用get_shot()、got_shot(),然后调用各个函数 角色中的life_value变量是用来减血的。但实际上我并没有使用got_shot(),而是直接调用角色roles[role_id]['life_value']来减血。但如果这么称呼的话,可能就简单粗暴了,因为其他人要在血量减少之前判断这个人物。是否穿着防弹衣等,如果穿着的话,伤害值肯定会降低。这种检测是在got_shot()函数中完成的。如果在这里直接绕过的话,程序就乱了。因此,这应该被设计成除了通过got_shot()之外没有办法减少角色的血量。然而,在上面的编程中,并没有办法实现。
  4. 现在需要添加一个让所有角色都穿防弹衣的功能。显然你必须在每个角色中放置一个属性来存储该角色是否穿着防弹衣。然后你需要更改每个字符的代码。添加新属性太low了,不符合代码复用性原则
以上四个问题如果不解决的话,以后肯定会导致更大的坑。有同学表示,解决办法并不复杂。只需在每个函数调用时进行角色判断即可。这是正确的。 ,如果非要这么霸道,那肯定是可以实现的,那么你可以自己开发相应的代码来处理上面提到的问题。但这些问题实际上可以通过 OOP 非常简单地解决。​
如果将之前的代码改成使用OOP中的“类”来实现,则如下:
类角色(对象):
def __init__(自身,名字,角色,武器,生命值=100,金钱=15000):
www.gsm-guard.net = 姓名
self.role = 角色
self.weapon = 武器
www.gsm-guard.net_value = life_value
自己的钱=钱 射门(自己):
print("射击...") def got_shot(自我):
print("啊...,我中枪了...") def buy_gun(self,gun_name):
print("刚买了 %s" %gun_name) r1 = Role('Alex','police','AK47') #生成角色
r2 = Role('Jack','terrorist','B22') #生成角色
不管语法细节如何,相比于函数式的写法,使用面向对象的类来写上面最直接的改进就是以下两点:
  1. 代码量减少了近一半
  2. 角色和作用看得一清二楚
我们来分解一下上面的代码的含义。
class Role(object): #定义一个类,class是定义类的语法,Role是类名,(object)是类的新写法,一定要这样写,后面会讲为什么
def __init__(self,name,role,weapon,life_value=100,money=15000): #初始化函数,生成角色时要初始化的一些属性在这里填写
www.gsm-guard.net = name #__init__中的第一个参数self和这里的self是什么意思?看下面的解释
self.role = 角色
self.weapon = 武器
www.gsm-guard.net_value = life_value
自己的钱=钱
上面的__init__()称为初始化方法(或构造方法)。当类被调用时,这个方法(虽然是函数的形式,但在类中不叫函数,叫方法)会自动执行。 ,执行一些初始化动作,所以我们这里写的__init__(self,name,role,weapon,life_value=100,money=15000)就是在创建角色的时候给它设置这些属性,那么这个第一个参数就是self用来晒的头发?​
要初始化角色,您需要调用该类一次:
r1 = Role('Alex','police','AK47') #生成角色并自动将参数传递给Role下的__init__(...)方法
r2 = Role('Jack','terrorist','B22') #生成角色
我们看到上面创建角色时,我们没有给__init__传值,程序也没有报错。这是因为类在调用自己的 __init__(...) 时帮你给 self 参数赋值了,
r1 = Role('Alex','police','AK47') #此时self就相当于r1, Role(r1,'Alex','police','AK47')
r2 = Role('Jack','terrorist','B22')#此时self就相当于r2, Role(r2,'Jack','terrorist','B22')
为什么会这样?你拉着我说你在犹豫。怎么会发生这种事?
当你执行 r1 = Role('Alex','police','AK47') 时,python 解释器实际上做了两件事:
  1. 在内存中创建一个空间,指向变量名r1
  2. 调用Role类,执行__init__(…)方法,相当于Role.__init__(r1,'Alex','police','AK47'),这样做为什么?就是将‘Alex’、‘警察’、‘AK47’这三个值与新开发的r1联系起来。就是将‘Alex’、‘police’、‘AK47’这三个值与新开发的r1联系起来。这样做的目的是将‘Alex’、‘police’、‘AK47’这三个值与新打开的r1关联起来。重要的事情说三遍,因为关联之后就可以直接www.gsm-guard.net,www.gsm-guard.net了。武器是这样称呼的。因此,为了实现这种关联,在调用__init__方法时,还必须传入变量r1,否则__init__将不知道将这三个参数与谁关联。
  3. 你明白吗?所以在这个__init__(...)方法中,www.gsm-guard.net = name、self.role = role等意思是把这些值存储到r1的内存空间中。
如果你还不明白,兄弟,就去测测你的智商吧。应该不会超过70吧,哈哈。
为了暴露你的智商,你这时候假装听懂了,然后又问,__init__(...)我听懂了,但是下面的函数,哦不,为什么下面的方法也需要self参数呢?什么?你初始化角色的时候不是已经把角色的属性绑定到r1了吗?​
问得好,我们先看一下上面类中的buy_gun方法:
def buy_gun(self,gun_name):
print(“%s 刚刚购买了 %s” %(www.gsm-guard.net,gun_name) )

如果通过类调用上述方法,则应该这样写:

r1 = 角色('亚历克斯','警察','AK47')
www.gsm-guard.net_gun("B21") #python会自动帮你转换为www.gsm-guard.net_gun(r1,"B21")
执行结果
#Alex刚刚买了B21
你还没有给self传递值,但是Python仍然会自动为你将r1分配给参数self。为什么?因为您可能需要在 buy_gun(..) 方法中访问 r1 的一些其他属性。例如,这里访问r1的名称。如何访问它?你必须告诉这个方法,所以你将r1传递给self参数,然后在buy_gun中调用www.gsm-guard.net相当于调用www.gsm-guard.net。如果还想知道r1的生命值,就直接写成self。 life_value 就可以了。说白了,调用类中的方法时,你要告诉别人你是谁。
好啦,总结2点:
  1. 上面的 r1 = Role('Alex','police','AK47') 这个动作称为类的“实例化”,就是通过这个动作转换一个虚拟的抽象类,成为一个具体的对象,这个对象称为实例
  2. 刚才定义的类体现了面向对象的第一个基本特征,封装。 其实就是利用构造方法将内容封装成一个具体的对象,然后通过该对象直接或者间接获取封装的内容。内容