面向对象基础
1. 面向对象与面向过程
在我们编写程序的时候,经常性的会听说过“面向对象”这个词语。我们可能听说过Python是“面向对象”的编程语言,C是“面向过程”的编程语言,那么什么是“面向对象”,什么是“面向过程”呢?
- 面向过程
- 是一种看待问题、解决问题的思维方式。
- 着眼点在于问题是如何一步步的解决的,然后亲力亲为的解决问题。
- 面向对象
- 是一种看待问题、解决问题的思维方式。
- 着眼点在于找到一个能够帮助解决问题的实体,然后委托这个实体帮助解决问题。
案例分析1: 小明需要自己组装一台电脑
- 面向过程的思维方式:
- (小明)去市场买零配件
- (小明)将零配件运回家里
- (小明)将电脑组装起来
- 面向对象的思维方式:
- 小明找到一个朋友 – 老王
- (老王)去市场买零配件
- (老王)将零配件送回来
- (老王)将电脑组装起来
案例分析2: 小明需要把大象装进冰箱
- 面向过程的思维方式:
- (小明)打开冰箱门
- (小明)把大象赶进冰箱
- (小明)关上冰箱门
- 面向对象的思维方式:
- (冰箱)开门
- (大象)自己走进冰箱去
- (冰箱)关门
因此,无论是面向对象还是面向过程,其实都是一种编程思想,而并不是某一种语言。在很多的新手手中,用“面向对象的语言”写出的代码,仍然是面向过程思想的代码;在很多的大神手中,用“面向过程的语言”也能够写出面向对象思想的代码。那我们应该怎样理解“Python是面向对象的编程语言”这句话呢?
使用Python这门语言可以更加容易写出具有面向对象编程思想的代码!
2. 类与对象
在面向对象的编程思想中,着眼点在于找到一个能够帮助解决问题的实体,然后委托这个实体解决问题。这个具有特定功能,能够帮助解决特定问题的实体,称为一个对象。而由若干个具有相同的特征和行为的对象组成的集合,称为一个类。
类是对象的集合,对象是类的个体
在程序中,我们需要先定义类,在类中定义该类的对象共有的特征和行为。
类是一种自定义的数据类型,通常用来描述生活中的一些场景
例如: Dog类用来描述狗类,
定义了特征: 姓名、性别、毛色
定义了行为: 吃饭、睡觉
那么,这个类的每一个对象都具备这些特征和行为。
3. 类的设计与对象的实例化
我们使用类来描述现实生活中的一些场景,这里我们以狗类为例
# 使用关键字class定义一个类
class Dog:
def __init__(self):
# 使用self.定义属性
self.name = None
self.age = None
self.kind = None
# 定义对象的行为,用方法来定义,又称为“成员方法”
# 成员方法的定义,参数必需添加self,表示对象本身
def bark(self):
# 在方法中,使用self.访问对象自己的属性和方法
print(f"{self.name} 在狗叫")
# 实例化对象
xiaobai = Dog()
# 访问类的属性
xiaobai.name = "xiaobai"
xiaobai.age = 1
print(xiaobai.name)
print(xiaobai.age)
# 访问类的方法
xiaobai.bark()
4. 构造方法
我们在一个类中可以定义很多的属性,在使用的时候一个个的进行赋值有点麻烦。因此我们就需要能够在创建对象的同时完成属性的初始化赋值操作,此时就可以使用“构造方法”
# 使用关键字class定义一个类
class Dog:
# __init__是初始化对象的时候自动调用的方法,称为“构造方法”
# 在构造方法中,也可以完成属性的定义与初始化赋值操作
def __init__(self, name, age, kind):
# 使用self.定义属性,后面直接使用形参完成初始化赋值
self.name = name
self.age = age
self.kind = kind
# 定义对象的行为,用方法来定义
# 成员方法的定义,参数必需添加self,表示对象本身
def bark(self):
# 在方法中,使用self.访问对象自己的属性和方法
print(f"{self.name} 在狗叫")
# 实例化对象
xiaobai = Dog("xiaobai", 1, "samo")
# 访问类的属性
print(xiaobai.name)
print(xiaobai.age)
# 访问类的方法
xiaobai.bark()
5. 魔术方法
在类中存在一些在方法名的前后都添加上__的方法,称为“魔术方法”。魔术方法不需要手动调用,而是在适当的时机自动调用。
魔术方法 | 调用时机 | 使用场景 |
---|---|---|
__new__ | 实例化对象,开辟内存空间的时候调用 | |
__init__ | 初始化对象,属性初始化的时候调用 | 定义属性、完成属性的初始化赋值操作 |
__del__ | 删除对象的时候调用 | 释放持有的资源等 |
__cal__ | 对象函数,将对象当作函数调用的时候触发 | |
__str__ | 将对象转成字符串的时候调用str() | 需要将对象转为自己希望的字符串表示形式 |
__repr__ | 返回对象的规范字符串表示形式 | 透过容器看对象 |
__add__ | 使用 + 对两个对象相加的时候调用 | 完成对象相加的逻辑 |
__sub__ | 使用 - 对两个对象相减的时候调用 | 完成对象相减的逻辑 |
__mul__ | 使用 * 对两个对象相乘的时候调用 | 完成对象的乘法逻辑 |
__truediv__ | 使用 / 对两个对象相除的时候调用 | 完成对象的除法逻辑 |
__floordiv__ | 使用 // 对两个对象相除的时候调用 | 完成对象的向下整除逻辑 |
__mod__ | 使用 % 对两个对象求模的时候调用 | 完成对象的求模逻辑 |
__pow__ | 使用 ** 对两个对象求幂的时候调用 | 完成对象的求幂逻辑 |
__gt__ | 使用 > 对两个对象比较的时候调用 | 完成对象的大于比较 |
__lt__ | 使用 < 对两个对象比较的时候调用 | 完成对象的小于比较 |
__ge__ | 使用 >= 对两个对象比较的时候调用 | 完成对象的大于等于比较 |
__le__ | 使用 <= 对两个对象比较的时候调用 | 完成对象的小于等于比较 |
__eq__ | 使用 == 对两个对象比较的时候调用 | 完成对象的相等比较 |
__ne__ | 使用 != 对两个对象比较的时候调用 | 完成对象的不等比较 |
__contains__ | 使用 in 判断是否包含成员的时候调用 | 完成in判断包含的逻辑 |
# 定义一个点类
class Point:
# 完成属性的初始化赋值
def __init__(self, x, y):
self.x = x
self.y = y
# 完成两个点的相加,得到一个新的点
def __add__(self, other):
return Point(self.x + other.x, self.y + other.y)
# 完成两个点的相减,得到一个新的点
def __sub__(self, other):
return Point(self.x - other.x, self.y - other.y)
# 判断两个点是否相等,通过属性值是否相等的判断
def __eq__(self, other):
return self.x == other.x and self.y == other.y
# 判断两个点是否不等,通过属性值是否相等的判断
def __ne__(self, other):
return self.x != other.x or self.y != other.y
# 判断一个点的x和y坐标中,是否包含指定的值
def __contains__(self, elem):
return elem == self.x or elem == self.y
# 将一个点转为字符串表示形式
def __str__(self):
return "{%d, %d}"%(self.x, self.y)
# 定义一个矩形类
class Rect:
# 完成属性的初始化赋值
def __init__(self, length, width):
self.length = length
self.width = width
# 计算面积
def getArea(self):
return self.length * self.width
# 完成两个矩形的面积比较
def __gt__(self, other):
return self.getArea() > other.getArea()
def __lt__(self, other):
return self.getArea() < other.getArea()
def __ge__(self, other):
return self.getArea() >= other.getArea()
def __le__(self, other):
return self.getArea() <= other.getArea()
def __eq__(self, other):
return self.getArea() == self.getArea()
def __ne__(self, other):
return self.getArea() != self.getArea()
# 将一个矩形转为字符串表示形式
def __str__(self):
return f"length: {self.length}, width: {self.width}, area: {self.getArea()}"
关于__str__和__repr__
__str__: 在使用str(obj)的时候触发
__repr__: 官方的描述是返回对象的规范字符串表示形式
两者都是将对象转成字符串的。
如果是直接用print打印的话,会自动的调用str()方法,因此会触发__str__
但是如果需要透过容器看对象的时候,是以__repr__为准的
那么我们应该写__str__还是__repr__呢?
推荐__repr__,因为如果只定义了__repr__,没有定义__str__。此时__str__是与__repr__相同的!
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f"str: name = {self.name}, age = {self.age}"
def __repr__(self):
return f"repr: name = {self.name}, age = {self.age}"
xiaobai = Person("xiaobai", 18)
# 直接打印
print(xiaobai) # str: name = xiaobai, age = 18
# 放入容器
arr = [xiaobai]
print(arr) # repr: name = xiaobai, age = 18
6. 类与类的关系
我们在一个程序中,可能会设计很多的类,而类与类之间的关系可以分为3种:
- 类中使用到另外一个类的对象来完成对应的需求
- 类中使用到另外一个类的对象作为属性
- 继承
使用到另一个类的对象完成需求
"""
案例: 上课了,老师让学生做自我介绍
分析:
1、需要设计两个类: 老师类 和 学生类
2、老师类的功能: 让学生做自我介绍
3、学生类的功能: 自我介绍
"""
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
def introduce(self):
print(f"大家好,我叫{self.name},我今年{self.age}岁了!")
class Teacher:
def __init__(self, name):
self.name = name
def make_introduce(self, student):
print(f"{self.name}说: 各位同学安静一下,我们听{student.name}来做自我介绍")
print(f"{student.name}: ", end="")
student.introduce()
# 实例化老师和学生的对象
xiaoming = Student("xiaoming", 19)
laowang = Teacher("laowang")
laowang.make_introduce(xiaoming)
使用到另一个类的对象作为属性
"""
案例: 判断一个圆是否包含一个点
分析:
需要设计的类: 圆类、点类
点类需要设计的属性: x, y坐标
圆类需要设计的属性: 圆心(点类型), 半径
"""
# 定义点类
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return "{%d, %d}"%(self.x, self.y)
# 定义圆类
class Circle:
def __init__(self, center, radius):
self.center = center
self.radius = radius
# 判断是否包含一个点
def contains_point(self, point):
distance = (self.center.x - point.x) ** 2 + (self.center.y - point.y) ** 2
return distance <= self.radius ** 2
def __contains__(self, item):
return self.containsPoint(item)
# 创建点对象
point = Point(10, 20)
# 创建圆对象
circle = Circle(Point(5, 18), 8)
# 判断圆是否包含点
print(circle.contains_point(point))
print(point in circle)