面向对象
1.简介
1.1 面向过程
1. “面向过程”(Procedure Oriented)是一种以过程为中心的编程思想。分析出解决问题所需要的步
骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
面向对象的方法也是含有面向过程的思想。面向过程最重要的是模块化的思想方
法。比如拿学生早上起来这件事说明面向过程,粗略的可以将过程拟为:
(1)起床
(2)穿衣
(3)洗脸刷牙
(4)去学校
而这4步就是一步一步地完成,它的顺序很重要,你只需要一个一个地实现就行了。
而如果是用面向对象的方法的话,可能就只抽象出一个学生的类,它包括这四个
方法,但是具体的顺序就不一定按照原来的顺序。
2.特性:
模块化 流程化
优点:
性能比面向对象高, 因为类调用时需要实例化,开销比较大,比较消耗资源;
单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
缺点:
没有面向对象易维护、易复用、易扩展
1.2 函数式编程
- 函数式编程是种编程方式,它将电脑运算视为函数的计算。函数编程语言最重要的基础是λ
演算(lambda calculus),而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值)。
主要思想: 把运算过程尽量写成一系列嵌套的函数调用。
1.3 面向对象编程
1. 面向对象是按人们认识客观世界的系统思维方式,把构成问题事务分解成各个对象,建立对
象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
2. 特性: 抽象 封装 继承 多态
优点: 易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,
可以设计出低耦合 的系统,使系统更加灵活、更加易于维护
缺点: 性能比面向过程低
1.4 面向过程和面向对象的处理过程
- 1.面向过程实现五子棋游戏
五子棋,面向过程的设计思路就是首先分析问题的步骤:
1、开始游戏
2、黑子先走
3、绘制画面
4、判断输赢
5、轮到白子
6、绘制画面
7、判断输赢
8、返回步骤2
9、输出最后结果。
把上面每个步骤用不同的方法来实现。
- 2.面向对象实现五子棋
- 五子棋,面向对象的设计思路就是首先分析问题的步骤:
1、黑白双方,这两方的行为是一模一样的。
2、棋盘系统,负责绘制画面。
3、规则系统,负责判定诸如犯规、输赢等。
第一类对象(玩家对象)负责接受用户输入,并告知第二类对象(棋盘对象)棋子布局的
变化,棋盘对象接收到了棋子的变化就要负责在屏幕上面显示出这种变化,同时利用第三
类对象(规则系统)来对棋局进行判定。
2. 对象和类
类(Class)是现实或思维世界中的实体在计算机中的反应,它将数据以及这些数据上的操作封装在一起。类(Class)是是创建实例的模板
对象(Object)是具有类类型的变量。类和对象是面向对象编程中最基本的概念。对象(Object)是一个一个具体的实例
类和对象的区别: 鱼和三文鱼的区别;(鱼是宽泛概念,三文鱼是具体种类)
1. 如何定义类? class 类(): pass
2. 如何将类转换成对象?
实例化是指在面向对象的编程中,把用类创建对象的过程称为实例化。是将一个抽象的概
念类,具体到该类实物的过程。实例化过程中一般由类名 对象名 = 类名(参数1,参数2...参数n)
构成。
#类class
class Cat:
#属性:一般是名词
name='name'
kind='kind'
#方法:一般情况是动词 ,eg:create,delete,eating,run...
def eat(self):
print('cat like eating fish.....')
#对象(Object):对类的实例化(具体化)
fentiao = Cat()
print(Cat) #<class '__main__.Cat'> ##当前环境下的类
print(fentiao) #<__main__.Cat object at 0x01BDEF10> ##当前环境下的类下的对象,存在内存环境中
3. 封装特性
3.1 介绍及操作
面向对象的三大特性是指:封装、继承和多态
1.封装,顾名思义就是将内容封装到某个地方,以后再去调用被封装在某处的内容。
所以,在使用面向对象的封装特性时,需要:
1). 将内容封装到某处
2). 从某处调用被封装的内容
1). 通过对象直接调用被封装的内容: 对象.属性名
2). 通过self间接调用被封装的内容: self.属性名
3). 通过self间接调用被封装的内容: self.方法名()
2.构造方法__init__与其他普通方法不同的地方在于,当一个对象被创建后,会立即调用构造方法。自动执行构造方法里面的内容
3.对于面向对象的封装来说,其实就是使用构造方法将内容封装到 对象 中,然后通过对象直接或者self间接获取被封装的内容。
#类class
class Cat:
def __init__(self,name,kind): # 形参
"""
1.构造方法,实例化对象时自动执行的方法
2.self是什么?self实质上是实例化的对象
3.类方法中python解释器会自动的把对象作为参数传给self
"""
print('正在执行init构造方法')
print('self:',self) #self: <__main__.Cat object at 0x0125EF10>
#属性:一般是名词
self.name=name
self.kind=kind
#方法:一般情况是动词 ,eg:create,delete,eating,run...
def eat(self):
print('cat %s like eating fish.....'%(self.name))
#对象(Object):对类的实例化(具体化)
fentiao = Cat("炖条","美短")
print(fentiao.name) #炖条
print(fentiao.kind) #美短
fentiao.eat() #cat 炖条 like eating fish.....
3.2 巩固封装特性
"""
创建一个类People,拥有的属性为姓名, 性别和年龄, 拥有的方法为购物,玩游戏,学习;实例化
对象,执行相应的方法。 显示如下:
小明,18岁,男,去西安赛格购物广场购物
小王,22岁,男,去西安赛格购物广场购物
小红,10岁,女,在西部开源学习
提示:
属性:name,age,gender
方法:shopping(), playGame(), learning()
"""
class People:
def __init__(self,name,age,gender):
self.name = name
self.age = age
self.gender = gender
def shopping(self):
print(f'{self.name},{self.age}岁,{self.gender},去西安赛格购物广场购物')
def playing(self):
print(f'{self.name},{self.age}岁,{self.gender},在家玩游戏')
def study(self):
print(f'{self.name},{self.age}岁,{self.gender},在西部开源学习')
xiaoming = People('小明',18,'男').shopping()
xiaoyu = People('小玉',22,'男').shopping()
xiaohong = People('小红',18,'女').study()
4. 继承特性
4.1 介绍
- 面向对象的三大特性是指:封装、继承和多态
1. 继承
2. 多继承
3. 私有属性与私有方法
- 继承描述的是事物之间的所属关系,当我们定义一个class的时候,可以从某个现有的class
继承,新的class称为子类、扩展类(Subclass),而被继承的class称为基类、父类或超类(Baseclass、
Superclass)。(父类有的继承,子类可以继承父类,也可以重新创造新类)
- 问题一: 如何让实现继承?
子类在继承的时候,在定义类时,小括号()中为父类的名字
问题二: 继承的工作机制是什么?
父类的属性、方法,会被继承给子类。 举例如下: 如果子类没有定义__init__方法,父类有,那
么在子类继承父类的时候这个方法就被继承了,所以只要创建对象,就默认执行了那个继承过来的
__init__方法
4.2 继承示例
class Student:
"""父类Student"""
def __init__(self,name,age): ##构造方法
self.name = name
self.age = age
def learning(self):
print(f'{self.name}正在学习')
class MathStudent(Student):
"""MathStudent的父类是Student"""
pass
##实例化
m1 = MathStudent('fentiao',8)
print(m1.name)
print(m1.age)
m1.learning ##子类没有,父类有,所以继承父类方法
4.3 重写父类继承示例
- 重写父类方法: 就是子类中,有一个和父类相同名字的方法,在子类中的方法会覆盖掉父类中同名的方法。
- 调用父类的方法:
1. 父类名.父类的方法名() ##b不推荐
2. super(): py2.2+的功能 ##推荐
class Student:
"""父类Student"""
def __init__(self,name,age): ##构造方法
self.name = name
self.age = age
def learning(self):
print(f'{self.name}正在学习')
def choice_source(self):
print('正在选课中'.center(50,'*'))
class MathStudent(Student):
"""MathStudent的父类是Student"""
def choice_source(self):
##需求:先执行父类方法choice_source,在执行子类choice_source
##Student.choice_source(self) ##解决方法1:直接执行父类方法,不推荐
##解决方法2:通过super找到父类,在执行方法(建议且生产环境代码常用方法)
super(MathStudent,self).choice_source()
info = """
课程表
1.高等数学
2.线性代数
"""
print(info)
##实例化
m1 = MathStudent('fentiao',8)
print(m1.name)
print(m1.age)
m1.learning ##子类没有,父类有,所以继承父类方法
m1.choice_source() ##重写父类执行
4.4 力扣题
"""
参考链接:https://cnblogs.com/klyjb/p/11237361.html
数组需要连续的内存空间
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
不需要连续的内存空间
数组 链表(增加和删除只需要删除next指针.查找和修改需要一个一个遍历)
增加元素 O(n) O(1)
删除元素 O(n) O(1)
查找元素 O(1) O(n)
修改元素 O(1) O(n)
"""
# 封装节点类
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def travel(self, head):
"""遍历链表里面的每一个元素"""
while head:
print(head.val, end=',')
head = head.next
def create_l1():
# l1 = 2,4,3
# l2 = 5, 6, 4
l1 = ListNode()
node1 = ListNode(val=2)
node2 = ListNode(val=4)
node3 = ListNode(val=3)
l1.next = node1
node1.next = node2
node2.next = node3
return l1.next
def create_l2():
# l1 = 2,4,3
# l2 = 5, 6, 4
l2 = ListNode()
node1 = ListNode(val=5)
node2 = ListNode(val=6)
node3 = ListNode(val=4)
l2.next = node1
node1.next = node2
node2.next = node3
return l2.next
##主代码
def addTwoNumbers(l1: ListNode, l2: ListNode) -> ListNode:
res = 0
l3 = ListNode()
cur = l3
while(l1 or l2):
if(l1):
res += l1.val # res=2
l1 = l1.next
if(l2):
res += l2.val # res=2+5=7
l2 = l2.next
# res=10, val=0, res=>val val=res%10
# res=14, val=4, 14%10=4
l3.next = ListNode(res%10)
l3 = l3.next
# res=10, 进位为1, 10//10=1
# res=14, 进位为1, 14//10=1
res //= 10
if res == 1:
l3.next = ListNode(1)
return cur.next
# 中午留个作业:查一下它的功能
if __name__ == '__main__':
l1 = create_l1()
l2 = create_l2()
l3 = addTwoNumbers(l1, l2)
l3.travel(l3)
4.5 多继承
4.5.1 新式类和经典类
1. 在Python 2及以前的版本中,由任意内置类型派生出的类,都属于“新式类”,都会获得所有“新式类”的特性;反之,即不由任意内置类型派生出的类,则称之为“经典类”。
新式类 class 类名(object):
pass
经典类
class 类名:
pass
2.“新式类”和“经典类”的区分在Python 3之后就已经不存在,在Python 3.x
之后的版本,因为所有的类都派生自内置类型object(即使没有显示的继承
object类型),即所有的类都是“新式类”。
"""
新式类: 广度优先算法
经典类: 深度优先算法(py2中的部分类属于经典类)
python3所有的类都属于新式类。新式类的继承算法是广度优先。
# 分析多继承的相关代码
>pip install djangorestframework
from rest_framework import viewsets
viewsets.ModelViewSet
"""
class D(object):
def hello(self):
print('D')
class C(D):
# def hello(self):
# print('C')
pass
class B(D):
pass
# def hello(self):
# print('B')
class A(B, C):
pass
# def hello(self):
# print('A')
a = A()
a.hello()
4.6 私有属性和私有方法
- 默认情况下,属性在 Python 中都是“public”, 大多数 OO 语言提供“访问控制符”来限定成员函数的访问。
在 Python 中,实例的变量名如果以 __ 开头,就变成了一个私有变量/属性
(private),实例的函数名如果以 __ 开头,就变成了一个私有函数/方法(private)只
有内部可以访问,外部不能访问
class Student:
"""父类Student"""
def __init__(self, name, age, score):
self.name = name
self.age = age
# 私有属性,以双下划线开头。
# 工作机制: 类的外部(包括子类)不能访问和操作,类的内部可以访问和操作。
self.__score = score
def learning(self):
print(f'{self.name}正在学习')
def get_score(self): ##类内部调用modify私有方法
self.__modify_score()
return self.__score
# 私有方法是以双下划线开头的方法,
#工作机制: 类的外部(包括子类)不能访问和操作,类的内部可以访问和操作。
def __modify_score(self):
self.__score += 20
class MathStudent(Student):
"""MathStudent的父类是Student"""
def get_score(self):
self.__modify_score()
return self.__score
# 报错原因: 子类无法继承父类的私有属性和私有方法。
s1 =Student('张三', 18, 100)
score = s1.get_score() ##运行结果为120
#s1 = MathStudent('张三', 18, 100) #父类私有方法依旧不可以调用,会报错
#score = s1.get_score()
print(score)
5.多态特性
- 多态(Polymorphism)按字面的意思就是“多种状态”。在面向对象语言中,接口的多种不同的实现方式即为多态。通俗来说: 同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。
- 多态的好处就是,当我们需要传入更多的子类,只需要继承父类就可以了,而方法既可以直接
不重写(即使用父类的),也可以重写一个特有的。这就是多态的意思。调用方只管调用,不管
细节,而当我们新增一种的子类时,只要确保新方法编写正确,而不用管原来的代码。这就是著
名的“开闭”原则:
1.对扩展开放(Open for extension):允许子类重写方法函数
2.对修改封闭(Closed for modification):不重写,直接继承父类方法函数
6.项目案例:栈的封装
- 栈是限制在一端进行插入操作和删除操作的线性表(俗称堆栈),允许进行操作的一端称为“栈顶”
,另一固定端称为“栈底”,当栈中没有元素时称为“空栈”。向一个栈内插入元素称为是进栈,push;
从一个栈删除元素称为是出栈,pop。特点 :后进先出(LIFO)
class Stack(object):
"""栈的封装[1, 2, 3, 4]"""
def __init__(self):
self.stack = []
def push(self, value):
"""入栈"""
self.stack.append(value)
print(f"入栈元素为{value}")
def pop(self):
"""出栈"""
if self.is_empty():
raise Exception("栈为空")
item = self.stack.pop()
print(f"出栈元素为{item}")
return item
def is_empty(self):
"""判断栈是否为空"""
return len(self.stack) == 0
def top(self):
"""返回栈顶元素"""
if self.is_empty():
raise Exception("栈为空")
return self.stack[-1]
def __len__(self):
"""魔术方法, len(object)自动执行的方法"""
return len(self.stack)
if __name__ == '__main__':
stack = Stack()
stack.push(1)
stack.push(2)
stack.push(3)
print(len(stack)) # 3
stack.pop()
print(stack.is_empty()) # False
print(stack.top()) # 2
7. 项目案例:队列的封装
- 队列是限制在一端进行插入操作和另一端删除操作的线性表,允许进行插入操作的一端称为“队尾”
,允许进行删除操作的一端称为“队头”,当队列中没有元素时称为“空队”。特点 :先进先出(FIFO)。
class Queue(object):
"""
队列的封装
1.列表左侧作为队尾
2.列表右侧作为队头
"""
def __init__(self):
self.queue = []
def enqueue(self, value):
"""入队"""
self.queue.insert(0,value)
print(f"入队元素为{value}")
def dequeue(self):
"""出队"""
if self.is_empty():
raise Exception("队列为空")
item = self.queue.pop()
print(f"出队元素为{item}")
return item
def is_empty(self):
"""判断栈是否为空"""
return len(self.queue) == 0
def first(self):
"""返回对头元素"""
if self.is_empty():
raise Exception("队为空")
return self.queue[-1]
def last(self):
"""返回对尾元素"""
if self.is_empty():
raise Exception("队为空")
return self.queue[0]
def __len__(self):
"""魔术方法, len(object)自动执行的方法,获取队列的长度"""
return len(self.queue)
if __name__ == '__main__':
stack = Queue()
stack.enqueue(1)
stack.enqueue(2)
stack.enqueue(3)
print(stack.is_empty()) # False
print(len(stack))
stack.dequeue() #1出队,剩32
print(stack.first()) #2
print(stack.last()) # 3
8. 二叉树的封装
"""
二叉树:
https://www.cnblogs.com/polly333/p/4740355.html
"""
class Node(object):
"""节点类"""
def __init__(self, val=None, left=None, right=None):
self.val = val
self.left = left
self.right = right
class BinaryTree(object):
"""封装二叉树"""
def __init__(self, root):
self.root = root
def pre_travel(self, root):
"""先序遍历: 根左右"""
if (root != None):
print(root.val)
self.pre_travel(root.left)
self.pre_travel(root.right)
def in_travel(self, root):
"""中序遍历: 左根右"""
if (root != None):
self.in_travel(root.left)
print(root.val)
self.in_travel(root.right)
def last_travel(self, root):
"""后序遍历: 左右根"""
if (root != None):
self.last_travel(root.left)
self.last_travel(root.right)
print(root.val)
if __name__ == '__main__':
node1 = Node(1)
node2 = Node(2)
node3 = Node(3)
node4 = Node(4)
node5 = Node(5)
node6 = Node(6)
node7 = Node(7)
node8 = Node(8)
node9 = Node(9)
node10 = Node(10)
bt = BinaryTree(root=node1)
node1.left = node2
node1.right = node3
node2.left = node4
node2.right= node5
node3.left = node6
node3.right = node7
node4.left = node8
node4.right = node9
node5.left = node10
# 先序遍历
bt.pre_travel(node1)