PHP面向对象(类、对象)全解析

2018-01-26 10:07:00
linefo
原创
1630
01、面向对象?


简称OOP,OOP达到了软件工程的三个目标:重用性,灵活性和扩展性。使用面向对象的好处在于:模块化的思想使开发更加灵活,便于理解和修改某部分模块。


02、什么是类?什么是对象?


举两个例子:

第一:小红和小李都是人
这里“人”就可以看作类,“小红”、“小李”就可以看作“对象”。作为类的“人”,有很多属性,比如身高、体重等。而小红和小李都属于“人”这个集合体。
而小红和小李虽然都属于人,但他们两个又是不同的个体。可以当作是从人这个类中声明出来的两个有差异的对象

第二:根据电脑配置单配置了10台电脑
电脑的配置清单就相当于一个类,根据配置清单配置的电脑就是对象。我们可以对配置出来的电脑进行不同的修改,这样对象就有差异了

03、成员属性、成员方法


PHP中声明一个类的形式为

【这里有一个规范,类名的首字母要大写】:
【类名用驼峰命名的规范,比如PersonMan这种的】


成员属性就是类中的常量和变量(这里的常量和变量指的是直接在类结构体下面的常量和变量,而非类的方法中的常量和变量),而成员方法就是指在类中的函数
结构如:



【public $name不能写成$name,不然会出错】
【var $name相当于public $name,不过这是比较老的写法,现在比较少了】

04、实例化对象


实例化对象非常简单。格式如:$bob = new Person();

一个类可以实例化出多个对象(红色箭头所指的就是实例化对象的方式了)





扩展知识:
对象和整型、浮点型一样,是一种数据类,都是存储不同类型的数据用的,在运行的时候都要加载到内存中去用。
我们可以把内存看作几段,其中包括栈空间段和堆空间段等。
栈空间段是存储占用相同空间长度并且占用空间小的数据类型的地方,比如说整型1, 10, 100, 1000, 10000, 100000等等,在内存里面占用空间是等长的,都是64位4个字节。
数据长度不定长,而且占有空间很大的数据类型的数据放在那内存的那个段里面呢?这样的数据是放在堆内存里面的。
栈内存是可以直接存取的,而堆内存是不可以直接存取的内存。对于我们的对象来说它是一种大的数据类型而且是占用空间不定长的类型,所以说对象是放在堆里面的,但对象名称是放在栈里面的,这样通过对象名称就可以使用对象了。

$p1=new Person();
对于这条代码, $p1是对象名称,名称(作用是引用)放在栈内存里面,真正的对象是在堆内存里面的
如图:




05、使用对象成员


使用成员属性:$p1->name

使用成员方法:$p1->say()
“->”符号表示一种“所属关系”
【注意下面我对成员属性赋予了默认值】


显示:



06、使用$this


$this表示“当前对象/本对象”,用来做对本对象的引用

显示:

当然也可以引用方法



07、构造方法(也叫初始化方法)__construct()和析构方法__destruct()


构造方法:新建一个对象时就会被调用的方法,一般用来做初始化

析构方法:当一个对象被销毁时调用的方法
【注意是两条下划线】

初步理解构造方法__construct():


显示:


【在一个类中只能声明一个构造方法,而且构造方法不能被主动调用,新建对象会被自动调用】


初步理解析构方法__destruct():


显示:


【系统确认对象不会再使用后就会调用析构方法,对象在内存中被销毁前调用析构函数,同时,析构函数不能带任何参数

使用构造函数进行初始化:


显示:

构造函数初始化时可以带默认值,比如:


析构函数的销毁顺序:

显示:


【注意:由于类实例是以堆栈的形式放在内存中,所以最后调用 析构函数 的时候,输出顺序是按 后进先出 的原则!】
【我们常在析构函数中进行清理、收尾的工作。比如用mysql_close()关闭被打开的数据连接】

【几个声明类的规范】
类名要有意义,格式为“类名.class.php”
最好一个类一个文件
类名首字母大写

08、封装


封装是面向对象编程的三大特性之一

封装的原则在软件上的反映是:要求使对象以外的部分不能随意存取对象的内部数据(属性),从而有效的避免了外部错误对它的"交叉感染",使软件错误能够局部化,大大减少查错和排错的难度。

private声明的对象只能在本类中访问(子类也不能访问)


显示:


类中的方法面前没有加private之类的关键字时,默认是public的方法(成员属性必须用public一类的关键词声明,前面不能为空)


我们可以通过类中的(public)方法去访问类中私有的成员属性/方法,比如:


利用构造函数对私有成员属性赋初值

显示:


09、__get()【获取属性】、__set()【设置属性】、__isset()【检查属性】、__unset()【删除属性】四个方法


一般来说,总是把类里面的成员属性定义为private,这样更符合显示的逻辑。

