一、什么是面向对象编程
面向对象编程(Object-Oriented Programming,简称OOP)是一种编程范式或编程风格,它使用“对象”来设计软件。在OOP中,对象是类的实例,而类定义了对象的属性和行为。这种范式强调将程序逻辑组织成一组可重用的对象,每个对象都包含数据(属性)和操作这些数据的方法(函数或过程)。
二、上手实践
1.为什么要有类
在C语言中,我们可以通过int array[]来定义数组,array[][]来定义二维数组。char a[]来定义单个字符的数组(字符串),a[][]来定义字符串的集合,但是在定义某些数据类型时,我们发现例如学生,他不止有字符串类型的姓名、性别,更有整数类型的学号、年龄等,甚至还有data类型的出生日期,离返校时间。有这些数据之后,我们发现简单的二维数组并不能满足我们的要求。于是,我们学习了C语言的结构体。
#include<stdio.h>struct student{int age;char* name;char* sex;long id;
}; int main(){struct student a;a.age = 20;a.id = 123456789;a.name = "张三";a.sex = "男";printf("%s同学的年龄是%d,学号是%ld,性别是%s",a.name,a.age,a.id,a.sex);
}
这就是在C语言中结构体的应用,我们发现这种把各种信息都放到一个变量中的方法,似乎更加的清晰。让代码的条例更加清楚。
在学习了这种方法之后,我们尝试把指针放入结构体让指针指向下一个结构体,同时每个结构体都存放数据,我们就可以得到一串的结构体,这就是我们所说的链表,在代码中我们只需要保留第一个结构体的地址,就可以把整串都读取出来。
接下来是C语言实现链表的基本操作
#include <stdio.h>
#include <iostream>
#include <stdlib.h>#define OK 1
#define ERROR -1using namespace std;struct node//结点定义
{int data;struct node *next;
};void createlinklist(node *p)//创建链表
{node *c=p; int a;while(scanf("%d",&a)&&a!=0){node *q=new node;q->next=NULL;q->data=a;c->next=q;c=q;}c=c->next;
}void print(node *p)
{node *a=p->next;while(a){cout<<a->data<<' ';a=a->next; }
}void lookingfor(node *p)
{int x;node *c=p->next;cout<<"输入要查找的数"; cin>>x;int y=1;while(c){if(c->data==x)printf("链表中第%d个数字\n",y);c=c->next;y=y+1;}
}void deletedata(node* q,int a)
{node *p=q->next;node *pre=q;while(p&&pre){if(p->data==a){pre->next=p->next;delete p;p=pre->next;}else{pre=pre->next;p=p->next;}}
}int main()
{int a;node *p=new node;createlinklist(p);print(p);cout<<endl<<"1 for looking for"<<endl<<"2 for delete"<<endl;cin>>a;switch(a){case 1:lookingfor(p);break;case 2:cout<<"the data you want to delete:";cin>>a;deletedata(p,a);print(p);}
}
大家只需要浏览一下即可,有兴趣也可以跟着敲一遍。
接下来我们进入正题。在C语言中有了结构体我们可以把不同类型的属性放到一起。但是在python中我们并没有struct关键字来定义结构体,难不成我们要用列表+字典嵌套来实现吗。理论上这种方法的确可行,但是显然代码会比较混乱,所以我们通过定义类来实现。我们可以把类理解为C语言中的结构体,但是它除了能把属性放进去还可以把方法(函数)放进去。这是类的概念:
类(Class):类是一个模板或蓝图,它定义了对象的属性和方法。类是一种抽象数据类型,它指定了对象可以拥有的状态(属性)和行为(方法)。
我们尝试写一个类可以包含学生的基本信息;
我们把属性都放在了__init__的方法中,给所有的属性赋值成空。之后给student创建一个实例化对象(我们把按照这个类创建的对象称为这个类的实例化,例如a=student()那么a是student的一个实例化对象)。随后我们给a的所有属性赋值,发现a实现了像C语言结构体一样的功能。
2.类如何把方法封装进去再调用
在上面的类中,__init__其实就是一个方法,但是它和其他的方法不同。我们首先用一个正常的方法来实践一下。
我们在student类里面定义了一个方法名为printAll()它会给我们打印出所有的信息。这样我们就把方法也放到了函数中。在下面通过a.printAll()就可以执行这个方法。注意在方法中我们要固定的传入一个self,这是规则。
3.构造方法__init__()的使用
比较上面的代码和C语言的struct之后,我们发现它好像没简单到哪去,这是因为我们还没有灵活的使用__init__方法。__init__()方法又叫 构造方法,它不需要有返回值,这个方法在类实例化时自动运行,规定了对象中的元素。
在上面的代码中,我们把初始化的元素全部赋值成了None,之后再给它赋值成我们想要的元素,那么我们能不能一开始就给它赋值成我们想要的元素呢?当然是可以的。我们只需要给__init__方法传入参数,在方法内赋值即可。
我们可以看到代码变得更加简便。
三、面向对象的特性
封装(Encapsulation):封装是将数据和操作这些数据的方法结合在一起,形成一个不可分割的单位。封装隐藏了对象的内部状态,只通过公开的方法与外界进行交互。这有助于保护对象的状态不被意外修改,同时也简化了对象的接口。
继承(Inheritance):继承是一种机制,允许一个类(子类或派生类)继承另一个类(父类或基类)的属性和方法。通过继承,子类可以重用父类的代码,同时添加或覆盖父类的行为。继承支持代码的复用和扩展。
多态(Polymorphism):多态允许一个接口有多种实现形式。在OOP中,多态通常通过方法重写(子类覆盖父类的方法)和接口实现来实现。多态使得相同的操作可以作用于不同的对象,并根据对象的类型产生不同的结果。
在上面的代码中我们已经实现了封装(把属性和方法放在一起),并且已经学会了如何调用属性和方法。接下来我们就尝试一下继承。
1.继承的使用
我们通过一个简单的例子引入:
爸爸有高鼻梁,双眼皮,我们让儿子继承父亲看看儿子的特征是什么。
在上面的代码中class son(father):代表的是我们定义的son类继承了father类。super().__init__()代表在son的构造方法中运行father的构造方法,这样son就有了father的基本属性。同时我们可以发现在son中我们没有定义printAll()方法,但是却能正常运行。这就说明son已经把father的方法也继承了过来。
我们思考一下如果son同时继承father和mother两个类将会怎么样。
与上面不一样的是我们的父类有两个,我们在继承的时候把父类都写在括号里,处理父类的属性时,要分别调用父类的构造方法。但是,在上面的代码中我们发现子类继承的是父亲的nose和母亲的eye。这是不是就像我们一样,继承父母的什么特征是完全随机的?当然不是,在son的构造方法中,我首先调用的是父亲的构造方法这时候son有了父亲的两个属性,之后又调用了母亲的构造方法,这时候由于母亲的eye属性和父亲的重复了,就像我们首先定义a=1之后我们又定义了a=2那么最终a的值一定是2。多继承中也是如此。
在观察后我们又发现了一点,明明父亲有goShopping方法母亲也有,但是son在继承的时候,继承了母亲的方法而不是父亲的。如果方法名重复了,子类继承谁的方法和什么有关系?
其实,字类继承谁的方法与我们继承的先后有关系,在定义字类时,我们发现class son(mother,father):是吧母亲放在了前面,所以说就先继承了母亲的方法。如果我们把父亲放前面他同样会先继承父亲的方法。
2.多态
我们再试想一个问题,父亲的watchBall方法运行结果是看足球,但是son不一定要继承父亲的爱好,有可能son爱看篮球,我们如何把son的watchBall方法改成看篮球呢?
我们只需要在子类中重新写一个和父类的方法相同名字的方法即可实现方法的重写方法重写是指子类提供了一个与其父类方法签名(名称和参数)相同的方法。在Python中,当子类的方法与父类的方法具有相同的名称时,子类的方法会覆盖父类的方法。这是多态的一种形式。
多态性主要有两种类型:
编译时多态(或静态多态):这通常在编译时确定方法调用,依赖于方法重载。然而,正如之前讨论的,Python不支持传统意义上的方法重载,因此编译时多态在Python中不是通过方法重载来实现的。不过,Python可以通过其他机制(如使用不同的函数或类)来模拟这种行为。
运行时多态(或动态多态):这是Python中实现多态的主要方式。当调用一个方法时,Python会在运行时确定应该调用哪个类的实现。这是通过继承和方法重写来实现的。如果子类重写了父类中的方法,那么当通过父类引用调用该方法时(该引用实际上指向一个子类对象),将调用子类中的实现。这是我们上面的例子。
由于它需要定义抽象类,并且在实际操作中使用不多,编译时多态我们不再过多介绍。
四、面向对象知识拓展
如果你学完了上面的知识,并且不打算深入研究那么学到这里,就可以结束了。如果你对代码感兴趣,那么下面的内容作为一些拓展知识来学习也是很不错的。
1.属性管理
在现实生活中,如果你直接访问某人的体重,显然是不礼貌的,并且我们一般不会得到答案,例如在代码中:
给我直接报错了,但是明明他有体重这个属性,我在访问的过程中,为什么要告诉我没有这个属性呢?仔细观察这个属性,它和别的属性不一样,它是以__开头的那么这个属性是被保护起来的,意思就是这个属性只有自己知道,别人谁也不知道。
我们无法直接访问这个属性,那么只有他自己说了我们才知道这个属性是多少。
这样,我们给他定义一个speak方法,让他从内部访问属性输出即可,这是一种保护数据的安全策略。
2.魔术方法
这个名字听起来比较有意思,为什么叫魔术方法,是因为它真的和魔术一样神奇。在你的电脑中1+1运算之后一定是2,如果我们能让他等于3是不是某种程度上也算是一种魔术。
看起来,我们成功实现了这个魔术,那么这个魔术到底是怎么实现的呢?我们看一下完整代码。
class Num:def __init__(self,value):self.value = valuedef __add__(self,other):return self.value + other.value +1def main():a = Num(1)b = Num(1)print(a+b)
main()
Num是我自己定义的类,我们定义了__add__方法,其实__add__方法就是’+‘这是内置的方法。同样的乘法、打印等都可以通过这种重写的方式改变成自己想要的样子。
3.装饰器的使用
Python装饰器是一种高级功能,它允许你在不修改函数或方法代码的情况下,给函数或方法添加额外的功能。装饰器本质上是一个函数,它接收一个函数作为参数,并返回一个新的函数或修改原来的函数。装饰器的语法使用@符号。
试想一下上一位程序员已经跑路,它为我们留下了一些难题,这个代码明显不能输出完整的单词表,我们需要做的就是完善func1函数,我们就要用到装饰器,这是专门拓展函数功能的机制。
4.如何调用其他包中的类
我在之前的代码中写过一个student,和我接下来要写的student完全一样,我不想再重新写一遍了怎么办?如果在同一个目录下(同一文件夹中),我们只需要通过
from yourfile import student
就可以在目前的代码中直接使用了。大家可以尝试一下我就不再写了。