面向对象编程(OOP)与结构化编程是两种不同的编程范式,它们解决问题的思路有着本质区别:
- 分析阶段:通过自顶向下的方式将问题分解为若干步骤
- 实现阶段:将每个步骤转化为独立的方法/函数
- 执行方式:程序通过依次调用这些方法来完成功能
- 示例:银行转账程序可能包含:
- validateInput()验证输入
- checkBalance()检查余额
- updateAccounts()更新账户
- logTransaction()记录日志
- 局限性:数据和方法分离,当系统复杂时维护困难
- 思维方式:将现实世界的事物抽象为具有属性和行为的对象
- 核心概念:
- 封装:隐藏对象内部实现细节(如汽车不需要了解发动机原理就能驾驶)
- 继承:实现代码复用(如"电动车"继承自"汽车"的基本特性)
- 多态:相同接口不同实现(如不同支付方式的支付操作)
面向过程编程(Procedure Oriented Programming,POP)是一种以过程为中心的编程范式。
- 详细分析功能需求,将其拆解为一系列具体的操作步骤
- 每个步骤对应一个独立的函数或过程
- 通过函数调用的线性组合来实现完整功能
- 开发者需要亲自处理每个细节,包括数据结构和算法实现
典型应用场景:
- 计算器程序:需要逐步实现输入处理、运算执行、结果显示等步骤
- 文件处理:需要依次完成打开文件、读取数据、处理内容、保存结果等操作
- 数学运算:如求解方程需要分解为输入系数、计算判别式、求根等步骤
面向对象编程(Object Oriented Programming,OOP)是一种以对象为中心的编程范式。在实现功能时,开发者关注的是:
- 识别系统中存在的对象及其属性和行为
- 通过创建对象实例来完成任务
- 对象之间通过消息传递进行协作
- 开发者可以"委托"对象完成工作,而非亲自实现每个细节
- 封装:将数据和操作封装在对象内部
- 继承:通过继承复用已有类的功能
- 多态:同一操作在不同对象上有不同表现
实际应用示例:
- 图形界面开发:按钮、窗口等组件都是对象,开发者只需设置属性,不用关心如何绘制
- 游戏开发:角色、道具等作为对象,开发者调用对象方法实现交互
- 企业系统:员工、部门等业务实体作为对象,通过对象协作完成业务流程
两者的本质区别在于: 面向过程是"怎么做"(How)的思维,强调算法和步骤;面向对象是"谁来做"(Who)的思维,强调责任分配和对象协作。面向对象通过封装和抽象,让开发者可以站在更高层次思考问题,将部分工作委托给对象完成。
类和对象:
类是对一系列具有相同属性和行为的事物的抽象描述,它是一个模板或蓝图,不是真实存在的具体事物。类定义了该类别事物共有的特征和行为。
class ClassName: """类的文档字符串""" class_body
其中:
ClassName
是类名,必须符合以下规则:
- 遵循大驼峰命名法(PascalCase),即每个单词首字母大写,如
StudentInfo
- 不能使用Python关键字
- 不能包含特殊字符(除下划线外)
- 尽量做到见名知意,反映类的功能或特性
class_body
包含类的属性和方法定义
class Car: """汽车类,描述汽车的基本属性和行为""" def __init__(self, brand, color): self.brand = brand # 品牌属性 self.color = color # 颜色属性 def drive(self): """行驶方法""" print(f"{self.color}的{self.brand}汽车正在行驶")
对象是类的具体实例,是面向对象编程的核心实体。类定义了结构,而对象则是根据这个结构创建的具体实例。
创建对象的格式:
object_name = ClassName(arguments)
其中:
object_name
是对象名,遵循小写字母和下划线的命名惯例ClassName(arguments)
调用类的构造方法创建对象
my_car = Car("Toyota", "红色") # 创建一个Car类的实例
your_car = Car("BMW", "黑色") # 创建另一个Car类的实例
类与对象的关系:
- 必须先定义类,才能创建该类的对象
- 类是一个抽象概念,对象是类的具体表现
- 一个类可以创建多个对象实例
对象独立性:
- 每个对象都是独立的,拥有自己的属性值
- 修改一个对象的属性不会影响其他对象
实际应用场景:
- 在游戏开发中,一个"Enemy"类可以创建多个敌人对象
- 在电商系统中,一个"Product"类可以创建多种商品对象
- 在社交应用中,一个"User"类可以创建多个用户账号对象
# 2.类和对象
# 类就是对一系列具有相同属性和行为的事物的统称,不是真实存在的事物
# 对象是类的具体表现,是类创建出来的真实存在的事物,是面向对象编程的核心
# 在开发中,先有类,再有对象
# 2.1 类的三要素
# 1.类名
# 2.属性:对象的特征描述,用来说明是什么样子的
# 3.方法:对象具有的功能(行为),用来说明能够做什么
# 举例:
# 类名:植物类
# 属性:叶子、根茎、果实
# 方法:光合作用、吸收水分、开花结果
# 2.2定义类
# 基本格式
# class类名: # 类名要符合标识符规定,同时遵循大驼峰命名法,见名知意
# 代码块
# 植物类
class Plant:
leaf = 100 # 类属性:就是类所拥有的属性
print(Plant.leaf)
# 新增类属性:类名.属性名 = 值
Plant.fruit = 50
print(Plant.fruit)
在面向对象编程中,创建对象是程序运行的基本操作之一。
# 创建对象
# 创建对象的过程也叫做实例化对象
# 2.3.1实例化对象的基本格式:对象名 = 类名()
# 实例化一个植物对象
class Plant:
leaf = 100
flower1 = Plant()
print(flower1) # 显示的对象在内存中的地址
# <__main__.Plant object at 0x000002973EF80F80>
# 第二次实例化
flower2 = Plant()
print(flower2)
# <__main__.Plant object at 0x000002973EF80F50>
# 内存地址不一样,说明是不同的对象,可以实例化多个对象
- 内存管理:频繁创建大量对象可能导致内存问题
- 原型继承:理解原型链对于正确创建对象很重要
- 性能考量:不同创建方式在性能上可能有差异
- 对象初始化:确保对象被正确初始化,避免undefined属性
实例对象是通过类创建的具体实体,每个实例都拥有独立的属性和方法。例如:
class Dog:
def __init__(self, name):
self.name = name # 实例变量
my_dog = Dog("Buddy") # 创建Dog类的实例对象
your_dog = Dog("Max") # 另一个实例对象
- 定义方式:实例方法第一个参数必须是
self
,代表当前实例对象- 调用方式:必须通过实例对象调用,不能通过类直接调用
- 访问权限:可以访问和修改实例的所有属性和方法
class BankAccount:
def __init__(self, account_holder, balance=0):
self.account_holder = account_holder
self.balance = balance
# 实例方法
def deposit(self, amount):
self.balance += amount
print(f"存入{amount}元,当前余额:{self.balance}")
def withdraw(self, amount):
if amount > self.balance:
print("余额不足")
else:
self.balance -= amount
print(f"取出{amount}元,当前余额:{self.balance}")
# 使用实例方法
account = BankAccount("张三", 1000)
account.deposit(500) # 调用实例方法
account.withdraw(200)
特性 | 实例方法 | 类方法 |
---|---|---|
定义修饰符 | 无 | @classmethod |
第一参数 | self (实例对象) | cls (类本身) |
调用方式 | 通过实例调用 | 通过类或实例调用 |
访问权限 | 可访问实例属性和类属性 | 只能访问类属性 |
- 对象状态修改:如游戏角色的移动、攻击等行为
- 对象间交互:如社交网络中的用户关注、点赞操作
- 数据封装:如银行账户的存取款操作封装为实例方法
- 实例方法不能脱离实例单独存在
- 不同实例的同名方法互不影响
- 实例方法可以访问类级别的属性和方法
- 在继承体系中,子类可以重写父类的实例方法
实例属性是绑定到某个特定对象实例上的属性,每个实例可以拥有不同的属性值。实例属性通常在类的
__init__
方法中定义,使用self
关键字进行绑定。类属性是直接绑定到类本身的属性,所有实例共享同一个类属性值。类属性在类定义中直接声明,位于任何方法之外。
存储位置不同
- 实例属性存储在单个实例的
__dict__
中- 类属性存储在类的
__dict__
中访问方式不同
- 实例属性:
instance.attribute
- 类属性:
Class.attribute
或instance.attribute
(当实例没有同名属性时)修改影响范围不同
- 修改实例属性只影响当前实例
- 修改类属性会影响所有实例(除非实例已创建同名实例属性)
class Dog:
# 类属性
species = "Canis familiaris"
def __init__(self, name, age):
# 实例属性
self.name = name
self.age = age
# 创建实例
buddy = Dog("Buddy", 5)
miles = Dog("Miles", 3)
# 访问类属性
print(buddy.species) # Canis familiaris
print(Dog.species) # Canis familiaris
# 修改实例属性
buddy.age = 6
# 修改类属性
Dog.species = "Canis lupus" # 影响所有实例
适合使用类属性的情况:
- 用于存储所有实例共享的常量值
- 用于统计实例数量(如
count = 0
)- 用于定义默认值
适合使用实例属性的情况:
- 需要为每个实例存储独特数据时
- 属性值需要在实例生命周期中变化时
- 需要隔离不同实例的状态时
- 属性查找顺序:Python会先查找实例属性,如果没有找到才会查找类属性
- 可变类属性:当类属性是可变对象(如列表)时,修改它会影响所有实例
- 动态添加属性:可以在运行时动态添加类属性和实例属性
构造函数(Constructor)是一种特殊的类成员函数,它在创建类对象时自动调用,主要用于初始化对象的状态。构造函数具有以下特点:
- 名称与类名完全相同
- 没有返回类型(连void也没有)
- 可以重载(一个类可以有多个不同参数的构造函数)
- 通常声明为public访问权限(除非有特殊需求)
当类没有定义任何构造函数时,编译器会自动生成一个无参数的默认构造函数。默认构造函数会:
- 为基本类型成员变量赋予默认值(如int为0,指针为nullptr等)
- 调用类类型成员变量的默认构造函数
# 3.构造函数__init__
# 作用:通常同来做属性初始化或者赋值操作
# 注意:在类实例化对象的时候,会被自动调用
class Tset:
def __init__(self): # self--实例方法
print("这是__init__()函数")
# 实例化对象:对象名 = 类名()
# li = Tset()
class Person: # 人
def __init__(self,name,age,hight):
self.name = name # 实例属性 姓名
self.age = age # 年龄
self.hight = hight # 身高
def play(self):
print(f"{self.name}在砍巨人")
def introduce(self):
print(f"{self.name}的年龄是{self.age},身高是{self.hight}cm")
# 实例化对象
li = Person('三笠',18,173)
li.play()
li.introduce()
# 实例化第二个对象
li2 = Person('艾伦',19,175)
li2.play()
li2.introduce()
# 实例化第三次
li3 = Person('尤米尔',15,166)
li3.play()
li3.introduce()
- 构造函数不应抛出异常(如果可能抛出,应考虑使用工厂模式)
- 构造函数中应避免调用虚函数(此时对象的虚表可能尚未完全初始化)
- 对于包含const成员或引用成员的类,必须使用初始化列表
- 当类有虚函数时,应定义虚析构函数
__del__方法: 析构方法__del__是Python中一个重要的特殊方法,它在对象生命周期结束时被自动调用。
当对象的引用计数降为0,或者垃圾回收器决定回收该对象时,__del__方法就会被执行。
该方法的主要作用是进行资源清理工作,如关闭文件、释放网络连接等。
具体来说:当没有任何变量引用该对象时,Python的垃圾回收机制会自动调用__del__方法该方法通常用于清理对象占用的非内存资源调用时机不确定,不建议依赖该方法进行关键资源清理
def __del__(self):
self.file.close()
print("文件资源已释放")
# 析构函数 __del__()
# 删除对象的时候,解释器会默认调用__del__()方法
class Person:
def __init__(self):
print("我是__init__()")
def __del__(self):
print("被销毁了")
li = Person()
del li # 删除li这个对象
# del li 语句执行的时候,内存会立即被回收,会调用对象本身的__del__()方法
print("这是最后两行代码")
print("这是最后一行代码")
# 正常运行时,不会调用__del__(),对象执行结束之后,系统会自动调用__del__()
# __del__()主要是表示程序块或者函数已经全部执行结束
__del__方法的调用时机由Python解释器决定 在循环引用的情况下,对象可能不会被立即回收 更好的做法是显式调用close()等方法进行资源管理 不建议在__del__中执行耗时操作或启动新线程
封装是指将复杂的信息、流程和实现细节隐藏在内部,对外仅暴露简单的接口,让使用者无需了解内部实现就能方便地使用功能。就像使用微波炉时,我们只需要设置时间和功率,而不需要知道微波是如何产生的。
- 信息隐藏:将对象的属性和实现细节隐藏在内部
- 接口简化:对外提供简单易用的操作方式
- 安全性:通过访问控制保护关键数据不被随意修改
# 封装
# 面向对象的三大特性:封装、继承、多态
# 封装:指的是隐藏对象中一些不希望被外部访问到的属性或者方法
class Person:
name = '三笠' # 类属性
li = Person()
print(li.name)
Person.name = '艾伦'
print(Person.name)
继承是面向对象编程的重要特性,它允许新创建的类(子类)获得已有类(父类)的属性和方法,并可以在此基础上进行扩展或修改。
- 代码复用:子类自动获得父类的功能
- 扩展性:可以在不修改父类的情况下添加新功能
- 层次结构:可以建立类与类之间的层级关系
封装的实际应用示例:
- 汽车驾驶:驾驶员只需要操作方向盘、油门和刹车,不需要了解发动机如何工作
- ATM取款:用户只需要输入密码和金额,银行系统内部处理所有的账户验证和资金转账
- 智能手机:用户通过触摸屏操作,不需要知道应用程序如何与硬件交互
普通变量和方法是最基础的类型,完全公开可访问。例如:
class MyClass:
def public_method(self):
print("这是一个公开方法")
value = 10 # 公开属性
用一个下划线前缀表示私有属性/方法,这是一种约定俗成的做法(PEP8规范),表示仅供内部使用:
class MyClass:
def _private_method(self):
print("这是一个私有方法,不建议外部调用")
_internal_value = 20 # 私有属性
def public_api(self):
# 在类内部可以调用私有方法
self._private_method()
return self._internal_value
- 仍可以从外部访问,只是表明"不建议直接使用"
- 导入时
from module import *
不会导入_xxx
名称- 常用于子类继承时使用的"保护"成员
双下划线前缀会触发Python的名称改写(name mangling),提供更强的隔离性:
class MyClass:
def __very_private_method(self):
print("这个方法的名称会被改写")
__secret_value = 30
def access_hidden(self):
# 在类内部仍可用原名访问
self.__very_private_method()
return self.__secret_value
- 实际会被改写为
_ClassName__xxx
的形式- 主要用于避免子类中的命名冲突
- 仍可通过改写后的名称访问,但强烈不建议
- 常用于实现框架内部机制
双下划线包围的是Python的特殊方法:
class MyClass:
def __init__(self): # 构造方法
pass
def __str__(self): # 字符串表示
return "MyClass instance"
- 这些都是Python预定义的
- 除非要实现特定协议,否则不要自行创建
- 例如
__len__
,__getitem__
等