为了方便对类中私有成员属性进行读取和设置,所以有__get()方法和__set()方法,这两个方法是要自己写到类里面去的
【1】__get()方法:



【2】__set()方法:


【通过设置__get()方法和__set()方法就能使得私有的成员属性能够像非私有一样调用】
【对成员属性进行私有化之后,再同时设置__get()方法和__set()方法就没有什么意义了。当只设置了__get(),就可以排除掉写入赋值的权限。如果两个一起用的话,更多是在加密解密这类操作上。】

【3】__isset()方法:
__isset()方法在类中使用时,要和isset()方法配合使用。
我们知道,isset()方法用于判定变量是否存在。
isset()方法在对类使用时:



显示:


【因为外部无法操作private、protected的成员属性,所以它们也被判断为不存在的成员属性(实际上存在),返回false。而public的经由isset()方法返回了true】

所以为了能够也判断一个对象中private、protected的成员属性是否存在,我们需要在类中加入__isset()方法(原理是在本对象中就可以访问到被限制的成员属性):
【__isset():该魔术方法必须含有一个参数,该参数为测定的私有属性的名称,在方法内部必须返回boolean】


显示:



【4】__unset()方法:
__unset()方法在类中使用时,要配合unset()方法使用。
我们知道,unset()函数用来删除变量。


显示结果:


为了能够删除对象里面被保护的成员属性,我们需要使用__unset()方法:
【__unset():该魔术方法必须含有一个参数,该参数为需删除的私有属性的名称。方法不需要返回值】


显示:



10、继承


继承是面向对象的三大特性之一。

我们称已存在的用来派生新类的类为基类,又称为父类以及超类。由已存在的类派生出的新类称为派生类,又称为子类
从一个基类派生的继承称为单继承;从多个基类派生的继承称为多继承。
但是在PHP和Java语言里面没有多继承,只有单继承,也就是说,一个类只能直接从一个类中继承数据, 这就是我们所说的单继承。



继承关键字 :extends
属于向下继承的原理,基类不能使用派生类里的内容
方法重载我们也可以理解方法覆盖,在派生类里使用与基类方法重名的方法名称执行重载。

【1】继承的简单应用:


显示:


【子类可以扩展新的成员属性、成员方法】

11、子类方法重写


显示:



12、在子类中调用父类的方法


【parent:: 是直接的父级,类名:: 可以往上回溯更多层级】


显示:


13、类型的访问修饰符


包括public、protected、private。



另外在子类覆盖父类的方法时也要注意一点,子类中方法的访问权限一定不能低于父类被覆盖方法的访问权限,也就是一定要高于或等于父类方法的访问权限。

14、final关键字


这个关键字只能用来定义和定义方法, 不能使用final这个关键字来定义成员属性

使用final关键标记的类不能被继承;



会出现下面错误:


使用final关键标记的方法不能被子类覆盖(重写),是最终版本;


会出现下面错误:



15、static和const关键字的使用(self::)

【static定义的成员属性/成员方法一样可以用public、protected、private修饰。默认是public】
static关键字是在类中描述成员属性成员方法是静态的;静态的成员好 处在哪里呢?前面我们声明了“Person”的人类,在“Person”这个类里如果我们加上一个“人所属国家”的属性,这样用“Person”这个类实 例化出几百个或者更多个实例对象,每个对象里面就都有“所属国家”的属性了,如果开发的项目就是为中国人而开发的,那么每个对象里面就都有一个国家的属性 是 “中国“,其它的属性是不同的,如果我们把“国家”的属性做成静态的成员,这样国家的属性在内存中就只有一个,而让这几百个或更多的对象共用这一个属性,static成员能够限制外部的访问【无法通过以类来声明对象的方式直接读到static成员(但是可以通过函数间接获取到)】,因为static的成员是属于类的,是不属于任何对象实例,是在类第一次被加载的时候分配的空间,其他类是无法访问的,只对类的实例共享,能一定程度对类该成员形成保护
从内存的角度我们来分析一下,内存从逻辑上被分为四段,其中对象是放在“堆内存”里面,对象的引用被放到了“栈内存“里,而静态成员则放到了“初始化静态段”,在类第一次被加载的时候放入的,可以让堆内存里面的每个对象所共享,如下图:


类的静态变量,非常类似全局变量,能够被所有类的实例共享,类的静态方法也是一样的,类似于全局函数。


【如果把public static写成static也可以,因为默认就是public。但是为了语义没有二义性,所以建议写上public】
显示:


【因为静态成员是在类第一次加载的时候就创建的,所以在类的外部不需要对象而使用类名就可以访问的到静态的成员】
使用对象访问不到静态成员的

