PHP面向对象技术(全面讲解)
本部分一共包括以下一些技术点:
1.面象对象的概念
2.什么是类, 什么是对象, 类和对象这间的关系
3.什么是面向对象编程呢?
4.如何抽象出一个类?
5.如何实例化对象
6.如何去使用对象中的成员
7.特殊的引用“$this“的使用
8.构造方法与析构方法
9.封装性
10.__set() __get() __isset() __unset()四个方法的应用
11.类的继函
13.重载新的方法
14.访问类型
15.静态成员以及类中常量的使用
16.final关键字的应用
17.__toString()方法
18.克隆对象
19.__call处理调用错误
20.自动加载类
21.把对象串行化
22.抽象方法和抽象类
23.php5接口技术
12.多态的应用
1.面象对象的概念
面向对象编程(Object Oriented Programming, OOP, 面向对象程序设计)是一种计算机编程架构,OOP的一条基本原则是计算机程序是由单个能够起到子程序作用的单元或对象组合而成,OOP达到了软件工程的三个目标:重用性、灵活性和扩展性。为了实现整体运算,每个对象都能够接收信息、处理数据和向其它对象发送信息。面向对象一直是软件开发领域内比较热门的话题,首先,面向对象符合人类看待事物的一般规律。其次,采用面向对象方法可以使系统各部分各司其职、各尽所能。为编程人员敞开了一扇大门,使其编程的代码更简洁、更易于维护,并且具有更强的可重用性。有人说PHP不是一个真正的面向对象的语言,这是事实。PHP 是一个混合型语言,你可以使用OOP,也可以使用传统的过程化编程。然而,对于大型项目,你可能需要在PHP 中使用纯的OOP去声明类,而且在你的项目里只用对象和类。这个概念我先不多说了,因为有很多人朋友远离面向对象编程的主要原因就是一接触面向对象概念的时候就理解不上去,
所以就不想去学下去了. 等读者看完整体内容后再去把概念搞明白吧。
2.什么是类, 什么是对象, 类和对象这间的关系
类的概念:类是具有相同属性和服务的一组对象的集合。它为属于该类的所有对象提供了统一的抽象描述,其内部包括属性和服务两个主要部分。在面向对象的编程语言中,类是一个独立的程序单位,它应该有一个类名并包括属性说明和服务说明两个主要部分。
对象的概念:对象是系统中用来描述客观事物的一个实体,它是构成系统的一个基本单位。一个对象由一组属性和对这组属性进行操作的一组服务组成。从更抽象的角度来说,对象是问题域或实现域中某些事物的一个抽象,它反映该事物在系统中需要保存的信息和发挥的作用;它是一组属性和有权对这些属性进行操作的一组服务的封装体。客观世界是由对象和对象之间的联系组成的。
类与对象的关系就如模具和铸件的关系,类的实例化结果就是对象,而对一类对象的抽象就是类.类描述了一组有相同特性(属性)和相同行为(方法)的对象。
上面大概就是它们的定义吧, 也许你是刚接触面象对象的朋友, 不要被概念的东西搞晕了, 给你举个列子吧,如果你去中关村想买几台组装的PC机,到了那里你第一步要干什么, 是不是装机的工程师和你坐在一起,按你提供的信息和你一起完成一个装机的配置单呀, 这个配置单就可以想像成是类,它就是一张纸,但是它上面记录了你要买的PC机的信息,如果用这个配置单买10台机器,那么这10台机子,都是按这个配置单组成的,所以说这10台机子是一个类型的,也可以说是一类的。那么什么是对象呢,类的实例化结果就是对象, 用这个配置单配置出来(实例化出来)的机子就是对象,
是我们可以操作的实体, 10台机子, 10个对象。 每台机子都是独立的,只能说明他们是同一类的,对其中一个机做任何动作都不会影响其它9台机器,但是我对类修改, 也就是在这个配置单上加一个或少一个配件, 那么装出来的9个机子都改变了, 这是类和对象的关系(类的实例化结果就是对象).
3.什么是面向对象编程呢?
就不说他的概念,如果你想建立一个电脑教室,首先要有一个房间, 房间里面要有N台电脑,有N个桌子, N个椅子, 白板, 投影机等等,这些是什么,刚才咱们说了, 这就是对象,能看到的一个个的实体,可以说这个电脑教室的单位就是这一个个的实体对象, 它们共同组成了这个电脑教室,那么我们是做程序,这和面向对象有什么关系呢?开发一个系统程序和建一个电脑教室类似,你把每个独立的功能模块抽象成类形成对象,由多个对象组成这个系统,这些对象之间都能够接收信息、处理数据和向其它对象发送信息等等相互作用。就构成了面向对象的程序。
4.如何抽象出一个类?
上面已经介绍过了, 面向对象程序的单位就是对象,但对象又是通过类的实例化出来的,所以我们首先要做的就是如何来声明类, 做出来一个类很容易,只要掌握基本的程序语法定义规则就可以做的出来,那么难点在那里呢? 一个项目要用到多少个类,用多少个对象, 在那要定义类,定义一个什么样的类,这个类实例化出多少个对象, 类里面有多少个属性, 有多少个方法等等,这就需要读者通过在实际的开发中就实际问题分析设计和总结了。
类的定义:
class 类名{
}
使用一个关键字class和后面加上一个你想要的类名以及加上一对大括号, 这样一个类的结构就定义出来了,只要在里面写代码就可以了, 但是里面写什么? 能写什么?怎样写才是一个完整的类呢?上面讲过来,使用类是为了让它实例出对象来给我们用, 这就要知道你想要的是什么样的对象了,像上面我们讲的一个装机配置单上写什么,你装出来的机子就有什么。比如说,一个人就是一个对象,你怎么把一个你看好的人推荐给你们领导呢?当然是越详细越好了:
首先, 你会介绍这个人姓名、性别、年龄、身高、体重、电话、家庭住址等等。
然后,你要介绍这个人能做什么, 可以开车, 会说英语, 可以使用电脑等等。
只要你介绍多一点, 别人对这个人就多一点了解, 这就是我们对一个人的描述, 现在我们总结一下,所有的对象我们用类去描述都是类似的, 从上面人的描述可以看到, 做出一个类来, 从定义的角度分两部分, 第一是从静态上描述, 第二是从动态上描述, 静态上的描述就是我们所说的属性, 像上面我们看到的,人的姓名、性别、年龄、身高、体重、电话、家庭住址等等。 动态上也就是人的这个对象的功能,比如这个人可以开车, 会说英语, 可以使用电脑等等,抽象成程序时,我们把动态的写成函数或者说是方法,函数和方法是一样的。所以,所有类都是从属性和方法这两方面去写,
属性又叫做这个类的成员属性,方法叫做这个类的成员方法。
class 人{
成员属性:姓名、性别、年龄、身高、体重、电话、家庭住址
成员方法:可以开车, 会说英语, 可以使用电脑
}
属性:
通过在类定义中使用关键字" var "来声明变量,即创建了类的属性,虽然在声明成员属性的时候可以给定初值, 但是在声明类的时候给成员属性初使值是没有必要的,比如说要是把人的姓名赋上“张三”,那么用这个类实例出几十个人,这几十个人都叫张三了,所以没有必要, 我们在实例出对象后给成员属性初使值就可以了。
如: var $somevar;
方法(成员函数):
通过在类定义中声明函数,即创建了类的方法。
如: function somefun(参数列表)
{ ... ... }
<?php
class Person
{
//下面是人的成员属性
var $name; //人的名子
var $sex; //人的性别
var $age; //人的年龄
//下面是人的成员方法
function say() //这个人可以说话的方法
{
echo "这个人在说话";
}
function run() //这个人可以走路的方法
{
echo "这个人在走路";
}
}
?>
上面就是一个类的声明, 从属性和方法上声明出来的一个类, 但是成员属性最好在声明的时候不要给初使的值, 因为我们做的人这个类是一个描述信息, 将来用它实例化对象, 比如实例化出来10个人对象, 那么这10个人, 每一个人的名子, 性别, 年龄都是不一样的, 所以最好不要在这个地方给成员属性赋初值, 而是对每个对象分别赋值的。
用同样的办法可以做出你想要的类了, 只要你能用属性和方法能描述出来的实体都可以定义成类, 去实例化对象。
为了加强你对类的理解, 我们再做一个类, 做一个形状的类, 形状的范围广了点, 我们就做个矩形吧, 先分析一下, 想一想从两方面分析,矩形的属性都有什么? 矩形的功能都有什么?
class 矩形
{
//矩形的属性
矩形的长;
矩形的宽;
//矩形的方法
矩形的周长;
矩形的面积;
}
<?php
class Rect
{
var $kuan;
var $gao;
function zhouChang()
{
计算矩形的周长;
}
function mianJi()
{
计算矩形的面积;
}
}
?>
如果用这个类来创建出多个矩形对象,每个矩形对象都有自己的长和宽, 都可以求出自己的周长和面积了。
类的声明我们就到这里吧!!
5.如何实例化对象
我们上面说过面向对象程序的单位就是对象,但对象又是通过类的实例化出来的,既然我们类会声明了,下一步就是实例化对象了。
当定义好类后,我们使用new关键字来生成一个对象。
$对象名称 = new 类名称();
<?php
class Person
{
//下面是人的成员属性
var $name; //人的名子
var $sex; //人的性别
var $age; //人的年龄
//下面是人的成员方法
function say() //这个人可以说话的方法
{
echo "这个人在说话";
}
function run() //这个人可以走路的方法
{
echo "这个人在走路";
}
}
$p1=new Person();
$p2=new Person();
$p3=new Person();
?>
$p1=new Person();
这条代码就是通过类产生实例对象的过程,$p1就是我们实例出来的对象名称, 同理,$p2, $p3也是我们实例出来的对象名称,一个类可以实例出多个对象,每个对象都是独立的,上面的代码相当于实例出来3个人来, 每个人之间是没有联系的, 只能说明他们都是人类, 每个人都有自己的姓名, 性别和年龄的属性,每个人都有说话和走路的方法,只要是类里面体现出来的成员属性和成员方法,实例化出来的对象里面就包含了这些属性和方法。
对像在PHP里面和整型、浮点型一样,也是一种数据类,都是存储不同类型数据用的,在运行的时候都要加载到内存中去用, 那么对象在内存里面是怎么体现的呢?内存从罗辑上说大体上是分为4段, 栈空间段, 堆空间段,代码段, 初使化静态段,程序里面不同的声明放在不同的内存段里面,栈空间段是存储占用相同空间长度并且占用空间小的数据类型的地方,比如说整型1, 10, 100, 1000, 10000, 100000等等,在内存里面占用空间是等长的,都是64位4个字节。
那么数据长度不定长,而且占有空间很大的数据类型的数据放在那内存的那个段里面呢?这样的数据是放在堆内存里面的。栈内存是可以直接存取的,而堆内存是不可以直接存取的内存。对于我们的对象来数就是一种大的数据类型而且是占用空间不定长的类型,所以说对象是放在堆里面的,但对象名称是放在栈里面的,这样通过对象名称就可以使用对象了。
$p1=new Person();
对于这个条代码, $p1是对象名称在栈内存里面,new Person()是真正的对象是在堆内存里面的,具体的请看下图:
从上图可以看出$p1=new Person();等号右边是真正的对象实例, 在堆内存里面的实体,上图一共有3次new Person(),所以会在堆里面开辟3个空间,产生3个实例对象,每个对象之间都是相互独立的,使用自己的空间,在PHP里面,只要有一个new这个关键字出现就会实例化出来一个对象,在堆里面开辟一块自己的空间.
每个在堆里面的实例对象是存储属性的,比如说,现在堆里面的实例对象里面都存有姓名、性别和年龄。每个属性又都有一个地址。
$p1=new Person();等号的右边$p1是一个引用变量,通过赋值运算符“=”把对象的首地址赋给“$p1“这个引用变量, 所以$p1是存储对象首地址的变量,$p1放在栈内存里边,$p1相当于一个指针指向堆里面的对象, 所以我们可以通过$p1这个引用变量来操作对象, 通常我们也称对象引用为对象。
6.如何去使用对象中的成员
上面看到PHP对象中的成员有两种一种是成员属性, 一种是成员方法。对象我们以经可以声明了,$p1=new Person(); 怎么去使用对象的成员呢?要想访问对象中的成员就要使用一个特殊的操作符”->”来完成对象成员的访问:
对象->属性 $p1->name; $p2->age; $p3->sex;
对象->方法 $p1->say(); $p2->run();
如下面实例:
<?php
class Person
{
//下面是人的成员属性
var $name; //人的名子
var $sex; //人的性别
var $age; //人的年龄
//下面是人的成员方法
function say() //这个人可以说话的方法
{
echo "这个人在说话";
}
function run() //这个人可以走路的方法
{
echo "这个人在走路";
}
}
$p1=new Person(); //创建实例对象$p1
$p2=new Person(); //创建实例对象$p2
$p3=new Person(); //创建实例对象$p3
//下面三行是给$p1对象属性赋值
$p1->name=”张三”;
$p1->sex=”男”;
$p1->age=20;
//下面三行是访问$p1对象的属性
echo “p1对象的名子是:”.$p1->name.”<br>”;
echo “p1对象的性别是:”.$p1->sex.”<br>”;
echo “p1对象的年龄是:”.$p1->age.”<br>”;
//下面两行访问$p1对象中的方法
$p1->say();
$p1->run();
//下面三行是给$p2对象属性赋值
$p2->name=”李四”;
$p2->sex=”女”;
$p2->age=30;
//下面三行是访问$p2对象的属性
echo “p2对象的名子是:”.$p2->name.”<br>”;
echo “p2对象的性别是:”.$p2->sex.”<br>”;
echo “p2对象的年龄是:”.$p2->age.”<br>”;
//下面两行访问$p2对象中的方法
$p2->say();
$p2->run();
//下面三行是给$p3对象属性赋值
$p3->name=”王五”;
$p3->sex=”男”;
$p3->age=40;
//下面三行是访问$p3对象的属性
echo “p3对象的名子是:”.$p3->name.”<br>”;
echo “p3对象的性别是:”.$p3->sex.”<br>”;
echo “p3对象的年龄是:”.$p3->age.”<br>”;
//下面两行访问$p3对象中的方法
$p3->say();
$p3->run();
?>
从上例中可以看出只是对象里面的成员就要使用对象->属性 、对象->方法 形式访问,再没有第二种方法来访问对象中的成员了。
7.特殊的引用“$this“的使用
现在我们知道了如何访问对象中的成员,是通过”对象->成员”的方式访问的,这是在对象的外部去访问对象中成员的形式, 那么如果我想在对象的内部,让对象里的方法访问本对象的属性, 或是对象中的方法去调用本对象的其它方法这时我们怎么办?因为对象里面的所有的成员都要用对象来调用,包括对象的内部成员之间的调用,所以在PHP里面给我提供了一个本对象的引用$this, 每个对象里面都有一个对象的引用$this来代表这个对象,完成对象内部成员的调用, this的本意就是“这个”的意思, 上面的实例里面,我们实例化三个实例对象$P1、 $P2、 $P3,这三个对象里面各自存在一个$this分别代表对象$p1、$p2、$p3 。
通过上图我们可以看到,$this就是对象内部代表这个对象的引用,在对象内部和调用本对象的成员和对象外部调用对象的成员所使用的方式是一样的。
$this->属性 $this->name; $this->age; $this->sex;
$this->方法 $this->say(); $this->run();
修改一下上面的实例,让每个人都说出自己的名字,性别和年龄:
<?php
class Person
{
//下面是人的成员属性
var $name; //人的名子
var $sex; //人的性别
var $age; //人的年龄
//下面是人的成员方法
function say() //这个人可以说话的方法
{
echo "我的名子叫:".$this->name." 性别:".$this->sex." 我的年龄是:".$this->age."<br>";
}
function run() //这个人可以走路的方法
{
echo "这个人在走路";
}
}
$p1=new Person(); //创建实例对象$p1
$p2=new Person(); //创建实例对象$p2
$p3=new Person(); //创建实例对象$p3
//下面三行是给$p1对象属性赋值
$p1->name="张三";
$p1->sex="男";
$p1->age=20;
//下面访问$p1对象中的说话方法
$p1->say();
//下面三行是给$p2对象属性赋值
$p2->name="李四";
$p2->sex="女";
$p2->age=30;
//下面访问$p2对象中的说话方法
$p2->say();
//下面三行是给$p3对象属性赋值
$p3->name="王五";
$p3->sex="男";
$p3->age=40;
//下面访问$p3对象中的说话方法
$p3->say();
?>
输出结果为:
我的名子叫:张三 性别:男 我的年龄是:20
我的名子叫:李四 性别:女 我的年龄是:30
我的名子叫:王五 性别:男 我的年龄是:40
分析一下这个方法:
function say() //这个人可以说话的方法
{
echo "我的名子叫:".$this->name." 性别:".$this->sex." 我的年龄是:".$this->age."<br>";
}
在$p1、$p2和$p3这三个对象中都有say()这个方法,$this分别代表这三个对象, 调用相应的属性,打印出属性的值,这就是在对象内部访问对象属性的方式, 如果相在say()这个方里调用run()这个方法也是可以的,在say()这个方法中使用$this->run()的方式来完成调用。
8.构造方法与析构方法
大多数类都有一种称为构造函数的特殊方法。当创建一个对象时,它将自动调用构造函数,也就是使用new这个关键字来实例化对象的时候自动调用构造方法。
构造函数的声明与其它操作的声明一样,只是其名称必须是__construct( )。这是PHP5中的变化,以前的版本中,构造函数的名称必须与类名相同,这种在PHP5中仍然可以用,但现在以经很少有人用了,这样做的好处是可以使构造函数独立于类名,当类名发生改变时不需要改相应的构造函数名称了。为了向下兼容,如果一个类中没有名为__construct( )的方法,PHP将搜索一个php4中的写法,与类名相同名的构造方法。
格式:function __construct ( [参数] ) { ... ... }
在一个类中只能声明一个构造方法,而是只有在每次创建对象的时候都会去调用一次构造方法,不能主动的调用这个方法,所以通常用它执行一些有用的初始化任务。比如对成属性在创建对象的时候赋初值。
<?
//创建一个人类
class Person
{
//下面是人的成员属性
var $name; //人的名子
var $sex; //人的性别
var $age; //人的年龄
//定义一个构造方法参数为姓名$name、性别$sex和年龄$age
function __construct($name, $sex, $age)
{
//通过构造方法传进来的$name给成员属性$this->name赋初使值
$this->name=$name;
//通过构造方法传进来的$sex给成员属性$this->sex赋初使值
$this->sex=$sex;
//通过构造方法传进来的$age给成员属性$this->age赋初使值
$this->age=$age;
}
//这个人的说话方法
function say()
{
echo "我的名子叫:".$this->name." 性别:".$this->sex." 我的年龄是:".$this->age."<br>";
}
}
//通过构造方法创建3个对象$p1、p2、$p3,分别传入三个不同的实参为姓名、性别和年龄
$p1=new Person(“张三”,”男”, 20);
$p2=new Person(“李四”,”女”, 30);
$p3=new Person(“王五”,”男”, 40);
//下面访问$p1对象中的说话方法
$p1->say();
//下面访问$p2对象中的说话方法
$p2->say();
//下面访问$p3对象中的说话方法
$p3->say();
?>
输出结果为:
我的名子叫:张三 性别:男 我的年龄是:20
我的名子叫:李四 性别:女 我的年龄是:30
我的名子叫:王五 性别:男 我的年龄是:40
如图:
析构函数:
与构造函数相对的就是析构函数。析构函数是PHP5新添加的内容,在PHP4中没有析构函数。析构函数允许在销毁一个类之前执行的一些操作或完成一些功能,比如说关闭文件, 释放结果集等,析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行,也就是对象在内存中被销毁前调用析构函数。与构造函数的名称类似,一个类的析构函数名称必须是__destruct( )。析构函数不能带有任何参数。
格式:function __destruct ( ) { ... ... }
<?
//创建一个人类
class Person
{
//下面是人的成员属性
var $name; //人的名子
var $sex; //人的性别
var $age; //人的年龄
//定义一个构造方法参数为姓名$name、性别$sex和年龄$age
function __construct($name, $sex, $age)
{
//通过构造方法传进来的$name给成员属性$this->name赋初使值
$this->name=$name;
//通过构造方法传进来的$sex给成员属性$this->sex赋初使值
$this->sex=$sex;
//通过构造方法传进来的$age给成员属性$this->age赋初使值
$this->age=$age;
}
//这个人的说话方法
function say()
{
echo "我的名子叫:".$this->name." 性别:".$this->sex." 我的年龄是:".$this->age."<br>";
}
//这是一个析构函数,在对象销毁前调用
function __destruct()
{
echo “再见”.$this->name.”<br>”;
}
}
//通过构造方法创建3个对象$p1、p2、$p3,分别传入三个不同的实参为姓名、性别和年龄
$p1=new Person(“张三”,”男”, 20);
$p2=new Person(“李四”,”女”, 30);
$p3=new Person(“王五”,”男”, 40);
//下面访问$p1对象中的说话方法
$p1->say();
//下面访问$p2对象中的说话方法
$p2->say();
//下面访问$p3对象中的说话方法
$p3->say();
?>
输出结果为:
我的名子叫:张三 性别:男 我的年龄是:20
我的名子叫:李四 性别:女 我的年龄是:30
我的名子叫:王五 性别:男 我的年龄是:40
再见张三
再见李四
再见王五
9.封装性
封装性是面象对象编程中的三大特性之一,封装性就是把对象的属性和服务结合成一个独立的相同单位,并尽可能隐蔽对象的内部细节,包含两个含义:1.把对象的全部属性和全部服务结合在一起,形成一个不可分割的独立单位(即对象)。2.信息隐蔽,即尽可能隐蔽对象的内部细节,对外形成一个边界〔或者说形成一道屏障〕,只保留有限的对外接口使之与外部发生联系。
封装的原则在软件上的反映是:要求使对象以外的部分不能随意存取对象的内部数据(属性),从而有效的避免了外部错误对它的"交叉感染",使软件错误能够局部化,大大减少查错和排错的难度。
用个实例来说明吧, 假如某个人的对象中有年龄和工资等属性,像这样个人隐私的属性是不想让其它人随意就能获得到的,如果你不使用封装,那么别人想知道就能得到,但是如果你封装上之后别人就没有办法获得封装的属性, 除非你自己把它说出去,否则别人没有办法得到。
在比如说,个人电脑都有一个密码,不想让其它人随意的登陆,在你电脑里面拷贝和粘贴。还有就是像人这个对象, 身高和年龄的属性, 只能是自己来增涨,不可以让别人随意的赋值等等。
使用private这个关键字来对属性和方法进行封装:
原来的成员:
var $name; //声明人的姓名
var $sex; //声明人的性别
var $age; //声明人的年龄
function run(){…….}
改成封装的形式:
private $name; //把人的姓名使用private关键字进行封装
private $sex; //把人的性别使用private关键字进行封装
private $age; //把人的年龄使用private关键字进行封装
private function run(){……} //把人的走路方法使用private关键字进行封装
注意:只要是成员属性前面有其它的关键字就要去掉原有的关键字”var”.
通过private就可以把人的成员(成员属性和成员方法)封装上了。封装上的成员就不能被类外面直接访问了,只有对象内部自己可以访问;下面的代码会产生错误:
class Person
{
//下面是人的成员属性
private $name; //人的名子,被private封装上了
private $sex; //人的性别, 被private封装上了
private $age; //人的年龄, 被private封装上了
//这个人可以说话的方法
function say()
{
echo "我的名子叫:".$this->name." 性别:".$this->sex." 我的年龄是:".$this->age."<br>";
}
//这个人可以走路的方法, 被private封装上了
private function run()
{
echo "这个人在走路";
}
}
//实例化一个人的实例对象
$p1=new Person();
//试图去给私有的属性赋值, 结果会发生错误
$p1->name="张三";
$p1->sex="男";
$p1->age=20;
//试图去打印私有的属性, 结果会发生错误
echo $p1->name.”<br>”;
echo $p1->sex.”<br>”;
echo $p1->age.”<br>”
//试图去打印私有的成员方法, 结果会发生错误
$p1->run();
?>
输出结果为:
Fatal error: Cannot access private property Person::$name
Fatal error: Cannot access private property Person::$sex
Fatal error: Cannot access private property Person::$age
Fatal error: Cannot access private property Person::$name
Fatal error: Call to private method Person::run() from context ‘‘
从上面的实例可以看到, 私有的成员是不能被外部访问的, 因为私有成员只能在本对象内部自己访问,比如,$p1这个对象自己想把他的私有属性说出去,在say()这个方法里面访问了私有属性,这样是可以。(没有加任何访问控制,默认的是public的,任何地方都可以访问)
//这个人可以说话的方法, 说出自己的私有属性,在这里也可以访问私有方法
function say()
{
echo "我的名子叫:".$this->name." 性别:".$this->sex." 我的年龄是:".$this->age."<br>";
//在这里也可以访问私有方法
//$this->run();
}
因为成员方法say()是公有的, 所以我们在类的外部调用say()方法是可以的,改变上面的代码;
class Person
{
//下面是人的成员属性
private $name; //人的名子,被private封装上了
private $sex; //人的性别, 被private封装上了
private $age; //人的年龄, 被private封装上了
//定义一个构造方法参数为私有的属性姓名$name、性别$sex和年龄$age进行赋值
function __construct($name, $sex, $age)
{
//通过构造方法传进来的$name给私有成员属性$this->name赋初使值
$this->name=$name;
//通过构造方法传进来的$sex给私有成员属性$this->sex赋初使值
$this->sex=$sex;
//通过构造方法传进来的$age给私有成员属性$this->age赋初使值
$this->age=$age;
}
//这个人可以说话的方法, 说出自己的私有属性,在这里也可以访问私有方法
function say()
{
echo "我的名子叫:".$this->name." 性别:".$this->sex." 我的年龄是:".$this->age."<br>";
}
}
//通过构造方法创建3个对象$p1、p2、$p3,分别传入三个不同的实参为姓名、性别和年龄
$p1=new Person(“张三”,”男”, 20);
$p2=new Person(“李四”,”女”, 30);
$p3=new Person(“王五”,”男”, 40);
//下面访问$p1对象中的说话方法
$p1->say();
//下面访问$p2对象中的说话方法
$p2->say();
//下面访问$p3对象中的说话方法
$p3->say();
?>
输出结果为:
我的名子叫:张三 性别:男 我的年龄是:20
我的名子叫:李四 性别:女 我的年龄是:30
我的名子叫:王五 性别:男 我的年龄是:40
因为构造方法是默认的公有方法(构造方法不要设置成私有的),所以在类的外面可以访问到,这样就可以使用构造方法创建对象, 另外构造方法也是类里面的函数,所以可以用构造方法给私有的属性赋初值。Say()的方法是默认公有的, 所以在外面也可以访问的到, 说出他自己的私有属性。
从上面的例子中我们可以看到, 私有的成员只能在类的内部使用, 不能被类外部直接来存取, 但是在类的内部是有权限访问的, 所以有时候我们需要在类的外面给私有属性赋值和读取出来,也就是给类的外部提供一些可以存取的接口,上例中构造方法就是一种赋值的形式, 但是构造方法只是在创建对象的时候赋值,如果我们已经有一个存在的对象了,想对这个存在的对象赋值, 这个时候,如果你还使用构造方法传值的形式传值, 那么就创建了一个新的对象,并不是这个已存在的对象了。所以我们要对私有的属性做一些可以被外部存取的接口,目的就是可以在对象存在的情况下,改变和存取属性的值,但要注意,只有需要让外部改变的属性才这样做,不想让外面访问的属性是不做这样的接口的,这样就能达到封装的目的,所有的功能都是对象自己来完成,给外面提供尽量少的操作。
如果给类外部提供接口,可以为私有属性在类外部提供设置方法和获取方法,来操作私有属性.例如:
prvate $age; //私有的属性年龄
function setAge($age) //为外部提供一个公有设置年龄的方法
{
if($age<0 || $age>130) //在给属性赋值的时候,为了避免非法值设置给属性
return;
$this->age=$age;
}
function getAge() //为外部提供一个公有获取年龄的方法
{
return($this->age);
}
上面的方法是为一个成员属性设置和获取值, 当然你也可以为每个属性用同样的方法对其进行赋值和取值的操作,完成在类外部的存取工作。
10.__set() __get() __isset() __unset()四个方法的应用
一般来说,总是把类的属性定义为private,这更符合现实的逻辑。但是, 对属性的读取和赋值操作是非常频繁的,因此在PHP5中,预定义了两个函数”__get()”和”__set()”来获取和赋值其属性,以及检查属性的”__isset()”和删除属性的方法”__unset()”。
上一节中,我们为每个属性做了设置和获取的方法,在PHP5中给我们提供了专门为属性设置值和获取值的方法,”__set()”和“__get()”这两个方法,这两个方法不是默认存在的, 而是我们手工添加到类里面去的,像构造方法(__construct())一样, 类里面添加了才会存在,可以按下面的方式来添加这两个方法,当然也可以按个人的风格来添加:
//__get()方法用来获取私有属性
function __get($property_name)
{
if(isset($this->$property_name)) {
return($this->$property_name);
}else {
return(NULL);
}
}
//__set()方法用来设置私有属性
function __set($property_name, $value)
{
$this->$property_name = $value;
}
__get()方法:这个方法用来获取私有成员属性值的,有一个参数, 参数传入你要获取的成员属性的名称,返回获取的属性值, 这个方法不用我们手工的去调用, 是在直接获取私有属性的时候自动调用的。因为私有属性已经被封装上了,是不能直接获取值的(比如:”echo $p1->name” 这样直接获取是错误的),但是如果你在类里面加上了这个方法,在使用”echo $p1->name” 这样的语句直接获取值的时候就会自动调用__get($property_name)方法,将属性name传给参数$property_name,通过这个方法的内部执行,返回我们传入的私有属性的值。
__set()方法:这个方法用来为私有成员属性设置值的,有两个参数,第一个参数为你要为设置值的属性名,第二个参数是要给属性设置的值,没有返回值。这个方法同样不用我们手工去调用,是在直接设置私有属性值的时候自动调用的,同样属性私有的已经被封装上了, 如果没有__set()这个方法,是不允许的, 比如:”$this->name=’zhangsan’ , 这样会出错,但是如果你在类里面加上了__set($property_name, $value)这个方法,在直接给私有属性赋值的时候,就会自动调用它,把属性比如name传给$property_name, 把要赋的值”zhangsan”传给$value,通过这个方法的执行,达到赋值的目的, 为了不传入非法的值, 还可以在这个方法给做一下判断。代码如下:
<?php
class Person
{
//下面是人的成员属性, 都是封装的私有成员
private $name; //人的名子
private $sex; //人的性别
private $age; //人的年龄
//__get()方法用来获取私有属性
function __get($property_name)
{
echo "在直接获取私有属性值的时候,自动调用了这个__get()方法<br>";
if(isset($this->$property_name)) {
return($this->$property_name);
}else {
return(NULL);
}
}
//__set()方法用来设置私有属性
function __set($property_name, $value)
{
echo "在直接设置私有属性值的时候,自动调用了这个__set()方法为私有属性赋值<br>";
$this->$property_name = $value;
}
}
$p1=new Person();
//直接为私有属性赋值的操作, 会自动调用__set()方法进行赋值
$p1->name="张三";
$p1->sex="男";
$p1->age=20;
//直接获取私有属性的值, 会自动调用__get()方法,返回成员属性的值
echo "姓名:".$p1->name."<br>";
echo "性别:".$p1->sex."<br>";
echo "年龄:".$p1->age."<br>";
?>
程序执行结果:
在直接设置私有属性值的时候, 自动调用了这个__set()方法为私有属性赋值
在直接设置私有属性值的时候, 自动调用了这个__set()方法为私有属性赋值
在直接设置私有属性值的时候, 自动调用了这个__set()方法为私有属性赋值
在直接获取私有属性值的时候,自动调用了这个__get()方法
姓名:张三
在直接获取私有属性值的时候,自动调用了这个__get()方法
性别:男
在直接获取私有属性值的时候,自动调用了这个__get()方法
年龄:20
以上代码如果不加上__get()和__set()方法,程序就会出错,因为不能在类的外部操作私有成员,而上面的代码是通过自动调用__get()和__set()方法来帮助我们直接存取封装的私有成员的。
__isset() 方法:
__unset()方法:
11.类的继承
继承作为面向对象的三个重要特性的一个方面,在面向对象的领域有着及其重要的作用,好像没听说哪个面向对象的语言不支持继承。 继承是php5面象对象程序设计的重要特性之一,它是指建立一个新的派生类,从一个或多个先前定义的类中继承数据和函数,而且可以重新定义或加进新数据和函数,从而建立了类的层次或等级。说的简单点就是,继承性是子类自动共享父类数据结构和方法的机制,这是类之间的一种关系。在定义和实现一个类的时候,可以在一个已经存在的类的基础之上来进行,把这个已经存在的类所定义的内容作为自己的内容,并加入若干新的内容。比如你现在已经有一个“人”这个类了,这个类里面有两个成员属性“姓名和年龄”以及还有两个成员方法“说话的方法和走路的方法“,
如果现在程序需要一个学生的类, 因为学生的也是人, 所以学生也有成员属性“姓名和年龄”以及成员方法“说话的方法和走路的方法“,这个时候你就可以让学生类来继承人这个类, 继承之后,学生类就会把人类里面的所有的属性都继承过来, 就不用你再去重新声明一遍这些成员属性和方法了, 因为学生类里面还有所在学校的属性和学习的方法,所以在你做的学生类里面有继承自人类里面的属性和方法之外在加上学生特有的”所在学校属性“和”学习的方法“, 这样一个学生类就声明完成了, 继函我们也可以叫作“扩展”, 从上面我们就可以看出,学生类对人类进行了扩展,
在人类里原有两个属性和两个方法的基础上加上一个属性和一个方法扩展出来一个新的学生类。
通过继承机制,可以利用已有的数据类型来定义新的数据类型。所定义的新的数据类型不仅拥有新定义的成员,而且还同时拥有旧的成员。我们称已存在的用来派生新类的类为基类,又称为父类以及超类。由已存在的类派生出的新类称为派生类,又称为子类。
在软件开发中,类的继承性使所建立的软件具有开放性、可扩充性,这是信息组织与分类的行之有效的方法,它简化了对象、类的创建工作量,增加了代码的可重性。采用继承性,提供了类的规范的等级结构。通过类的继承关系,使公共的特性能够共享,提高了软件的重用性。
在C++语言中,一个派生类可以从一个基类派生,也可以从多个基类派生。从一个基类派生的继承称为单继承;从多个基类派生的继承称为多继承。
但是在PHP和Java语言里面没有多继承,只有单继承,也就是说,一个类只能直接从一个类中继承数据, 这就是我们所说的单继承。
例如:
下面是“人”类的抽象
//定义一个“人”类做为父类
class Person
{
//下面是人的成员属性
var $name; //人的名子
var $sex; //人的性别
var $age; //人的年龄
//定义一个构造方法参数为属性姓名$name、性别$sex和年龄$age进行赋值
function __construct($name, $sex, $age)
{
$this->name=$name;
$this->sex=$sex;
$this->age=$age;
}
//这个人可以说话的方法, 说出自己的属性
function say()
{
echo "我的名子叫:".$this->name." 性别:".$this->sex." 我的年龄是:".$this->age."<br>";
}
}
下面我们做一个”学生类”,如果不是用继承如下:
//定义一个“人”类做为父类
class Student
{
//下面是人的成员属性
var $name; //人的名子
var $sex; //人的性别
var $age; //人的年龄
var $school; //学生所在学校的属性
//定义一个构造方法参数为属性姓名$name、性别$sex和年龄$age进行赋值
function __construct($name=””, $sex=””, $age=””, $school=””)
{
$this->name=$name;
$this->sex=$sex;
$this->age=$age;
$this->school=$school;
}
//这个人可以说话的方法, 说出自己的属性
function say()
{
echo "我的名子叫:".$this->name." 性别:".$this->sex." 我的年龄是:".$this->age."<br>";
}
//这个学生学习的方法
function study()
{
echo "我的名子叫:".$this->name." 我正在”.$this->school.” 学习<br>";
}
}
//定义一个子类“学生类“使用”extends”关键字来继承”人”类
class Student extends Person
{
var $school; //学生所在学校的属性
//这个学生学习的方法
function study()
{
echo "我的名子叫:".$this->name." 我正在”.$this->school.” 学习<br>";
}
}
通过上面“Student“类的定义, Student类通过使用”extends”这个关键字把Person 类里的所有成员属性和成员方法都继承过来了, 并扩展了一个所在学校成员属性”school”,和一个学习方法“study()”. 现在子类”Student”里面和使用这个类实例出来的对象都具有如下的属性和方法:
学生类”Student”里面的成员属性有:
姓名:name;
年龄:age;
性别:sex;
学校:school;
学生类”Student”里面的成员方法有:
说话方法:say();
学习方法:study();
通过上面类继承的使用简化了对象、类的创建工作量,增加了代码的可重性。但是从上面这一个例子上中“可重用性”以及其它的继承性所带来的影响,我们看的还不是特别的明显,你扩展的去想一下,人有无数个岗位,比如上面的学生还有老师,工程师, 医生,工人等,很多很多,如果每个类都定义“人“都共同具有的属性和方法, 想一想会有很大的工作量, 这些属性和方法都可以从“Person”人类里面继承过来。
12.重载新的方法
在学习PHP 这种语言中你会发现, PHP中的方法是不能重载的, 所谓的方法重载就是定义相同的方法名,通过“参数的个数“不同或“参数的类型“不同,来访问我们的相同方法名的不同方法。但是因为PHP是弱类型的语言, 所以在方法的参数中本身就可以接收不同类型的数据,又因为PHP的方法可以接收不定个数的参数,所以通过传递不同个数的参数调用不相同方法名的不同方法也是不成立的。所以在PHP里面没有方法重载。不能重载也就是在你的项目中不能定义相同方法名的方法。另外,因为PHP没有名子空间的概念,在同一个页面和被包含的页面中不能定义相同名称的方法, 也不能定义和PHP给我提供的方法的方法重名,当然在同一个类中也不能定义相同名称的方法。
我们这里所指的重载新的方法所指的是什么呢?其实我们所说的重载新的方法就是子类覆盖父类的已有的方法,那为什么要这么做呢?父类的方法不是可以继承过来直接用吗?但有一些情况是我们必须要覆盖的,比如说我们前面提到过的例子里面, “Person”这个人类里面有一个“说话”的方法,所有继承“Person”类的子类都是可以“说话”的,我们“Student”类就是“Person”类的子类,所以“Student”的实例就可以“说话“了, 但是人类里面“说话”的方法里面说出的是“Person”类里面的属性, 而“Student”类对“Person”类进行了扩展,又扩展出了几个新的属性,如果使用继承过来的“say()”说话方法的话,只能说出从“Person”类继承过来的那些属性,那么新扩展的那些属性使用这个继承过来的“say()”的方法就说不出来了,那有的人就问了,我在“Student”这个子类中再定义一个新的方法用于说话,说出子类里面所有的属性不就行了吗?一定不要这么做, 从抽象的角度来讲, 一个“学生”不能有两种“说话”的方法,就算你定义了两个不同的说话的方法,可以实现你想要的功能,被继承过来的那个“说话“方法可能没有机会用到了,而且是继承过来的你也删不掉。这个时候我们就要用到覆盖了。
虽然说在PHP里面不能定义同名的方法, 但是在父子关系的两个类中,我们可以在子类中定义和父类同名的方法,这样就把父类中继承过来的方法覆盖掉了。
<?
//定义一个“人”类做为父类
class Person
{
//下面是人的成员属性
var $name; //人的名子
var $sex; //人的性别
var $age; //人的年龄
//定义一个构造方法参数为属性姓名$name、性别$sex和年龄$age进行赋值
function __construct($name, $sex, $age)
{
$this->name=$name;
$this->sex=$sex;
$this->age=$age;
}
//这个人可以说话的方法, 说出自己的属性
function say()
{
echo "我的名子叫:".$this->name." 性别:".$this->sex." 我的年龄是:".$this->age."<br>";
}
}
class Student extends Person
{
var $school; //学生所在学校的属性
//这个学生学习的方法
function study()
{
echo "我的名子叫:".$this->name." 我正在”.$this->school.” 学习<br>";
}
//这个学性可以说话的方法, 说出自己所有的属性,覆盖了父类的同名方法
function say()
{
echo "我的名子叫:".$this->name." 性别:".$this->sex." 我的年龄是:".$this->age."我在".$this->school."上学.<br>";
}
}
?>
上面的例子, 我们就在“Student”子类里覆盖了继承父类里面的”say()”的方法,通过覆盖我们就实现了对“方法”扩展。
但是,像这样做虽然解决了我们上面说的问题,但是在实际开发中,一个方法不可能就一条代码或是几条代码,比如说“Person”类里面的“say()”方法有里面有100条代码,如果我们想对这个方法覆盖保留原有的功能外加上一点点功能,就要把原有的100条代码重写一次, 再加上扩展的几条代码,这还算是好的,而有的情况,父类中的方法是看不见原代码的,这个时候你怎么去重写原有的代码呢?我们也有解决的办法,就是在子类这个方法中可以调用到父类中被覆盖的方法, 也就是把被覆盖的方法原有的功能拿过来再加上自己的一点功能,可以通过两种方法实现在子类的方法中调用父类被覆盖的方法:
一种是使用父类的“类名::“来调用父类中被覆盖的方法;
一种是使用“parent::”的方试来调用父类中被覆盖的方法;
class Student extends Person
{
var $school; //学生所在学校的属性
//这个学生学习的方法
function study()
{
echo "我的名子叫:".$this->name." 我正在”.$this->school.” 学习<br>";
}
//这个学性可以说话的方法, 说出自己所有的属性,覆盖了父类的同名方法
function say()
{
//使用父类的“类名::“来调用父类中被覆盖的方法;
// Person::say();
//或者使用“parent::”的方试来调用父类中被覆盖的方法;
parent::say();
//加上一点自己的功能
echo “我的年龄是:".$this->age."我在".$this->school."上学.<br>";
}
}
现在用两种方式都可以访问到父类中被覆盖的方法,我们选那种方式最好呢?用户可能会发现自己写的代码访问了父类的变量和函数。如果子类非常精炼或者父类非常专业化的时候尤其是这样。 不要用代码中父类文字上的名字,应该用特殊的名字 parent,它指的就是子类在 extends 声明中所指的父类的名字。这样做可以避免在多个地方使用父类的名字。如果继承树在实现的过程中要修改,只要简单地修改类中 extends 声明的部分。
同样,构造方法在子类中如果没有声明的话,也可以使用父类中的构造方法,如果子类中重新定义了一个构造方法也会覆盖掉父类中的构造方法,如果想使用新的构造方法为所有属性赋值也可以用同样的方式。
class Student extends Person
{
var $school; //学生所在学校的属性
function __construct($name, $sex, $age, $school)
{
//使用父类中的方法为原有的属性赋值
parent::__construct($name, $sex, $age);
$this->school=$school;
}
//这个学生学习的方法
function study()
{
echo "我的名子叫:".$this->name." 我正在”.$this->school.” 学习<br>";
}
//这个人可以说话的方法, 说出自己的属性
function say()
{
parent::say();
//加上一点自己的功能
echo “我的年龄是:".$this->age."我在".$this->school."上学.<br>";
}
}
13.访问类型
类型的访问修饰符允许开发人员对类成员的访问进行限制,这是PHP5的新特性,但却是OOP语言的一个好的特性。而且大多数OOP语言都已支持此特性。PHP5支持如下3种访问修饰符
public (公有的、默认的),private (私有的)和protected (受保护的)三种.
public 公有修饰符,类中的成员将没有访问限制,所有的外部成员都可以访问(读和写)这个类成员(包括成员属性和成员方法),在PHP5之前的所有版本中,PHP中类的成员都是public的, 而且在PHP5中如果类的成员没有指定成员访问修饰符,将被视为public 。
例:public $name;
public function say(){};
private 私有修改符,被定义为private的成员,对于同一个类里的所有成员是可见的,即是没有访问限制;但对于该类的外部代码是不允许改变甚至读操作,对于该类的子类,也不能访问private修饰的成员。
例: private $var1 = ‘A’; //属性
private function getValue(){} //函数
protected保护成员修饰符,被修饰为protected的成员不能被该类的外部代码访问。但是对于该类的子类有访问权限,可以进行属性、方法的读及写操作,该子类的外部代码包括其的子类都不具有访问其属性和方法的权限。
例:protected $name;
protected function say(){};
private |
protected |
public |
|
同一个类中 |
√ |
√ |
√ |
类的子类中 |
√ |
√ |
|
所有的外部成员 |
√ |
<?php
/**
* Define MyClass
*/
class MyClass
{
public $public = ‘Public‘;
protected $protected = ‘Protected‘;
private $private = ‘Private‘;
function printHello()
{
echo $this->public;
echo $this->protected;
echo $this->private;
}
}
$obj = new MyClass();
echo $obj->public; // Works
echo $obj->protected; // Fatal Error
echo $obj->private; // Fatal Error
$obj->printHello(); // Shows Public, Protected and Private
/**
* Define MyClass2
*/
class MyClass2 extends MyClass
{
// We can redeclare the public and protected method, but not private
protected $protected = ‘Protected2‘;
function printHello()
{
echo $this->public;
echo $this->protected;
echo $this->private;
}
}
$obj2 = new MyClass2();
echo $obj->public; // Works
echo $obj2->private; // Undefined
echo $obj2->protected; // Fatal Error
$obj2->printHello(); // Shows Public, Protected2, not Private
?>
<?php
/**
* Define MyClass
*/
class MyClass
{
// Contructors must be public
public function __construct() { }
// Declare a public method
public function MyPublic() { }
// Declare a protected method
protected function MyProtected() { }
// Declare a private method
private function MyPrivate() { }
// This is public
function Foo()
{
$this->MyPublic();
$this->MyProtected();
$this->MyPrivate();
}
}
$myclass = new MyClass;
$myclass->MyPublic(); // Works
$myclass->MyProtected(); // Fatal Error
$myclass->MyPrivate(); // Fatal Error
$myclass->Foo(); // Public, Protected and Private work
/**
* Define MyClass2
*/
class MyClass2 extends MyClass
{
// This is public
function Foo2()
{
$this->MyPublic();
$this->MyProtected();
$this->MyPrivate(); // Fatal Error
}
}
$myclass2 = new MyClass2;
$myclass2->MyPublic(); // Works
$myclass2->Foo2(); // Public and Protected work, not Private
?>
另外在子类覆盖父类的方法时也要注意一点,子类中方法的访问权限一定不能低于父类被覆盖方法的访问权限,也就是一定要高于或等于父类方法的访问权限。
例如,如果父类方法的访问权限是protected那么子类中要覆盖的权限就要是protected和public,如果父类的方法是public那么子类中要覆盖的方法只能也是public,总之子类中的方法总是要高于或等于父类被覆盖方法的访问权限。
14.final关键字的应用
这个关键字只能用来定义类和定义方法, 不能使用final这个关键字来定义成员属性,因为final是常量的意思,我们在PHP里定义常量使用的是define()函数,所以不能使用final来定义成员属性。
使用final关键标记的类不能被继承;
final class Person
{
……
}
class Student extends Person
{
}
会出现下面错误:
Fatal error: Class Student may not inherit from final class (Person)
使用final关键标记的方法不能被子类覆盖,是最终版本;
class Person
{
final function say()
{
}
}
class Student extends Person
{
function say()
{
}
}
会出现下面错误:
Fatal error: Cannot override final method Person::say()
15.static和const关键字的使用
Static关键字是在类中描述成员属性和成员方法是静态的;静态的成员好处在那里呢?前面我们声明了“Person”的人类,在”Person”这个类里如果我们加上一个“人所属国家”的属性,这样用”Person”这个类实例化出几百个或者更多个实例对象,每个对象里面就都有“所属国家”的属性了,如果开发的项目就是为中国人而开发的,那么每个对象里面就都有一个国家的属性是“中国“其它的属性是不同的,如果我们把“国家”的属性做成静态的成员,这样国家的属性在内存中就只有一个,而让这几百个或更多的对象共用这一个属性,static成员能够限制外部的访问,因为static的成员是属于类的,是不属于任何对象实例,是在类第一次被加载的时候分配的空间,其他类是无法访问的,只对类的实例共享,能一定程度对类该成员形成保护;
从内存的角度我们来分析一下,内存从逻辑上被分为四段,其中对象是放在“堆内存”里面,对象的引用被放到了“栈内存“里,而静态成员则放到了“初始化静态段”,在类第一次被加载的时候放入的,可以让堆内存里面的每个对象所共享,如下图;
类的静态变量,非常类似全局变量,能够被所有类的实例共享,类的静态方法也是一样的,类似于全局函数。
<?
class Person
{
//下面是人的静态成员属性
public static $myCountry="中国";
// var $name; //人的名子
//这是人的静态成员方法
public static function say()
{
echo "我是中国人<br>";
}
}
//输出静态属性
echo Person::$myCountry;
//访问静态方法
Person::say();
//重新给静态属性赋值
Person::$myCountry="美国";
echo Person::$myCountry;
?>
因为静态成员是在类第一次加载的时候就创建的,所以在类的外部不需要对象而使用类名就可以访问的到静态的成员;上面说过,静态成员被这个类的每个实例对象所共享,那么我们使用对象可不可以访问类中的静态成员呢?从上图中我们可以看到,静态的成员不是在每个对象内部存在的,但是每个对象都可以共享,所以我们如果使用对象访问成员的话就会出现没有这个属性定义,使用对象访问不到静态成员的,在其它的面向对象的语言中,比如Java是可以使用对象的方式访问静态成员的,如果PHP中可以使用对象访问静态成员的话,我们也尽量不要去使用,因为静态的成员我们在做项目的时候目的就是使用类名去访问。
类里面的静态方法只能访问类的静态的属性,在类里面的静态方法是不能访问类的非静态成员的,原因很简单,我们要想在本类的方法中访问本类的其它成员,我们需要使用$this这个引用,而$this这个引用指针是代表调用此方法的对象,我们说了静态的方法是不用对象调用的,而是使用类名来访问,所以根本就没有对象存在,也就没有$this这个引用了,没有了$this这个引用就不能访问类里面的非静态成员,又因为类里面的静态成员是可以不用对象来访问的,所以类里面的静态方法只能访问类的静态的属性,即然$this不存在,在静态方法中访其它静态成员我们使用的是一个特殊的类”self”;
self和$this相似,只不过self是代表这个静态方法所在的类。所以在静态方法里,可以使用这个方法所在的类的“类名“,也可以使用“self“来访问其它静态成员,如果没有特殊情况的话,我们通常使用后者,即”self::成员属性”的方式。
<?
class Person
{
//下面是人的静态成员属性
public static $myCountry="中国";
//这是人的静态成员方法, 通过self访问其它静态成员
public static function say()
{
echo "我是".self::$myCountry."<br>";
}
}
//访问静态方法
Person::say();
?>
在非静态方法里可不可以访问静态成员呢,当然也是可以的了,但是也不能使用”$this”引用也要使用类名或是”self::成员属性的形式”。
const是一个定义常量的关键字,在PHP中定义常量使用的是”define()”这个函数,但是在类里面定义常量使用的是”const”这个关键字,类似于C中的#define如果在程序中改变了它的值,那么会出现错误,用”const”修饰的成员属性的访问方式和”static”修饰的成员访问的方式差不多,也是使用”类名”,在方法里面使用”self”关键字。但是不用使用”$”符号,也不能使用对象来访问。
<?php
class MyClass
{
//定义一个常量constant
const constant = ‘constant value‘;
function showConstant() {
echo self::constant . "/n"; //使用self访问,不要加”$”
}
}
echo MyClass::constant . "/n"; //使用类名来访问,也不加”$”
$class = new MyClass();
$class->showConstant();
// echo $class::constant; 是不允许的
?>
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 语句).
<?php
// Declare a simple class
class TestClass
{
public $foo;
public function __construct($foo) {
$this->foo = $foo;
}
//定义一个__toString方法,返加一个成员属性$foo
public function __toString() {
return $this->foo;
}
}
$class = new TestClass(‘Hello‘);
//直接输出对象
echo $class;
?>
上例输出:Hello
17.克隆对象
有的时候我们需要在一个项目里面,使用两个或多个一样的对象,如果你使用“new”关键字重新创建对象的话,再赋值上相同的属性,这样做比较烦琐而且也容易出错,所以要根据一个对象完全克隆出一个一模一样的对象,是非常有必要的,而且克隆以后,两个对象互不干扰。
在PHP5中我们使用”clone”这个关键字克隆对象;
<?
class Person
{
//下面是人的成员属性
var $name; //人的名子
var $sex; //人的性别
var $age; //人的年龄
//定义一个构造方法参数为属性姓名$name、性别$sex和年龄$age进行赋值
function __construct($name="", $sex="", $age="")
{
$this->name=$name;
$this->sex=$sex;
$this->age=$age;
}
//这个人可以说话的方法, 说出自己的属性
function say()
{
echo "我的名子叫:".$this->name." 性别:".$this->sex." 我的年龄是:".$this->age."<br>";
}
}
$p1=new Person("张三", "男", 20);
//使用“clone”克隆新对象p2,和p1对象具有相同的属性和方法。
$p2=clone $p1;
$p2->say();
?>
PHP5定义了一个特殊的方法名“__clone()”方法,是在对象克隆时自动调用的方法,用“__clone()”方法将建立一个与原对象拥有相同属性和方法的对象,如果想在克隆后改变原对象的内容,需要在__clone()中重写原本的属性和方法, ”__clone()”方法可以没有参数,它自动包含$this和$that两个指针,$this指向复本,而$that指向原本;
class Person
{
//下面是人的成员属性
var $name; //人的名子
var $sex; //人的性别
var $age; //人的年龄
//定义一个构造方法参数为属性姓名$name、性别$sex和年龄$age进行赋值
function __construct($name="", $sex="", $age="")
{
$this->name=$name;
$this->sex=$sex;
$this->age=$age;
}
//这个人可以说话的方法, 说出自己的属性
function say()
{
echo "我的名子叫:".$this->name." 性别:".$this->sex." 我的年龄是:".$this->age."<br>";
}
//对象克隆时自动调用的方法, 如果想在克隆后改变原对象的内容,需要在__clone()中重写原本的属性和方法
function __clone()
{
//$this指的复本p2, 而$that是指向原本p1,这样就在本方法里,改变了复本的属性。
$this->name="我是假的$that->name";
$this->age=30;
}
}
$p1=new Person("张三", "男", 20);
$p2=clone $p1;
$p1->say();
$p2->say();
?>
上例输出:
我的名子叫:张三 性别:男 我的年龄是:20
我的名子叫:我是假的张三 性别:男 我的年龄是:30
18.__call处理调用错误
在程序开发中,如果在使用对象调用对象内部方法时候,调用的这个方法不存在那么程序就会出错,然后程序退出不能继续执行。那么可不可以在程序调用对象内部不存在的方法时,提示我们调用的方法及使用的参数不存在,但程序还可以继续执行,这个时候我们就要使用在调用不存在的方法时自动调用的方法”__call()”.
<?php
//这是一个测试的类,里面没有属性和方法
class Test
{
}
//产生一个Test类的对象
$test=new Test();
//调用对象里不存在的方法
$test->demo("one", "two", "three");
//程序不会执行到这里
echo "this is a test<br>";
?>
上例出现如下错误,程序通出不能继续执行;
Fatal error: Call to undefined method Test::demo()
下面我们加上“__call()”方法,这个方法有2个参数,第一个参数为调用不存在的方法过程中,自动调用__call()方法时,把这个不存在的方法的方法名传给第一个参数,第二个参数则是把这个方法的多个参数以数组的形式传进来。
<?php
//这是一个测试的类,里面没有属性和方法
class Test
{
//调用不存的方法时自动调用的方法,第一个参数为方法名,第二个参数是数组参数
function __call($function_name, $args)
{
print "你所调用的函数:$function_name(参数:";
print_r($args);
print ")不存在!<br>/n";
}
}
//产生一个Test类的对象
$test=new Test();
//调用对象里不存在的方法
$test->demo("one", "two", "three");
//程序不会退出可以执行到这里
echo "this is a test<br>";
?>
上例输出结果为:
你所调用的函数: demo(参数:Array ( [0] => one [1] => two [2] => three ) )不存在!
this is a test.
19.抽象方法和抽象类
在OOP语言中,一个类可以有一个或多个子类,而每个类都有至少一个公有方法做为外部代码访问其的接口。而抽象方法就是为了方便继承而引入的,我们先来看一下抽象类和抽象方法的定义再说明它的用途.
什么是抽象方法?我们在类里面定义的没有方法体的方法就是抽象方法,所谓的没有方法体指的是,在方法声明的时候没有大括号以及其中的内容,而是直接在声明时在方法名后加上分号结束,另外在声明抽象方法时还要加一个关键字“abstract”来修饰;
例如:
abstract function fun1();
abstract function fun2();
上例是就是“abstract”修饰的没有方法体的抽象方法“fun1()”和“fun2()”,不要忘记抽象方法后面还要有一个分号;那么什么是抽象类呢?只要一个类里面有一个方法是抽象方法,那么这个类就要定义为抽象类,抽象类也要使用“abstract”关键字来修饰;在抽象类里面可以有不是抽象的方法和成员属性,但只要有一个方法是抽象的方法,这个类就必须声明为抽象类,使用”abstract”来修饰。
例如:
abstract class Demo
{
var $test;
abstract function fun1();
abstract function fun2();
function fun3()
{
….
}
}
上例中定义了一个抽象类“Demo”使用了”abstract”来修饰, 在这个类里面定义了一个成员属性“$test”,和两个抽象方法“fun1”和“fun2”还有一个非抽象的方法fun3();那么抽象类我们怎么使用呢?最重要的一点就是抽象类不能产生实例对象,所以也不能直接使用,前面我们多次提到过类不能直接使用,我们使用的是通过类实例化出来的对象,那么抽象类不能产生实例对象我们声明抽象类有什么用呢?我们是将抽象方法是做为子类重载的模板使用的,定义抽象类就相当于定义了一种规范,这种规范要求子类去遵守,子类继函抽象类之后,把抽象类里面的抽象方法按照子类的需要实现。子类必须把父类中的抽象方法全部都实现,否则子类中还存在抽象方法,那么子类还是抽象类,还是不能实例化对;为什么我们非要从抽象类中继承呢?因为有的时候我们要实现一些功能就必须从抽象类中继承,否则这些功能你就实现不了,如果继承了抽象类,就要实现类其中的抽象方法;
<?
abstract class Demo
{
var $test;
abstract function fun1();
abstract function fun2();
function fun3()
{
….
}
}
$demo=new Demo(); //抽象类为能产生实例对象,所以这样做是错的,实例化对象交给子类
class Test extends Demo
{
function fun1()
{
…
}
function fun2()
{
…
}
}
$test=new Test(); //子类可以实例化对象,因为实现了父类中所有抽象方法
?>
20. php5接口技术
PHP与大多数面向对象编程语言一样,不支持多重继承.也就是说每个类只能继承一个父类.为了解决这个问题,PHP引入了接口,接口的思想是指定了一个实现了该接口的类必须实现的一系列方法.接口是一种特殊的抽象类,抽象类又是一种特殊的类,所以接口也是一种特殊的类,为什么说接口是一种特殊的抽象类呢?如果一个抽象类里面的所有的方法都是抽象方法,那么我们就换一种声明方法使用“接口“;也就是说接口里面所有的方法必须都是声明为抽象方法,另外接口里面不能声明变量,而且接口里面所有的成员都是public权限的。所以子类在实现的时候也一定要使用public权限实限。
声明一个类的时候我们使用的关键字是”class”,而接口一种特殊的类,使用的关键字是“interface”;
类的定义: class 类名{ … } ,接口的声明:interface 接口名{ …}
<?php
//定义一个接口使用interface关键字,“One”为接口名称
interface One
{
//定义一个常量
const constant = ‘constant value‘;
//定义了一个抽象方法”fun1”
public function fun1();
//定义了抽象方法”fun2”
public function fun2();
}
?>
上例中定义了一个接口”one”,里面声明了两个抽象方法“fun1”和”fun2”,因为接口里面所有的方法都是抽象方法,所以在声明抽象方法的时候就不用像抽象类那样使用”abstract”这个关键字了,默认的已经加上这个关键字,另外在接口里边的”public”这个访问权限也可以去掉,因为默认就是public的,因为接口里所有成员都要是公有的,所在对于接口里面的成员我们就不能使用“private”的和”protected”的权限了,都要用public或是默认的。另外在接口里面我们也声明了一个常量“constant“, 因为在接口里面不能用变量成员,所以我们要使用const这个关键字声明。
因为接口是一种特殊的抽象类,里面所有的方法都是抽象方法,所以接口也不能产生实例对象; 它也做为一种规范,所有抽象方法需要子类去实现。
我们可以使用”extends”关键字让一个接口去继承另一个接口;
<?php
//使用”extends”继承另外一个接口
interface Two extends One
{
function fun3();
function fun4();
}
?>
而我们定义一接口的子类去实现接口中全部抽象方法使用的关键字是”implements”,而不是我们前面所说的”extends”;
<?php
//使用“implements”这个关键字去实现接口中的抽象方法
class Three implements One
{
function fun1()
{
….
}
function fun2()
{
….
}
}
//实现了全部方法,我们去可以使用子类去实例化对象了
$three=new Three();
?>
我们也可以使用抽象类,去实现接口中的部分抽象方法,但要想实例化对象,这个抽象类还要有子类把它所有的抽象方法都实现才行;
在前面我们说过,PHP是单继承的,一个类只能有一父类,但是一个类可以实现多个接口,就相当于一个类要遵守多个规范,就像我们不仅要遵守国家的法律,如果是在学校的话,还要遵守学校的校规一样;
<?php
//使用implements实现多个接口
class Four implemtns 接口一, 接口二, ….
{
//必须把所有接口中的方法都要实现才可以实例化对象。
}
?>
PHP中不仅一个类可以实现多个接口,也可以在继承一个类的同时实现多个接口, 一定要先继承类再去实现接口;
<?php
//使用extends继承一个类,使用implements实现多个接口
class Four extends 类名一 implemtns 接口一, 接口二, ….
{
//所有接口中的方法都要实现才可以实例化对象
………..
}
?>
21.多态的应用
多态是除封装和继承之外的另一个面象对象的三大特性之一,我个人看来PHP中虽然可以实现多态,但和c++还有Java这些面向对象的语言相比,多态性并不是那么突出,因为PHP本身就是一种弱类型的语言,不存在父类对象转化为子类对象或者是子类对象转化为父类对象的问题,所以多态的应用并不是那么的明显;所谓多态性是指一段程序能够处理多种类型对象的能力,比如说在公司上班,每个月财务发放工资,同一个发工资的方法,在公司内不同的员工或是不同职位的员工,都是通过这个方法发放的,但是所发的工资都是不相同的。所以同一个发工资的方法就出现了多种形态。对于面向对象的程序来说,多态就是把子类对象赋值给父类引用,然后调用父类的方法,去执行子类覆盖父类的那个方法,但在PHP里是弱类型的,对象引用都是一样的不分父类引用,还是子类引用。
我们现在来看一个例子,首先还是要使用多态就要有父类对象和子类对象的关系。做一个形状的接口或是抽象类做为父类,里面有两个抽象方法,一个求周长的方法,另一个是求面积的方法;这接口的子类是多种不同的形状,每个形状又都有周长和面积,又因为父类是一个接口,所以子类里面就必须要实现父类的这两个周长和面积的抽象方法,这样做的目的是每种不同形状的子类都遵守父类接口的规范,都要有求周长和求面积的方法。
<?
//定义了一个形状的接口,里面有两个抽象方法让子类去实现
interface Shape
{
function area();
function perimeter();
}
//定义了一个矩形子类实现了形状接口中的周长和面积
class Rect implements Shape
{
private $width;
private $height;
function __construct($width, $height)
{
$this->width=$width;
$this->height=$height;
}
function area()
{
return "矩形的面积是:".($this->width*$this->height);
}
function perimeter()
{
return "矩形的周长是:".(2*($this->width+$this->height));
}
}
//定义了一个圆形子类实现了形状接口中的周长和面积
class Circular implements Shape
{
private $radius;
function __construct($radius)
{
$this->radius=$radius;
}
function area()
{
return "圆形的面积是:".(3.14*$this->radius*$this->radius);
}
function perimeter()
{
return "圆形的周长是:".(2*3.14*$this->radius);
}
}
//把子类矩形对象赋给形状的一个引用
$shape=new Rect(5, 10);
echo $shape->area()."<br>";
echo $shape->perimeter()."<br>";
//把子类圆形对象赋给形状的一个引用
$shape=new Circular(10);
echo $shape->area()."<br>";
echo $shape->perimeter()."<br>";
?>
上例执行结果:
矩形的面积是:50
矩形的周长是:30
圆形的面积是:314
圆形的周长是:62.8
通过上例我们看到,把矩形对象和圆形对象分别赋给了变量$shape, 调用$shape引用中的面积和周长的方法,出现了不同的结果,这就是一种多态的应用,其实在我们PHP这种弱类形的面向对象的语言里面,多态的特性并不是特别的明显,其实就是对象类型变量的变项应用。
22.把对象串行化
有时候需要把一个对象在网络上传输,为了方便传输,可以把整个对象转化为二进制串,等到达另一端时,再还原为原来的对象,这个过程称之为串行化, 就像我们现在想把一辆汽车通过轮船运到美国去,因为汽车的体积比较大,我们可以把汽车拆开成小的部件,然后我们把这些部件通过轮般运到美国去,到了美国再把这些部件组装回汽车。
有两种情况我们必须把对象串行化,第一种情况就是把一个对象在网络中传输的时候要将对象串行化,第二种情况就是把对象写入文件或是数据库的时候用到串行化。
串行化有两个过程,一个是串行化,就是把对象转化为二进制的字符串,我们使用serialize()函数来串行化一个对象,另一个是反串行化,就是把对象转化的二进制字符串再转化为对象, 我们使用unserialize()函数来反串行化一个对象.
PHP中serialize()函数的参数为对象名,返回值为一个字符串,Serialize()返回的字符串含义模糊,一般我们不会解析这个串来得到对象的信息,我们只要把返回来的这个字符串传到网络另一端或是保存到方件中即可。
PHP中unserialize()函数来反串行化对象,这个函数的参数即为serialize()函数的返回值,输出当然是重新组织好的对象.
<?
class Person
{
//下面是人的成员属性
var $name; //人的名子
var $sex; //人的性别
var $age; //人的年龄
//定义一个构造方法参数为属性姓名$name、性别$sex和年龄$age进行赋值
function __construct($name="", $sex="", $age="")
{
$this->name=$name;
$this->sex=$sex;
$this->age=$age;
}
//这个人可以说话的方法, 说出自己的属性
function say()
{
echo "我的名子叫:".$this->name." 性别:".$this->sex." 我的年龄是:".$this->age."<br>";
}
}
$p1=new Person("张三", "男", 20);
$p1_string=serialize($p1); //把一个对象串行化,返一个字符串
echo $p1_string."<br>"; //串行化的字符串我们通常不去解析
$p2=unserialize($p1_string); //把一个串行化的字符串反串行化形成对象$p2
$p2->say();
?>
上例输出结果:
O:6:"Person":3:{s:4:"name";s:4:"张三";s:3:"sex";s:2:"男";s:3:"age";i:20;}
我的名子叫:张三 性别:男 我的年龄是:20
在php5中有两个魔术方法__sleep()方法和__wakeup()方法,在对象串行化的时候,会调用一个__sleep()方法来完成一些睡前的事情;而在重新醒来,即由二进制串重新组成一个对象的时候,则会自动调用PHP的另一个函数__wakeup(),做一些对象醒来就要做的动作。
__sleep()函数不接受任何参数, 但返回一个数组,其中包含需要串行化的属性。末被包含的属性将在串行化时被忽略,如果没有__sleep()方法,PHP将保存所有属性。
<?
class Person
{
//下面是人的成员属性
var $name; //人的名子
var $sex; //人的性别
var $age; //人的年龄
//定义一个构造方法参数为属性姓名$name、性别$sex和年龄$age进行赋值
function __construct($name="", $sex="", $age="")
{
$this->name=$name;
$this->sex=$sex;
$this->age=$age;
}
//这个人可以说话的方法, 说出自己的属性
function say()
{
echo "我的名子叫:".$this->name." 性别:".$this->sex." 我的年龄是:".$this->age."<br>";
}
//指定串行化时把返回的数组中$name和$age值串行化,忽略没在数组中的属性$sex
function __sleep() {
$arr=array("name", "age");
return($arr);
}
//重新生成对象时,并重新赋值$age为40
function __wakeup() {
$this->age = 40;
}
}
$p1=new Person("张三", "男", 20);
//把一个对象串行化,返一个字符串,调用了__sleep()方法,忽略没在数组中的属性$sex
$p1_string=serialize($p1);
echo $p1_string."<br>"; //串行化的字符串我们通常不去解析
$p2=unserialize($p1_string); //反串行化形成对象$p2重新赋值$age为40
$p2->say();
?>
上例输出值为:
O:6:"Person":2:{s:4:"name";s:4:"张三";s:3:"age";i:20;}
我的名子叫:张三 性别: 我的年龄是:40
23.自动加载类
很多开发者写面向对象的应用程序时,对每个类的定义建立一个 PHP 源文件。一个很大的烦恼是不得不在每个脚本(每个类一个文件)开头写一个长长的包含文件的列表。
在软件开发的系统中,不可能把所有的类都写在一个PHP文件中,当在一个PHP文件中需要调用另一个文件中声明的类时,就需要通过include把这个文件引入。不过有的时候,在文件众多的项目中,要一一将所需类的文件都include进来,是一个很让人头疼的事,所以我们能不能在用到什么类的时候,再把这个类所在的php文件导入呢?这就是我们这里我们要讲的自动加载类。
在 PHP 5 中,可以定义一个 __autoload()函数,它会在试图使用尚未被定义的类时自动调用,通过调用此函数,脚本引擎在 PHP 出错失败前有了最后一个机会加载所需的类, __autoload()函数接收的一个参数,就是你想加载的类的类名,所以你做项目时,在组织定义类的文件名时,需要按照一定的规则,最好以类名为中心,也可以加上统一的前缀或后缀形成文件名,比如xxx_classname.php、classname_xxx.php以及就是classname.php等等.
本例尝试分别从 MyClass1.php 和 MyClass2.php 文件中加载 MyClass1 和 MyClass2 类
<?php
function __autoload($classname)
{
require_once $classname . ‘.php‘;
}
//MyClass1类不存在自动调用__autoload()函数,传入参数”MyClass1”
$obj = new MyClass1();
//MyClass2类不存在自动调用__autoload()函数,传入参数”MyClass2”
$obj2 = new MyClass2();
?>