面向对象
一.对面向对象和面向过程的认知
面向对象是一种抽象化的编程思想,是当前软件开发领域的主流技术。如Python,适用于大型项目
而面向过程适用于中小型项目,例如C语言
面向对象和面向过程的不同:
1.面向过程是将问题的解决步骤全部分析出来,然后通过函数一步一步实现,依次调用。而面向对象是将一个问题分为若干个对象,建立该对象的目的并不是为了完成某一个步骤,而是为了描述在解决某个问题过程中的行为。
2.面向过程思想偏向于我们做一件事的流程,首先做什么,其次做什么,最后做什么。面向对象思想偏向于了解一个人,他的特长,性格,有没有遗传到什么能力。
3.面向过程流程化的编程使编程任务十分明确,在实行之前已经基本考虑了实现方式和结果,便于节点分析。效率高,代码短小精悍,善于结合数据结构来高效率的开发程序。但是需要编程者深度的思考,耗费精力。代码的重用性低,扩展能力差,后期维护的难度大。面向对象易维护、易拓展、易复用、,有封装、继承、多态的特性、更容易设计出耦合的系统。但性能比面向过程低。
点击此处查看原图
二.类和对象
开发中现有类再有对象
1.类(大驼峰命名)
类就是对一系列具有相同属性和行为的事物的统称
属性:就是特征,用来描述是什么样子(静态概念,如身高、体重)
行为:所具有的功能,用来说明能做什么(动态概念,如跑步、考试)
类是一个抽象的概念,并不是真实存在的
2.对象:
是类的具体表现,是类创建出来的真实存在的事物,也是面向对象的核心(Python万物皆为对象)
3.定义类
三种定义方式
<1>
class A:
pass
a=A()
<2>目前使用这种方法
class A():
pass #占位符,表示空语句,运行时直接跳过
a=A()
<3>推荐使用,在了解继承后便可以理解了
object是顶级父类,如果一个类没有使用继承任何类,默认也会继承object类
class A(object):
pass
a=A()
4.实例化对象
Python可以根据已经定义的类创建一个或多个对象
class FriendName():
"""朋友名字类"""
pass
hong=FriendName() #第一次实例化对象
print("hong:",hong)
ming=FriendName() #第二次实例化对象
print("ming:",ming)
运行结果:(运行结果是对象的内存地址,因为没有任何的方法调用)
hong: <main.FriendName object at 0x000001B506B33DC8>
ming: <main.FriendName object at 0x000001B506B73788>
内存地址不一样,说明是不同的对象
5.在类中定义方法
<1>实例方法(方法即是函数)
由对象来调用,至少有一个self函数,执行实例方法的时候,自动将调用该方法的对象赋给self
class Person(): #定义类Person
def eat(self): #定义方法eat,实例方法必须有参数self,用来代表对象本身
print("爱干饭")
print("self:",self) #哪一个对象调用实例方法,那么实例方法中的self就是对象本身的内存地址
su=Person() #实例化对象
print("su:",su)
su.eat() #由对象去调用实例方法
<2>静态方法
静态方法用@stadicmethod来修饰,既不需要传递类对象也不需要传递实例对象
可以取消不需要的参数传递,有利于减少不必要的内存占用和性能消耗
当方法中既不需要用到实例对象、实例属性,又不需要用到类对象时定义静态方法
格式: class 类名(object):
@staticmethod
def 方法名(形参):
方法体
class person():
@staticmethod
def eat(name):
print(f"{name}需要干饭")
person.eat("susu") #susu需要干饭
<3>类方法
使用@classmethod作为标识的方法。对于类方法,第一个参数必须是类对象,一般以cls为第一个参数
类方法一般和类属性配合使用,当方法中需要使用类(如访问类属性、调用其它类方法)时可以定义类方法
格式: class 类名(object):
@classmethod
def 方法名(cls,形参)
方法体
class person():
@classmethod
def eat(cls): #cls代表类对象
print(cls)
person.eat()
print(person)
6.在类中定义属性
<1>类属性
class Person():
favourite = "干饭" #类属性
def eat(self):
print("self:",self)
su=Person()
print(su.favourite) #干饭
print(Person.favourite) #干饭
类属性不仅可以通过对象名访问,也可以通过类名访问
<2>实例属性
定义在类的方法中的属性,只能用于当前对象
class Person():
favourite = "干饭"
def eat(self):
print(f"su的年龄是{self.age}") #实例属性
su=Person()
print(su.favourite) #干饭
su.age=18
su.eat() #su的年龄是18
mao=Person()
mao.age=19
mao.eat() #su的年龄是19
lin=Person()
lin.age=20
lin.eat() #su的年龄是20
每实例化一次,都需要给实例化属性赋值,很麻烦。因此就有了构造函数
7.构造函数
构造函数是一种特殊的 实例方法,实例化对象时,会自动执行该函数
通常用来属性的初始化或赋值
class Person():
def __init__(self,name,age): #设置形参
self.name=name
self.age=age
def say(self):
print(f"我的名字是{self.name},年龄是{self.age}")
p1=Person("susu",18) #带入实参
p1.say() #我的名字是susu,年龄是18
p2=Person("linlin",19)
p2.say() #我的名字是linlin,年龄是19
8.析构函数
在程序执行到最后时或对象被删除时自动执行析构函数
用途:
1.用于程序执行后
class Person():
def __init__(self): #构造函数
print("我是程序执行开始时时执行的")
def __del__(self): #析构函数
print("我是程序结束时执行的")
p1=Person()
print("123")
p2=Person()
print("34")
我们可以看出并不是在一个对象的方法结束后执行del,而是全部代码执行后之后执行的
2.对象被删除后
class Person():
def __del__(self):
print("程序被删除")
p1 = Person()
del p1
print("qqq")
p2 = Person()
del p2
print("qqq")
我们可以看出是在对象被删除后执行的,而不是程序结束时
使用场景:对象即将被销毁的时候需要做的一些工作,比如关闭文件、关闭数据库、关闭各种方法
10.__str__方法
用于对象的描述信息,如果在类中定义了该方法,在打印对象的时候,默认输出该方法的返回值
str方法必须有返回值,而且返回值必须是字符串类型的数据
class Person():
def __str__(self):
return "打印成功"
p1=Person()
print(p1) #打印成功
p2=Person()
print(p2) #打印成功
三.三大特性:封装、继承、多态
1.封装性
<1>基本认知
根据职责将属性和方法都放在抽象的类中,然后通过实例化对象来处理。类本质上就是一种封装
面向对象编程:
1.定义类,将属性和方法封装到抽象的类中
2.外界使用类创建对象,然后由独对象调用类中方法(使用缩进来控制界外界内)
3.对象方法的细节都被封装在类的内部
class Person(): #1.定义类
def __init__(self,name,age):
self.name=name
self.age=age
def say_hello(self):
print(f"hello,my name is {self.name},age is {self.age}")
p1=Person("susu",21) #2.外界使用类创建对象
p1.say_hello()
print(p1.age) #通过对象访问实例属性
<2>私有属性
注意在python中并没有专门的修饰符用于定义属性/方法的私有。
如果不希望被外部访问,在属性名前使用==__==
class Person(): #1.定义类
def __init__(self,name,age):
self.name=name
self.__age=age #不希望在类的外部被使用,加入了双下划线,只允许在类的内部使用
def say_hello(self):
print(f"hello,my name is {self.name},age is {self.age}")
p1=Person("susu",21) #2.外界使用类创建对象
print(p1.name) #susu
print(p1.age) #报错
方法一:dir
但是这种方法是不严谨的,仍然有其他方法可以读取出age
class Person():
def __init__(self,name,age):
self.name=name
self.__age=age #加下划线
def say_hello(self):
print(f"hello,my name is {self.name},age is {self.age}")
p1=Person("susu",21)
print(dir(p1))
print(p1._Person__age)
p1._Person__age=17
print(p1._Person__age)
我们可以看出,实际上是对名称做了特殊处理。使用双下划线开头的属性会让python自动为属性改名字:_类名__属性名 使得外界无法访问。当找出其真正的名称时仍然可以进行修改、查看操作(但是不推荐这种方法,不正规,像小偷小摸似的)
方法2:在类中定义方法使用
class Person():
def __init__(self,name,age):
self.name=name
self.__age=age #加下划线
def say_hello(self):
print(f"hello,my name is {self.name},age is {self.age}")
def tell_age(self):
print(self.__age)
p1=Person("susu",21)
print(p1.age) #报错
p1.tell_age() #21
2.继承性
指多个类之间的所属关系,即让类和类之间转换为父子关系,子类默认继承父类的所有属性和关系
实现代码的重用,相同的代码不需要重复编写,以提高开发的效率
父类可以写多个,父类之间用 , 隔开。也可以不写父类,默认继承object(顶级父类)
#语法:class 类名(父类名):
# 类体
<1>单继承
单继承:子类只有一个父亲,具有父亲的属性和方法
class Person():
"""父类 人"""
def sleep(self):
print("人类需要睡觉")
def eat(self):
print("人类需要吃饭")
class Man(Person):
"""子类 男人"""
class Woman(Person):
"""子类 女人"""
M1=Man()
M1.sleep() #人类需要睡觉
M1.eat() #人类需要吃饭
W1=Woman()
W1.sleep() #人类需要睡觉
W1.eat() #人类需要吃饭
在Man类中他没有任何的方法,但是调用方法却成功了。这就是因为他继承了他父类的所有属性和方法.而且一个父类是可以继承多个子类的。
<2>继承的传递
A1、A2、A3类,A3继承A2,A2继承A1 A3具有A2和A1的属性和方法
即子类拥有父类和父类的父类……的属性和方法
class Grandfather():
"""父类 爷爷"""
def Retirement_pay(self):
print("有退休金")
class Father(Grandfather):
def salary(self):
print("有工资")
class younth(Father):
pass
y1=younth()
y1.salary() #有工资
y1.Retirement_pay() #有退休金
<3>方法的重写
指在子类中有定义与父类同名的方法
方法一;覆盖父类方法
在子类中定义一个和父类同名的方法,调用会使子类重写
class Grandfather():
"""父类 爷爷"""
def Retirement_pay(self):
print("有退休金")
class Father(Grandfather):
"""子类 父亲"""
def Retirement_pay(self):
print("需要交五险一金")
y1=Father()
y1.Retirement_pay() #需要交五险一金
方法二:对父类方法进行拓展
继承父类的方法,子类还可以增加自己的功能.共有三种实现方式(但是第三种方式在python2中的方法现在已经过时了,便不陈述了)
【1】父类名.方法名(self)
class Grandfather():
"""父类 爷爷"""
def Retirement_pay(self):
print("有退休金")
class Father(Grandfather):
def Retirement_pay(self):
Grandfather.Retirement_pay(self) #!!!!!
print("需要交五险一金")
y1=Father()
y1.Retirement_pay()
但是使用这种方式,如果想要改变父类名的话,下面子类中的调用就必须都得进行改变,不利于代码的可维护性
【2】super().方法名(self)
class Grandfather():
"""父类 爷爷"""
def Retirement_pay(self):
print("有退休金")
class Father(Grandfather):
def Retirement_pay(self):
super().Retirement_pay(self) #!!!!!!
print("需要交五险一金")
y1=Father()
y1.Retirement_pay()
super代表上级父类对象,可以使用父类中的方法
<4>例题
需求:创建Person类,属性有姓名name、年龄age、性别sex、方法有printInfo方法,用于打印这个人的信息
创建Student类,继承Person类,属性有学院college,班级class,重写父类printInfo方法,调用父类方法打印个人信息外,将学生的学院、班级信息也打印出来
class Person():
def __init__(self,name,age,sex):
self.name=name
self.age=age
self.sex=sex
def printInfo(self):
print(f"此人的姓名为{self.name},年龄为{self.age},性别为{self.sex}")
class Student(Person):
def __init__(self,name,age,sex,college,class1):
super().__init__(name,age,sex)
self.college=college
self.class1=class1
def printInfo(self):
super().printInfo()
print(f"学院为{self.college}班级为{self.class1}")
s1=Student("susu","21","men","河工大","204")
s1.printInfo()
<2>多继承
多继承:子类可以有多个父亲,并且具有所有父亲的属性和方法
class Father(object):
def weight(self):
print(f"爸爸体重160")
class Mother(object):
def appearance(self):
print(f"妈妈颜值90分")
class Son(Father,Mother): #son继承了两个父类的所有方法
pass
son1=Son()
son1.weight() #爸爸体重160
son1.appearance() #妈妈颜值90分
如果有一个子类继承多个父类方法,但是父类方法有重名时,使用就近原则,子类先继承谁,就是用谁的属性(如果子类有着自己的同名属性时,优先使用自己的)
class Father(object):
def weight(self):
print(f"爸爸体重160")
class Mother(object):
def weight(self):
print(f"妈妈体重90")
class Son(Mother,Father): #同名方法,采用就近原则,先继承的Mother
pass
son1=Son()
son1.weight() #妈妈体重90
在搞不清楚的情况下可以使用mro来查看方法搜索顺序
多继承在有同名方法时,调用方法的时候,会按照mro属性的值,按照从左到右的顺序进行查找
class Father(object):
def weight(self):
print(f"爸爸体重160")
class Mother(object):
def weight(self):
print(f"妈妈体重90")
class Son(Mother,Father):
def weight(self):
print(f"自己体重128")
son1=Son()
print(Son.__mro__) #注意这里是子类的方法查找顺序号
son1.weight()
3.多态
同一行为具有多个不同的表现形式。不同的对象调用同一个方法会产生不同的执行结果,能够增加代码的灵活度
多态的两大前提:继承,重写
class Animal():
def shout(self,obj): #obj为形参,在此例中obj=cat
print("动物会叫")
obj.shout() #cat.shout()
class Cat(Animal):
def shout(self):
print("猫咪喵喵")
class Dog(Animal):
def shout(self):
print("修狗汪汪")
a=Animal()
cat=Cat()
a.shout(cat)
通过定义函数设置形参来控制调用不同的对象的同名方法,来实现多态性
例题
利用多态思想,编写程序模拟主人喂养宠物的场景
class master():
def feed(self,obj): #obj为形参,在此例中obj=cat
obj.feed() #cat.shout()
class Cat(master):
def feed(self):
print("主人喂猫咪鱼")
class Dog(master):
def feed(self):
print("主人喂修狗骨头")
a=master()
cat=Cat()
dog=Dog()
a.feed(cat) #主人喂猫咪鱼
a.feed(dog) #主人喂修狗骨头
四.各种小例题
1.定义一个Point类为父类,在父类中分别有x,y,z,h四个属性,用来传入未知整数,再定义一个Line类为子类,继承Point类,并在Line类中定义getPoint方法,getPoint方法用于计算x-y的绝对值和z-h的绝对值相乘的结果,最后通过子类创建对象传入实参检测是否继承成功。
计算公式:(x-y)*(z-h)
class Point(object):
def __init__(self,x,y,z,h):
self.x=x
self.y = y
self.z = z
self.h = h
class Line(Point):
def getPoint(self):
result=abs(self.x-self.y)*abs(self.z-self.h)
print(result)
l=Line(2,3,4,5)
l.getPoint()
2.定义一个Car类,并在类中定义一个move方法,然后分别创建BMW_X9、AUDI_A9对象,并在类里面添加颜色、马力、型号等属性,然后通过调用move方法方法分别打印出属性值。
提示: 使用__ init__方法完成属性赋值。
class Car():
def __init__(self,color,horsepower,model):
self.color=color
self.horsepower=horsepower
self.model=model
def move(self):
print(f"该车颜色为{self.color},马力为{self.horsepower},型号为{self.model}")
BMW_X9=Car("blue",150,"x9")
AUDI_A9=Car("red",160,"a9")
BMW_X9.move()
AUDI_A9.move()