类里面的静态方法只能访问类的静态的属性,在类里面的静态方法是不能访问类的非静态成员的,原因很简单,我们要想在本类的方法中访问本类的其它成员,我们需要使用$this这个引用,而$this这个引用指针是代表调用此方法的对象,我们说了静态的方法是不用对象调用的,而是使用类名来访问, 所以根本就没有对象存在,也就没有$this这个引用了

即然$this不存在,在静态方法中访其它静态成员我们使用的是一个特殊的类“self”; self和$this相似,只不过self是代表这个静态方法所在的类。所以在静态方法里,可以使用这个方法所在的类的“类名“,也可以使用“self”来访问其它静态成员,如果没有特殊情况的话,我们通常使用后者,即“self::成员属性”的方式。




【在非静态方法里面访问静态成员也要使用类名或是”self::成员属性的形式】


显示:



const是一个定义常量的关键字,在PHP中定义常量使用的是“define()”这个函数,但是在类里面定义常量使用的是“const”这个关键字,类似于C中的#define如果在程序中改变了它的值,那么会出现错误,用“const”修饰的成员属性的访问方式和“static”修饰的成员访问的方式差不多,也是使用“类名”,在方法里面使用“self”关键字(用$this->常量名来调用无效,要self::常量名)。但是不用使用“$”符号,也不能使用对象来访问



【外部访问常量也用类名::常量名,记住常量名不用加$】
【对象引用是一个指针】
【类中的常量没有public这类关键字的说法,本身就可以内部调用和外部调用】
下面这种写法是错误的,会报错:




16、toString()方法:


我们前面说过在类里面声明“__”开始的方法名的方法(PHP给我们提供的),都是在某一时刻不同情况下自动调用执行的方 法,“__toString()”方法也是一样自动被调用的,是在直接输出对象引用时自动调用的, 前面我们讲过对象引用是一个指针,比如 说:“$p=new Person()“中,$p就是一个引用,我们不能使用echo 直接输出$p,这样会输 出 “Catchable fatal error: Object of class Person could not be converted to string” 这样的错误,如果你在类里面定义了“__toString()”方法,在直接输出对象引用的时候,就不会产生错误,而是自动调用 了”__toString()”方法, 输出“__toString()”方法中返回的字符,所以“__toString()”方法一定要有个返回值(return 语句)




显示:



17、克隆对象


顾名思义,就是克隆一个各种属性一模一样的新对象出来

语法:
新对象 = clone 旧对象;



有一个特殊的方法“__clone()”:
对象克隆时被自动调用,在__clone()方法中可以对克隆后的对象进行修改。
【$that指针好像PHP5中被移除了】


显示:



18、__call()处理调用错误


使用了__call()方法后,当调用类中不存在的方法时,__call()就会被自动调用。

【__call方法里面不能用return来返回值】
__call()方法有两个参数:
第一个参数是错误调用的方法名。第二个参数是错误方法所传进来的参数(以数组形式)。



【可以利用__call()方法进行方法别名的处理|方便不同命名习惯的人】
目的:类中有一个talk方法,但是另一个人想用say方法达到同样的效果。并且不想重新写这个方法
例子:



19、抽象方法和抽象类(abstract)


抽象方法就是为了方便继承而引入的

什么是抽象方法?我们在类里面定义的没有方法体的方法就是抽象方法所谓的没有方法体指的是,在方法声明的时候没有大括号以及其中的内容,而是直接在声明时在方法名后加上分号结束,另外在声明抽象方法时还要加一个关键字“abstract”来修饰
例如:


上例是就是“abstract”修饰的没有方法体(函数体)的抽象方法“fun1()”和“fun2()”,不要忘记抽象方法后面还要有一个分号;那么什么是抽象类呢?只要一个类里面有一个方法是抽象方法,那么这个类就要定义为抽象类,抽象类也要使用“abstract”关键字来修饰;在抽象类里面可以有不是抽象的方法和成员属性,但只要有一个方法是抽象的方法,这个类就必须声明为抽象类,使用”abstract”来修饰。
例如:


上例中定义了一个抽象类“Demo”使用了”abstract”来修饰, 在这个类里面定义了一个成员属性“$test”,和两个抽象方法“fun1”和“fun2”,还有一个非抽象的方法fun3();

那么抽象类我们怎么使用呢?最重要的一点就是抽象类不能产生实例对象, 所以也不能直接使用,前面我们多次提到过类不能直接使用,我们使用的是通过类实例化出来的对象,那么抽象类不能产生实例对象我们声明抽象类有什么用呢?我 们是将抽象方法是做为子类重载的模板使用的,定义抽象类就相当于定义了一种规范,这种规范要求子类去遵守,子类继承抽象类之后,把抽象类里面的抽象方法按 照子类的需要实现。子类必须把父类中的抽象方法全部都实现,否则子类中还存在抽象方法,那么子类还是抽象类,还是不能实例化类;为什么我们非要从抽象类中继承呢?因为有的时候我们要实现一些功能就必须从抽象类中继承,否则这些功能你就实现不了,如果继承了抽象类,就要实现类其中的抽象方法;


显示:


20、接口技术(interface)


PHP与大多数面向对象编程语言一样,不支持多重继承。也就是说每个类只能继承一个父类。为了解决这个问题,PHP引入了接口,接口的思想是指定了一个实现了该接口的类必须实现的一系列方法。接口是一种特殊的抽象类,抽象类又是一种特殊的类,所以接口也是一种特殊的类,为 什么说接口是一种特殊的抽象类呢?如果一个抽象类里面的所有的方法都是抽象方法,那么我们就换一种声明方法使用“接口”;也就是说接口里面所有的方法必须 都是声明为抽象方法(这点和普通的抽象类不一样),另外接口里面不能声明变量(但可声明常量constant,抽象类可以声明变量),而且接口里面所有的成员都是public权限的。所以子类在实现的时候 也一定要使用public权限实现。



上例中定义了一个接口“one”,里面声明了两个抽象方法“fun1”和”fun2”,因为接口里面所有的方法都是抽象方法,所以在声明抽象方法的 时候就不用像抽象类那样使用“abstract”这个关键字了(加了反而会报错),另外在接口里边的”public”这个访问权限也可以去掉,因 为默认就是public的,因为接口里所有成员都要是公有的,所在对于接口里面的成员我们就不能使用“private”的和“protected”的权限 了,都要用public或是默认的。另外在接口里面我们也声明了一个常量“constant“, 因为在接口里面不能用变量成员,所以我们要使用 const这个关键字声明。
因为接口是一种特殊的抽象类,里面所有的方法都是抽象方法,所以接口也不能产生实例对象; 它也做为一种规范,所有抽象方法需要子类去实现(也就是说继承了某接口后,其内所有方法子类中都要去实现)。
我们可以使用”extends”关键字让一个接口去继承另一个接口:
类实现接口的方法(理解成继承亦可)用“implements”关键字


显示:



亦可在继承类的同时实现接口:
class 类名1 extends 类名2 implements 接口1,接口2......

21、多态


多态是除封装继承之外的另一个面象对象的三大特性之一

所谓多态性是指一段程序能够处理多种类型对象的能力,比如说在公司上班,每个月财务发放工资,同一个发工资的方法,在公司内不同的员工或是不同职位的员工,都是通过这个方法发放的,但是所发的工资都是不相同的。所以同一个发工资的方法就出现了多种形态。对于面向对象的程序来说,多态就是把子类对象赋值给父类引用,然后调用父类的方法,去执行子类覆盖父类的那个方法
其实在我们PHP这种弱类形的面向对象的语言里面,多态的特性并不是特别的明显,其实就是对象类型变量的变相引用。
【所以暂缺】

22、把对象串行化(一串附带类所有信息的字符串,传输时可能要这么用):


serialize()、unserialize()、__sleep()、__unsleep()四个相关的函数:

【尼玛,重大发现!数组也可以使用serialize()函数进行串行化】
不过现在交互还是用Json吧,json_encode()和json_decode()。

23、实现简单的函数重载


【这个函数重载(伪)不涉及参数类型,而是仅仅针对参数的个数不同而执行不同的方法】


------------------------------------------------------------------------------------------------------


<meta charset="utf-8" />
<?php
class Demo{
         public function __call($funcName,$args){
                   if($funcName == 'countParam'){
                            switch(count($args)){
                                     case 0:
                                               $this->countParam0();
                                               break;
                                     case 1:
                                               $this->countParam1();
                                               break;
                                     case 2:
                                               $this->countParam2();
                                               break;
                                     case 3:
                                               $this->countParam3();
                                               break;
                                     default:
                                               $this->countOver();
                                               break;
                            }
                   }
         }
        
         public function countParam0(){
                   echo "传了0个参数";
         }
        
         public function countParam1(){
                   echo "传了1个参数";
         }
        
         public function countParam2(){
                   echo "传了2个参数";
         }
        
         public function countParam3(){
                   echo "传了3个参数";
         }
        
         public function countOver(){
                   echo "传了超过3个参数";
         }
}
 
$demo =new Demo();
// 传一个参数时
$demo->countParam(1);
 
// 传三个参数时
$demo->countParam(1,2,3);
 
// 超过三个参数时
$demo->countParam(1,2,3,4,5);

-----------------------------------------------------------------------------------------------------
24、__autoload()方法:


此方法用来批量引用类。

首先来看目录结构
根目录:


根目录->class:


例子:
三个类文件的内容都是这种形式:



然后
index.php



运行:



【解析】
__autoload()函数采用了一种类似文件遍历的方式。参数是智能化的识别的文件名的第一部分(比如A.class.php就会识别到A)。
具体使用方式如上所示





文章分类
联系我们
联系人: Mr.Chen
QQ: 185391277