|

楼主 |
发表于 2020-9-7 21:23:16
|
显示全部楼层
本帖最后由 why 于 2020-9-10 10:04 编辑
从世界观的角度可以认为:面向对象的基本哲学是认为世界是由各种各样具有自己的运动规律和内部状态的对象所组成的;不同对象之间的相互作用和通讯构成了完整的现实世界。因此,人们应当按照现实世界这个本来面貌来理解世界,直接通过对象及其相互关系来反映世界。这样建立起来的系统才能符合现实世界的本来面目。
从方法学的角度可以认为:面向对象的方法是面向对象的世界观在开发方法中的直接运用。它强调系统的结构应该直接与现实世界的结构相对应,应该围绕现实世界中的对象来构造系统,而不是围绕功能来构造系统。
面向对象:"Object Oriented"
Object: n. 物体; 物品; 东西; (极欲得到、研究、注意等的) 对象; 宗旨; 目的; 目标;
v. 不同意; 不赞成; 反对; 提出…作为反对的理由; 抗辩说;
Oriented: v. 朝向; 面对; 确定方向; 使适应; 确定方位; 认识方向; 熟悉; 适应;
从哲学层面来讲,现实世界中你作为一个独立的个体,区别于其他客体.你作为一切事务的主体,用观察的眼光去看待客体,所有除你以外的都属于客体,我们把客体不断分类细化,便于我们进行观察一个单一并且唯一的客体.面向对象的编程理念是建立在这种哲学层面的角度构建的,世界是由不同类型的对象构建的,把对象进行分类,对这一类对象再以属性进行分类,抽离出这一类对象的共性,作为基础,这个基础共性适用于这一类对象,这是继承.这一类对象的不同属性用来增加单一客体的唯一性,这是对象属性,属性可以用来进行修改,以确定单一客体的唯一辩识性.
面向对象是面向研究对象,面向对象干嘛?面向对象才能看到对象,看到对象才能更好的研究,研究什么?研究问题的共性,剥离共性解决哪些参差不齐的不同点,用属性来加以标识细化处理,单个解决.
以下内容转载至:https://www.cnblogs.com/net2012/archive/2013/04/03/2997280.html
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 "这个人在说话";
}f
unction run() //这个人可以走路的方法
{
echo "这个人在走路";
}
}
?>
上面就是一个类的声明,从属性和方法上声明出来的一个类,但是成员属性最好在声明的时候不要给初始的值,因为我们做的人这个类是一个描述信息,将来用它实例化对象,比如实例化出来10 个人对象,那么这10 个人, 每一个人的名字、性别、年龄都是不一样的,所以最好不要在这个地方给成员属性赋初值,而是对每个对象分别赋值的。用同样的办法可以做出你想要的类了,只要你能用属性和方法能描述出来的实体都可以定义成类,去实例化对象。为了加强你对类的理解,我们再做一个类,做一个形状的类,形状的范围广了点, 我们就做个矩形吧,先分析一下,想一想从两方面分析,矩形的属性都有什么?矩形的功能都有什么?
class 矩形
{
//矩形的属性
矩形的长;
矩形的宽;
//矩形的方法
矩形的周长;
矩形的面积;
}
<?php
class Rect
{
var $kuan;
var $gao;
function zhouChang()
{
计算矩形的周长;
}f
unction mianJi()
{
计算矩形的面积;
}
}
?>
如果用这个类来创建出多个矩形对象,每个矩形对象都有自己的长和宽, 都可以求出自己的周长和面积了。
类的声明我们就到这里吧!!
5.如何实例化对象
我们上面说过面向对象程序的单位就是对象,但对象又是通过类的实例化出来的,既然我们类会声明了,下一步就是实例化对象了。当定义好类后,我们使用new 关键字来生成一个对象。
$对象名称= new 类名称();
<?php
class Person
{
//下面是人的成员属性
var $name; //人的名字
var $sex; //人的性别
var $age; //人的年龄
//下面是人的成员方法
function say() //这个人可以说话的方法
{
echo "这个人在说话";
}f
unction 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 "这个人在说话";
}f
unction 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>”;
LAMP 大讲堂PHP 面向对象技术(全面讲解)
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>";
}f
unction 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;
}f
unction getAge() //为外部提供一个公有获取年龄的方法
{
return($this->age);
}
上面的方法是为一个成员属性设置和获取值, 当然你也可以为每个属性用同样的方法对其进行赋值和取值的操作,完成在类外部的存取工作。
10.__set() __get() __isset() __unset()四个方法的应用
一般来说,总是把类的属性定义为private,这更符合现实的逻辑。但是,对属性的读取和赋值操作是非常频繁的,因此在PHP5 中,预定义了两个函数“__get()”和“__set()”来获取和赋值其属性,以及检查属性的“__isset()”和删除属性的方法“__unset()”。
上一节中,我们为每个属性做了设置和获取的方法,在PHP5 中给我们提供了专门为属性设置值和获取值的方法,“__set()”和“__get()”这两个方法,这两个方法不是默认存在的,而是我们手工添加到类里面去的,像构造方法(__construct())一样, 类里面添加了才会存在,可以按下面的方式
来添加这两个方法,当然也可以按个人的风格来添加:
//__get()方法用来获取私有属性
private function __get($property_name)
{
if(isset($this->$property_name))
{
return($this->$property_name);
}else
{
return(NULL);
}
}/
/__set()方法用来设置私有属性
private 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()方法用来获取私有属性
private function __get($property_name)
{
echo "在直接获取私有属性值的时候,自动调用了这个__get()方法<br>";
if(isset($this->$property_name))
{
return($this->$property_name);
}e
lse
{
return(NULL);
}
}/
/__set()方法用来设置私有属性
private 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() 方法:在看这个方法之前我们看一下“isset()”函数的应用,isset()是测定变量是否设定用的函数,传入一个变量作为参数,如果传入的变量存在则传回true,否则传回false。那么如果在一个对象外面使用“isset()”这个函数去测定对象里面的成员是否被设定可不可以用它呢?分两种情况,如果对象里面成员是公有的,我们就可以使用这个函数来测定成员属性,如果是私有的成员属性,这个函数就不起作用了,原因就是因为私有的被封装了,在外部不可见。那么我们就不可以在对象的外部使用“isset()”函数来测定私有成员属性是否被设定了呢?可以,你只要在类里面加上一个“__isset()”方法就可以了,当在类外部使用”isset()”函数来测定对象里面的私有成员是否被设定时, 就会自动调用类里面的“__isset()”方法了帮我们完成这样的操作,“__isset()”方法也可以做成私有的。你可以在类里面加上下面这样的代码就可以了:
private function __isset($nm)
{
echo "当在类外部使用isset()函数测定私有成员$nm时,自动调用<br>";
return isset($this->$nm);
}
__unset()方法:看这个方法之前呢,我们也先来看一下“unset()”这个函数,“unset()”这个函数的作用是删除指定的变量且传回true,参数为要删除的变量。那么如果在一个对象外部去删除对象内部的成员属性用“unset()”函数可不可以呢,也是分两种情况,如果一个对象里面的成员属性是公有的,就可以使用这个函数在对象外面删除对象的公有属性,如果对象的成员属性是私有的,我使用这个函数就没有权限去删除,但同样如果你在一个对象里面加上“__unset()”这个方法,就可以在对象的外部去删除对象的私有成员属性了。在对象里面加上了“__unset()”这个方法之后,在对象外部使用“unset()”函数删除对象内部的私有成员属性时,自动调用“__unset()”函数来帮我们删除对象内部的私有成员属性,这个方法也可以在类的内部定义成私有的。在对象里面加上下
面的代码就可以了:
private function __unset($nm)
{
echo "当在类外部使用unset()函数来删除私有成员时自动调用的<br>";
unset($this->$nm);
}
我们来看一个完整的实例:
<?php
class Person
{
//下面是人的成员属性
private $name; //人的名字
private $sex; //人的性别
private $age; //人的年龄
//__get()方法用来获取私有属性
private function __get($property_name)
{
if(isset($this->$property_name))
{
return($this->$property_name);
}else {
return(NULL);
}
}/
/__set()方法用来设置私有属性
private function __set($property_name, $value)
{
$this->$property_name = $value;
}/
/__isset()方法
private function __isset($nm)
{
echo "isset()函数测定私有成员时,自动调用<br>";
return isset($this->$nm);
}/
/__unset()方法
private function __unset($nm)
{
echo "当在类外部使用unset()函数来删除私有成员时自动调用的<br>";
unset($this->$nm);
}
}
$p1=new Person();
$p1->name="this is a person name";
//在使用isset()函数测定私有成员时,自动调用__isset()方法帮我们完成,返回结果为true
echo var_dump(isset($p1->name))."<br>";
echo $p1->name."<br>";
//在使用unset()函数删除私有成员时,自动调用__unset()方法帮我们完成,删除name私有属性
unset($p1->name);
//已经被删除了, 所这行不会有输出
echo $p1->name;
?>
输出结果为:
isset()函数测定私有成员时,自动调用
bool(true)
this is a person name
当在类外部使用unset()函数来删除私有成员时自动调用的__set()、__get()、__isset()、__unset() 这四个方法都是我们添加到对象里面的,在需要时自动调用的,来完成在对象外部对对象内部私有属性的操作
以下内容转载至:https://www.cnblogs.com/457248499-qq-com/p/7384839.html
接口(interface)技术
什么是接口?
先看抽象类:
abstract class 类名 {
属性1;
属性2;
.....
非抽象方法1;
非抽象方法2;
......
抽象方法1;
抽象方法2;
......
}
设想,将上述抽象类中“实在的成员”,删除,即删除那些非抽象的成员。则,自然该抽象类中,就只有抽象方法;
abstract class 类名 {
抽象方法1;
抽象方法2;
......
}
由此,可以理解为:这个抽象类,“太抽象了”,几乎自己什么都没做,就光让别人做什么。
那么:
接口就是这样一个“天然不实在”的家伙:
接口,就是规定,里面只能放“抽象方法”和“常量”的一种类似类的语法结构;
——可见,接口就是“比抽象类更抽象的”一种语法结构。
接口(interface)定义形式:
interface 接口名{
常量1;
常量2;
.....
抽象方法1;
抽象方法2;
.....
}
说明:
1,可见,接口中,只有常量(接口常量)和抽象方法两种成员;
2,接口常量的使用形式为: 接口名称::常量名称;
3,接口中的抽象方法,不要使用abstract修饰,也不需要使用访问控制修饰符,因为其天然就是public
为什么需要接口?
面向对象编程思想是对“现实世界”的描述(模拟)!
现实世界往往都都是多继承的;
但:
出于降低类跟类之间关系的复杂度的考虑,就将语言设计为单继承的;
但这样,就无法表达出现实世界的多继承特性;
则:
接口就是对没有多继承的类之间关系的一个补充;
因为:接口可以实现“多继承”——但此时不称为继承而已,而是称为“实现”;
即:
接口1 -->> 类1;
就称为:类1实现了接口1;
其本质,其实就是类1中,有了接口1中“特征信息”;
使用形式:
形式为:
class 类名 implements 接口名1, 接口名2, ....{
//类的定义。
}
这里,叫做,类实现了接口。
其中,接口跟接口之间,也可以继承,跟类之间的继承:
interface 接口1 extends 接口2{
//接口的成员定义;。。。。
}
类和接口的大总结
以下内容转载至:https://www.cnblogs.com/lixiuran/p/3769879.html
PHP在魔术函数__autoload()方法出现以前,如果你要在一个程序文件中实例化100个对象,那么你必须用include或者require包含进来100个类文件,或者你把这100个类定义在同一个类文件中——相信这个文件一定会非常大。
但是__autoload()方法出来了,以后就不必为此大伤脑筋了,这个类会在你实例化对象之前自动加载制定的文件。
下边我们通过一个例子来看一下,具体的使用方法,并在稍后说明使用PHP魔术函数__autoload应该注意些什么。
代码如下 复制代码
//定义一个类ClassA,文件名为ClassA.php
class ClassA{
public function __construct(){
echo "ClassA load success!";
}
}
//定义一个类ClassB,文件名为ClassB.php,ClassB继承ClassA
class ClassB extends ClassA {
public function __construct(){
//parent::__construct();
echo "ClassB load success!";
}
}
定义两个测试用的类之后,我们来编写一个含有__autoload()方法的PHP运行程序文件如下:
代码如下 复制代码
function __autoload($classname){
$classpath="./".$classname.'.php';
if(file_exists($classpath)){
require_once($classpath);
}
else{
echo 'class file'.$classpath.'not found!';
}
}
$newobj = new ClassA();
$newobj = new ClassB();
这个文件的运行是一点问题都没有的,可见autoload是多么的好用啊,呵呵……
但是不得不提醒你一下几个方面是必须要注意的。
1、如果类存在继承关系(例如:ClassB extends ClassA),并且ClassA不在ClassB所在目录
利用__autoload魔术函数实例化ClassB的时候就会受到一个致命错误:
代码如下 复制代码
Fatal error: Class ‘Classd’ not found in ……ClassB.php on line 2,
解决方法:把所有存在extends关系的类放在同一个文件目录下,或者在实例化一个继承类的时候在文件中手工包含被继承的类;
2、另外一个需要注意的是,类名和类的文件名必须一致,才能更方便的使用魔术函数__autoload;
其他需要注意的事情:
3、在CLI模式下运行PHP脚本的话这个方法无效;
4、如果你的类名称和用户的输入有关——或者依赖于用户的输入,一定要注意检查输入的文件名,例如:.././这样的文件名是非常危险的。
第6章 面向对象特性
我们用句柄(唯一标识符)来标识对象
对象可以按类进行分类.类是表示彼此之间可能互不相同,但是必须具有一些共同点的对象集合.虽然类所包含的对象可能具有不同属性值,但是,这些对象都具有以相同方式实现的相同操作以及表示相同事务的相同属性
面向对象的编程语言必须支持多态性,多态性是指不同类对同一操作可以用不同的行为实现
多态性与其说是对象的特性,不如说是行为的特征.在PHP中,只有类的成员函数可以是多态的
集成允许我们使用子类之间创建层次关系,子类将从它的超类(也叫父类)继承属性和操作.通过继承,我们可以在已有的基础上创建新类.根据实际需要,可以从一个简单的基类开始,派生出更复杂.更专门的类
class关键字用来定义类
如下所示创建一个名为"classname"的类,它具有两个属性$attribute1,$attribute2
class classname
{
public $attribute1;
public $attribute2;
}
通过在类定义中声明函数,可以创建类的操作.如下所示的代码创建一个名为classname的类,该类包含两个不执行任何操作的方法,其中operation1()不带参数,而operation2()带有两个参数:
class classname
{
function operation1()
{
}
function operation2($param1,$param2)
{
}
}
大多数类都有一种称为构造函数的特殊操作.当创建一个对象时,对象的构造函数将被调用.通常这将执行一些有用的初始化任务.如:设置属性的初始值或者创建该对象需要的其他对象
构造函数的声明与其他操作的声明一样,只是其名称必须是_construct().尽管可以手动调用构造函数,但其本意是在创建一个对象时自动调用.如下所示的代码声明了一个具有构造函数的类:
class classname
{
function _construct($param)
{
echo "Constructor called with parameter ".$param."<br />";
}
}
与构造函数相对的就是析构函数.析构函数允许在销毁一个类之前被调用执行,它将完成一些操作或实现一些功能,这些操作或功能通常在所有对该类的引用都被重置或超出作用域时自动发生
与构造函数的命名类似,一个析构函数名称必须是_destruct().析构函数不能带有任何参数
在声明一个类后,需要创建一个可供使用的对象,它是一个特定的个体,即类的一个成员.这也叫创建一个实例或实例化一个类.可以使用关键词"new"来创建一个对象.需要指定创建的对象是哪一个类的实例,并且为构造函数提供任何所需的参数
如下所示的代码声明了一个具有构造函数,名为classname的类,然后又创建两个classname类型的对象
class classname
{
function _construct($param)
{
echo "Constructor called with parameter ".$param."<br />";
}
}
$a=new classname("First");
$b=new classname("Second");
由于每次创建一个对象都将调用这个构造函数,以上代码将产生如下所示的输出:
Constructor called with parameter First
Constructor called with parameter Second
在一个类中,可以访问一个特殊的指针---$this.如果当前类的一个属性为$attribute,则当在该类中通过一个操作设置或访问该变量时,可以使用$this->attribute来引用
如下所示的代码说明了如何在一个类中设置和访问属性:
class classname
{
public $attribute;
function operation($param)
{
$this->attribute=$param;
echo $this->attribute;
}
}
是否可以在类外部访问一个属性是由访问修饰符来确定的,这个示例没有对属性设置访问限制,因此可以按照如下所示的方式从类外部该问该属性:
- class classname
- {
- public $attribute;
- }
- $a=new classname();
- $a->attribute="value";
- echo $a->attribute;
复制代码
输出结果:
value
- <?php
- class classname //声明类
- {
- public $attribute; //声明属性,public关键字用来声明属性
- function operation($param) //声明函数创建类操作
- {
- $this->attribute=$param;
- //echo $this->attribute; 引用成功,这里这行注释并不影响返回值
- }
- }
- $a=new classname(); //创建对象
- $a->attribute="value"; //访问类的$attribute,并带回字符串赋值给类属性供函数调用
- echo $a->attribute; //返回类属性
- ?>
复制代码
输出结果:
value
可以以调用类属性相同的方法调用类操作,有如下所示类:
class classname
{
function operation1();
{
}
function operation2($param1,$param2);
{
}
}
创建一个classname类型的对象,命名为$a,如下代码所示:
$a=new classname();
可以按照调用其他函数的方法调用类操作:操作名称以及必要参数.由于这些操作属于一个对象,而不是普通的函数,所以必须执行操作所属的对象.与访问对象属性方法一样,可以通过对象名称及操作名称来访问操作,如下代码所示:
$x=$a->operation1(); //访问操作方法,返回值赋值给变量x
$y=$a->operation2(12,"test"); //访问操作方法,返回值赋值给变量y
php提供了访问修饰符.它们可以控制属性和方法的可见性.通常访问修饰符放置在属性和方法声明之前.PHP支持如下3中不同的访问修饰符:
默认选项是public,这意味着如果没有为一个属性或方法指定访问修饰符,它将是public.公有属性方法可以在类的内部或外部进行访问
private访问修饰符意味着被标记的属性或方法只能在类内部直接进行访问.私有的属性和方法将不会被继承
protected访问修饰符意味着被标记的属性或方法只能在类内部进行访问,它也存在于任何子类,在这里protected理解成位于private和public之间的关键字
如下所示的代码说明了public和private访问修饰符的使用方法:
class manners
{
private $greeting="Hello"; //私有
public function greet($name) //公有
{
echo "$this->greeting, $name";
}
}
最初版本的访问器函数如下所示:
<?php
class classname //声明类
{
public $attribute; //属性
function _get($name) //创建_get操作
{
return $this->$name;
}
function _set($name,$value) //创建_set操作
{
$this->$name=$value;
}
}
$a=new classname(); //创建$a对象
$a->attribute; //访问attribute属性,_get函数对象名attribute
$a->attribute=5; //访问attribute属性,_set函数返回对象的值
?>
要指定一个类称为另一个类的子类,可以使用关键字"extends"
如下代码创建了一个名为B的类,它继承了在它前面定义的类A
class B extends A
{
public $attribute2;
function operation2()
{
}
}
如果A类如下:
class A
{
public $attribute1;
function operation1()
{
}
}
如下所示的有对类B的对象的操作和属性的访问都是有效的:
$b=new B(); //创建B类对象$b
$b->operation1(); //访问A类属性$attribute1的操作
$b->attribute1=10; //访问A类属性$attribute1
$b->operation2(); //访问B类属性$attribute1的操作
$b->attribute2=10; //访问B类属性$attribute1
因为类B继承了类A,所以可以直接访问到类A的属性和操作,尽管这些操作和属性是在类A声明的.作为A的子类,B具有与A一样的功能和数据.此外B还声明了一个字节的的属性和操作
继承是单方向的.子类可以从父类或超类继承特性,但是父类却不能从子类继承特性
可以使用private和protected访问修饰符来控制需要继承的内容.如果一个属性或方法被指定为private,它将不能被继承.如果一个属性或方法被指定为protected,它将在类外部不可见(就像一个private元素),但是可以被继承
- <?php
- class A
- {
- private function operation1() //私有操作
- {
- echo "operation1 called";
- }
- protected function operation2() //protected可以被继承但只能在子类内部使用
- {
- echo "operation2 called";
- }
- public function operation3() //公有操作可以在类外部调用
- {
- echo "operation3 called";
- }
- }
- class B extends A
- {
- function _construct()
- {
- $this->operation1();
- $this->operation2();
- $this->operation3();
- }
- }
- $b=new B;
- $this->operation1(); //错误,私有操作不能在子类中调用
- $this->operation2(); //错误,protected可以被继承但只能在子类内部使用
- $this->operation3(); //公有操作可以在类外部调用
- ?>
复制代码
在子类中,再次声明相同的属性和操作也是有效的,而且在某些情况下这将会是非常有用的.我们可能需要在子类中给某个属性赋予一个与其超类属性值不同的默认值,或者给某个操作赋予一个与其超类操作不同的功能,这就叫覆盖
- <?php
- class A
- {
- public $attribute='default value';
- function operation()
- {
- echo 'Something<br />';
- echo 'The value of $attribute is '.$this->attribute.'<br />';
- }
- }
- class B extends A
- {
- public $attribute='different value';
-
- function operation()
- {
- echo '<br />Something else<br />';
- echo 'The value of $attribute is '.$this->attribute.'<br />';
- echo parent::operation(); //调用类A操作
- }
- }
- $a=new A();
- $a->operation();
- /*类A的对象调用operation方法,产生如下结果:
- Something
- The value of $attribute is default value*/
- $b=new B();
- $b->operation();
- /*类B的对象调用operation方法,产生如下结果:
- Something else
- The value of $attribute is different value
- Something //输出的这话是因为在类B中调用了类A的operation()操作
- The value of $attribute is different value*/
- ?>
复制代码
echo parent : : operation(); 调用父类的操作,还可以调用父类的构造函数,对它们进行修改
PHP提供了两种机制来支持类多重继承功能:接口和Trait
接口可以看作是多重继承问题的解决办法,接口的思想是指定一个实现该接口的类必须实现的一系列函数,如需要一系列能够显示自身的类.除了可以定义具有display()函数的父类,同时使这些子类都继承父类并覆盖该方法外,还可以实现一个接口,如下所示:
interface Displayable
{
function display();
}
class webpage implement Displayable
{
function display()
{
//....
}
}
以上代码说明了多重继承的一种解决办法,因为webpage类可以继承一个类,同时又可以实现一个或多个接口
如果没有实现接口中指定的方法(在这个实例中是display()方法),将产生一个致命的错误
Trait是能充分利用多重继承又不带来痛苦的方法.在Trait中,可以对将在多个类中重复的功能进行分组.一个类可以组合多个Trait,而Trait可以继承其他Trait.Trait是代码重用的最佳构建模块
接口和Trait最重要的不同就是:Trait包含了实现,而接口则不需要
可以按照创建类的方式创建Trait,但是需要使用Trait关键字,如下代码所示:
Trait logger
{
public function logmessage($message,$level='DEBUG')
{
//write $message to a log
}
}
使用Trait,需要编写如下代码
class fileStorage
{
use logger;
function store($data){
//...
$this->logmessage($msg)
}
如果需要,fileStorage类可以覆盖logmessage()方法.但是,需要注意的是,如果fileStorage类从父类继承了logmessage()方法,在默认情况下,名为logger的Trait将覆盖logmessage()方法.也就是,Trait方法覆盖继承的方法,但当前类方法覆盖了Trait的方法.
Trait的一个优点是可以组合多个Trait,当有多个方法具有相同名称,可以显式地指定需要使用特定Trait的功能.考虑如下所示示例:
- <?php
- trait filelogger
- {
- public function logmessage($message,$level='DEBUG')
- {
- //wite $message to a log file
- }
- }
- trait sysLogger
- {
- public function logmessage($message,$level='DEBUG')
- {
- //wite $message to a log file
- }
- }
- class fileStorager
- {
- use fileLogger,sysLogger
- {
- fileLogger::logmessage insteadof sysLogger; //insteadof使用sysLogger操作
- sysLogger::logmessage as private logsysmessage; //as关键字重命名该Trait,后边的private修改了操作的可见性
- }
-
- function store($data)
- {
- //..
- $this->logmessage($message);
- $this->logsysmessage($message);
- }
- }
- ?>
复制代码
我们在use子句中使用了两个不同的logging(日志记录)Trait.由于每个Trait都实现了相同的logmessage()方法,我们必须指定具体使用那个.如果不指定,PHP将产生致命错误,因为PHP无法解决此种冲突
使用insteadof关键字,可以指定要使用的Trait,如下代码所示:
fileLogger::logmessage insteadof sysLongger;
以上代码显式地告诉PHP使用fileLogger Trait的logmessage()方法.但是,在这个示例中,我们也需要访问sysLogger Trait的logmessage()方法.要解决此问题,可以通过as关键字重命名该Trait,如下代码所示:
sysLogger::logmessage as private logsysmessage;
sysLogger的logsysmessage()方法就可以用了,请注意,在这个示例中,其实是修改了方法的可见范围.这并不是必须的,介绍它的原因是说明这是可行的
还可以进一步构建完全由其他Trait组成或包含的Trait.这也就是真正实现水平组合特性.要实现水平组合,可以在Trait内使用use语句,就像在类中使用
page类提供了简单灵活的方法来创建TAL页面
- <?php
- class Page //声明类
- {
- //class Page's attributes
- public $content; //属性
- public $title="TLA Consulting Pty Ltd"; //属性
- public $keywords="TLA Consulting,Three Letter Abbreviation, //属性
- some of my best friends are search engines";
- public $buttons=array("Home"=>"home.php", //属性
- "Contact"=>"contact.php",
- "Services"=>"services.php",
- "site Map"=>"map.php"
- );
- //class Page's operations
- public function _set($name,$value) //_set()给私有属性赋值,_get()获取私有属性值
- {
- $this->$name=$value;
- }
- public function Display() //创建Display操作,显示网页格式
- {
- echo "<html>\n<head>\n"; //输出头部head
- $this->DisplayTitle();
- $this->DisplayKeyworde();
- $this->DisplayStyles();
- echo"</head>\n<body>\n"; //输出主体body
- $this->DisplayHeader();
- $this->DisplayMenu($this->buttons);
- echo $this->content; //引用content属性
- $this->DisplayFooter(); //引用页脚
- echo "</body>\n</html>\n";
- }
- public function DisplayTitle() //创建DisplayTitle操作
- {
- echo "<title>".$this->title."</title>"; //输出<title>
- }
- public function DisplayKeyworde() //创建DisplayKeyworde操作
- {
- echo "<meat name='keywords' content='".$this->keywords."'/>"; 输出<meat>
- }
- public function DisplayStyles() //创建DisplayStyles操作
- {
- ?>
- <link href="styles.css" type="text/css" rel="stylesheet">
- <?php //PHP方式输出<link>
- }
- public function DisplayHeader() //创建DisplayHeader操作
- {
- ?>
- <!-- page header -->
- <header>
- <img src="logo.gif" alt="TLA logo" height="70" width="70" />
- <h1>TLA Consulting</h1>
- </header>
- <?php //输出<header>
- }
- public function DisplayMenu($buttons) //创建DisplayMenu操作
- {
- echo "<!-- menu -->
- <nav>";
- while (list($name,$url)=each($buttons)){ //each($buttons)获取返回数组的键名和键值,list($name,$url)把数组中的值赋值给变量$name和$url
- $this->DisplayButton($name,$url,
- !$this->IsURLCurrentPage($url));
- }
- echo "</nav>\n";
- } //输出<nav>
- public function IsURLCurrentPage($url) //创建IsURLCurrentPage操作
- {
- if(strpos($_SERVER['PHP_SELF'],$url)===false) //$_SERVER['PHP_SELF']获取当前页面地址,strpos($_SERVER['PHP_SELF'],$url)在当前页面地址中查找$url
- {
- return false; //如果成功在当前页面获取到的地址中有字符串$url,返回flesh
- }
- else
- {
- return true; //否则返回true
- }
- }
- public function DisplayButton($name,$url,$active=true)
- {
- if($active){?>
- <div class="menuitem">
- <a href="<?=$url?>">
- <img src="s-logo.gif" alt="" height="20" windth="20" />
- <span class="menutext"><?=$name?></span>
- </a>
- </div>
- <?php //如果返回true执行这里
- }else{ ?>
- <div class="menuitem">
- <img src="side-logo.gif">
- <span class="menutext"><?=$name?></span>
- </div>
- <?php //否则执行这里
- }
- }
- public function DisplayFooter() //创建DisplayFooter
- {
- ?>
- <!-- page footer-->
- <footer>
- <p>©TLA Consulting Pty Ltd.<br />
- Please see our
- <a href="legal.php">legal information page</a>.</p>
- </footer>
- <?php
- }
- }
- ?>
复制代码
首页使用page类完成生成页面的大部分工作
- <?php
- require("page.php"); //引入page.php
- $homepage= new Page(); //创建$homepage对象
- //$homepage对象访问content属性
- $homepage->content="<!-- page content -->
- <section>
- <h2>Welcome to the home of TLA Consulting.</h2>
- <p>Please take some time to get to konw us.</p>
- <p>We specialize in serving your business needs
- and hope to hear from you soon.</p>
- </section>";
- $homepage->Display(); //引用Display操作
- ?>
复制代码
使用类级别常量
PHP提供了类级别常量思想.这个常量可以在不需要初始化该类的情况下使用,如下代码所示:
<?php
class Math
{
const pi=3.1415926;
}
echo "Math::pi=".Math::pi;
?>
输出结果:
Math::pi=3.1415926
可以通过::操作符并指定常量所属的类来访问类级别常量,如上所示
static静态化引用
<?PHP
class Math
{
static function squared($input) //创建静态操作
{
return $input*$input;
}
}
echo Math::squared(8); //静态引用不能使用this关键字
?>
输出结果:
64
instanceof关键字允许检查一个对象的类型,它可以检查一个对象是否是特定类的实例.是否是从某个类继承过来或者是否实现了某个接口.instanceof关键字是一个高效率的条件操作符,如前面的示例中,类B作为类A的子类,如下语句:
{$b instanceof B} 将返回true
{$b instanceof A} 将返回true
{$b instanceof Display} 将返回true
以上这些语句都是假设类A,类B接口Display都位于当前的作用域;否则将产生一个错误
function check_hint(B $someclass) //要求$someclass必须是类B的实例
{
//....
}
如果传入了类A的一个实例会出现致命错误
如果指定的是类A而传入类B的实例将不会产生错误,因为类B继承了类A
在相同函数有多个实现的继承曾级中,可以使用延迟静态绑定来确定调用具体类的方法.如下:
<?PHP
class A{
public static function whichclass(){
echo _CLASS_;
}
public static function test(){
self::whichclass();
}
}
class B extends A{
public static function whichclass(){
echo _CLASS_;
}
}
A::test();
B::test();
?>
都是输出的类A当前的类类名
self关键字和static都是获取当前类名,不同点如下:
* 1.在一个类A中,self::who() 等同于 static::who()
* 2.当子类B继承父类A,子类B::test(),调用的时候,区别:
* test()方法调用 self::who() 调用父类的who()方法
* test()方法调用 static::who()调用的是子类的who()方法
clone关键字,允许赋值一个已有对象
$c=clone $b;
将创建对象$b的副本,具有与$b相同的类型与属性值
_call()实现方法重载
call()方法必须带有两个参数,第一个包含了被调用的方法名称,而第二个参数包含了传递给该方法的参数数组
public function _call($method,$p) //方法重载
{
if ($method =="display"){
if(is_object($p[0])){
$this->displayobject($p[0]);
}else if(is_array($p[0])){
$this->displayArray($p[0]);
}else{
$this->displayScalar($p[0]);
}
}
}
$ov=new overload;
$ov->display(array(1,2,3));
$ov->display('cat');
另一个特殊的函数时_autoload().它不是一个类方法,而是一个单独的函数,也就是说,可以在任何类声明之外声明这个函数.如果实现了这个函数,它将在实例化一个还没有被声明的类时自动调用
_autoload()方法的主要用途是尝试引入特定文件,而又需要该文件来初始化所需类
function _autoload($name)
{
include_once $name.".php";
}
该实例将引入一个具有与该类相同名称的文件
foreach()循环遍历一个对象的所以属性,就像数组方式一样
foreach 仅能用于数组,当试图将其用于其它数据类型或者一个未初始化的变量时会产生错误。有两种语法,第二种是第一种的有用的扩展。
foreach(array_expression as $value) statement
foreach(array_expression as $key => $value) statement
第一种格式遍历给定的 array_expression 数组。每次循环中,当前单元的值被赋给 $value 并且数组内部的指针向前移一步(因此下一次循环中将会得到下一个单元)。
第二种格式做同样的事,只是除了当前单元的值以外,键值也会在每次循环中被赋给变量 $key。
如果需要更复杂的行为,可以实现一个迭代器(iterator).要实现一个迭代器,被迭代的类必须实现IteratorAggregate接口,并且定义一个能够返回该迭代类实例的getIterator方法,迭代器和基类示例程序清单如下所示:
- <?PHP
- class ObjectIterator implements Iterator{ //implements实现接口,Iterator迭代器;ObjectIterator实现了接口Iterator
- private $obj; //对象
- private $count; //数据元素的数量
- private $currentIndex; //当前指针
-
- function __construct($obj) //构造函数
- {
- $this->obj=$obj;
- $this->count=count($this->obj->data);
- }
- function rewind() //重置迭代器,指针指向0
- {
- $this->currentIndex=0;
- }
- function valid() //验证迭代器是否有数据
- {
- return $this->currentIndex<$this->count;
- }
- function key() //迭代器key当前位置
- {
- return $this->currentIndex;
- }
- function current() //获取当前内容
- {
- return $this->obj->data[$this->currentIndex];
- }
- function next() //移动key到下一个
- {
- $this->currentIndex++;
- }
- }
- class bject implements IteratorAggregate //object实现了接口IteratorAggregate
- {
- public $data=array(); //属性
-
- function __construct($in) //构造函数
- {
- $this->data=$in;
- }
- function getIterator() //getIterator方法
- {
- return new ObjectIterator($this); //创建ObjectIterator对象,并返回实例名
- }
- }
- $myObject =new bject(array(2,4,6,8,10)); //创建对象
- $myIterator=$myObject->getIterator();
- for($myIterator->rewind();$myIterator->valid();$myIterator->next())
- {
- $key=$myIterator->key();
- $value=$myIterator->current();
- echo $key."=>".$value."<br />";
- }
- ?>
复制代码
输出结果:
0=>2
1=>4
2=>6
3=>8
4=>10
在很多方面,生成器与迭代器类似,但生成器更简单.可以理解生成器为:定义时像函数,运行时像迭代器
编写生成器与普通函数的区别在于:生成器需要使用yield关键字返回结果.而普通函数使用return关键字返回结果,
必须在foreach循环中调用生成器函数,这将创建一个能够保存生成器函数内部状态的Generator对象.在外部foreach循环的每次迭代中,生成器执行下一个内部迭代
使用生成器打印fizzbuzz序列
- <?PHP
- function fizzbuzz($start,$end)
- {
- $current=$start; //$current=1,$end=20
- while($current<=$end){
- if($current%3==0&&$current%5==0){
- yield"fuzzbuzz";
- }else if($current%3==0){
- yield"fizz";
- }else if($current%5==0){
- yield"buzz";
- }else{
- yield $current;
- }
- $current++; //每循环一次$current+1,当$current=21时停止循环
- }
- }
- foreach(fizzbuzz(1,20) as $number){
- echo $number.'<br />'; //循环取出数组元素1-20的值并输出
- }
- ?>
复制代码
输出结果:
1
2
fizz
4
buzz
fizz
7
8
fizz
buzz
11
fizz
13
14
fuzzbuzz
16
17
fizz
19
buzz
__toString() 是魔术方法的一种,具体用途是当一个对象被当作字符串对待的时候,会触发这个魔术方法 以下说明摘自PHP官方手册
public string __toString ( void )
__toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。
Warning
不能在 __toString() 方法中抛出异常。这么做会导致致命错误。
- <?php
- // Declare a simple class
- class TestClass
- {
- public $foo;
- public function __construct($foo)
- {
- $this->foo = $foo;
- }
- public function __toString() {
- return $this->foo;
- }
- }
- $class = new TestClass('Hello'); //实例化类
- echo $class;
- ?>
复制代码
输出结果:
Hello
php的面向对象特性还包括反射api.反射是通过访问已有类和对象来找到类和对象结构及内容的能力
显示page类的信息
- <?php
- require_once("page.PHP");
- $class=new ReflectionClass("page");
- echo "<pre>".$class."</pre>";
- ?>
复制代码
输出结果:
Class [ class Page ] {
@@ G:\phpstudy\WWW\page.php 2-106
- Constants [0] {
}
- Static properties [0] {
}
- Static methods [0] {
}
- Properties [4] {
Property [ public $content ]
Property [ public $title ]
Property [ public $keywords ]
Property [ public $buttons ]
}
- Methods [10] {
Method [ public method _set ] {
@@ G:\phpstudy\WWW\page.php 15 - 18
- Parameters [2] {
Parameter #0 [ $name ]
Parameter #1 [ $value ]
}
}
Method [ public method Display ] {
@@ G:\phpstudy\WWW\page.php 19 - 31
}
Method [ public method DisplayTitle ] {
@@ G:\phpstudy\WWW\page.php 32 - 35
}
Method [ public method DisplayKeyworde ] {
@@ G:\phpstudy\WWW\page.php 36 - 39
}
Method [ public method DisplayStyles ] {
@@ G:\phpstudy\WWW\page.php 40 - 45
}
Method [ public method DisplayHeader ] {
@@ G:\phpstudy\WWW\page.php 46 - 55
}
Method [ public method DisplayMenu ] {
@@ G:\phpstudy\WWW\page.php 56 - 65
- Parameters [1] {
Parameter #0 [ $buttons ]
}
}
Method [ public method IsURLCurrentPage ] {
@@ G:\phpstudy\WWW\page.php 66 - 76
- Parameters [1] {
Parameter #0 [ $url ]
}
}
Method [ public method DisplayButton ] {
@@ G:\phpstudy\WWW\page.php 77 - 94
- Parameters [3] {
Parameter #0 [ $name ]
Parameter #1 [ $url ]
Parameter #2 [ $active = true ]
}
}
Method [ public method DisplayFooter ] {
@@ G:\phpstudy\WWW\page.php 95 - 105
}
}
}
PHP 命名空间(namespace)
PHP 命名空间(namespace)是在PHP 5.3中加入的,如果你学过C#和Java,那命名空间就不算什么新事物。 不过在PHP当中还是有着相当重要的意义。
PHP 命名空间可以解决以下两类问题:
用户编写的代码与PHP内部的类/函数/常量或第三方类/函数/常量之间的名字冲突。
为很长的标识符名称(通常是为了缓解第一类问题而定义的)创建一个别名(或简短)的名称,提高源代码的可读性。
定义命名空间
默认情况下,所有常量、类和函数名都放在全局空间下,就和PHP支持命名空间之前一样。
命名空间通过关键字namespace 来声明。如果一个文件中包含命名空间,它必须在其它所有代码之前声明命名空间。语法格式如下;
- <?php
- // 定义代码在 'MyProject' 命名空间中
- namespace MyProject;
-
- // ... 代码 ...
复制代码
你也可以在同一个文件中定义不同的命名空间代码,如:
- <?php
- namespace MyProject;
- const CONNECT_OK = 1;
- class Connection { /* ... */ }
- function connect() { /* ... */ }
- namespace AnotherProject;
- const CONNECT_OK = 1;
- class Connection { /* ... */ }
- function connect() { /* ... */ }
- ?>
复制代码
不建议使用这种语法在单个文件中定义多个命名空间。建议使用下面的大括号形式的语法。
- <?php
- namespace MyProject {
- const CONNECT_OK = 1;
- class Connection { /* ... */ }
- function connect() { /* ... */ }
- }
- namespace AnotherProject {
- const CONNECT_OK = 1;
- class Connection { /* ... */ }
- function connect() { /* ... */ }
- }
- ?>
复制代码
将全局的非命名空间中的代码与命名空间中的代码组合在一起,只能使用大括号形式的语法。全局代码必须用一个不带名称的 namespace 语句加上大括号括起来,例如:
- <?php
- namespace MyProject {
- const CONNECT_OK = 1;
- class Connection { /* ... */ }
- function connect() { /* ... */ }
- }
- namespace { // 全局代码
- session_start();
- $a = MyProject\connect();
- echo MyProject\Connection::start();
- }
- ?>
复制代码
在声明命名空间之前唯一合法的代码是用于定义源文件编码方式的 declare 语句。所有非 PHP 代码包括空白符都不能出现在命名空间的声明之前。
- <?php
- declare(encoding='UTF-8'); //定义多个命名空间和不包含在命名空间中的代码
- namespace MyProject {
- const CONNECT_OK = 1;
- class Connection { /* ... */ }
- function connect() { /* ... */ }
- }
- namespace { // 全局代码
- session_start();
- $a = MyProject\connect();
- echo MyProject\Connection::start();
- }
- ?>
复制代码
以下代码会出现语法错误:
<html>
- <?php
- namespace MyProject; // 命名空间前出现了“<html>” 会致命错误 - 命名空间必须是程序脚本的第一条语句
- ?>
复制代码
子命名空间
与目录和文件的关系很像,PHP 命名空间也允许指定层次化的命名空间的名称。因此,命名空间的名字可以使用分层次的方式定义:
- <?php
- namespace MyProject\Sub\Level; //声明分层次的单个命名空间
- const CONNECT_OK = 1;
- class Connection { /* ... */ }
- function Connect() { /* ... */ }
- ?>
复制代码
上面的例子创建了常量 MyProject\Sub\Level\CONNECT_OK,类 MyProject\Sub\Level\Connection 和函数 MyProject\Sub\Level\Connect。
命名空间使用
PHP 命名空间中的类名可以通过三种方式引用:
非限定名称,或不包含前缀的类名称,例如 $a=new foo(); 或 foo::staticmethod();。如果当前命名空间是 currentnamespace,foo 将被解析为 currentnamespace\foo。如果使用 foo 的代码是全局的,不包含在任何命名空间中的代码,则 foo 会被解析为foo。 警告:如果命名空间中的函数或常量未定义,则该非限定的函数名称或常量名称会被解析为全局函数名称或常量名称。
限定名称,或包含前缀的名称,例如 $a = new subnamespace\foo(); 或 subnamespace\foo::staticmethod();。如果当前的命名空间是 currentnamespace,则 foo 会被解析为 currentnamespace\subnamespace\foo。如果使用 foo 的代码是全局的,不包含在任何命名空间中的代码,foo 会被解析为subnamespace\foo。
完全限定名称,或包含了全局前缀操作符的名称,例如, $a = new \currentnamespace\foo(); 或 \currentnamespace\foo::staticmethod();。在这种情况下,foo 总是被解析为代码中的文字名(literal name)currentnamespace\foo。
下面是一个使用这三种方式的实例:
file1.php 文件代码
- <?php
- namespace Foo\Bar\subnamespace;
- const FOO = 1;
- function foo() {}
- class foo
- {
- static function staticmethod() {}
- }
- ?>
复制代码
file2.php 文件代码
- <?php
- namespace Foo\Bar;
- include 'file1.php';
- const FOO = 2;
- function foo() {}
- class foo
- {
- static function staticmethod() {}
- }
- /* 非限定名称 */
- foo(); // 解析为函数 Foo\Bar\foo
- foo::staticmethod(); // 解析为类 Foo\Bar\foo ,方法为 staticmethod
- echo FOO; // 解析为常量 Foo\Bar\FOO
- /* 限定名称 */
- subnamespace\foo(); // 解析为函数 Foo\Bar\subnamespace\foo
- subnamespace\foo::staticmethod(); // 解析为类 Foo\Bar\subnamespace\foo,
- // 以及类的方法 staticmethod
- echo subnamespace\FOO; // 解析为常量 Foo\Bar\subnamespace\FOO
-
- /* 完全限定名称 */
- \Foo\Bar\foo(); // 解析为函数 Foo\Bar\foo
- \Foo\Bar\foo::staticmethod(); // 解析为类 Foo\Bar\foo, 以及类的方法 staticmethod
- echo \Foo\Bar\FOO; // 解析为常量 Foo\Bar\FOO
- ?>
复制代码
注意访问任意全局类、函数或常量,都可以使用完全限定名称,例如 \strlen() 或 \Exception 或 \INI_ALL。
在命名空间内部访问全局类、函数和常量:
- <?php
- namespace Foo;
- function strlen() {}
- const INI_ALL = 3;
- class Exception {}
- $a = \strlen('hi'); // 调用全局函数strlen
- $b = \INI_ALL; // 访问全局常量 INI_ALL
- $c = new \Exception('error'); // 实例化全局类 Exception
- ?>
复制代码
命名空间和动态语言特征
PHP 命名空间的实现受到其语言自身的动态特征的影响。因此,如果要将下面的代码转换到命名空间中,动态访问元素。
example1.php 文件代码:
- <?php
- class classname
- {
- function __construct()
- {
- echo __METHOD__,"\n";
- }
- }
- function funcname()
- {
- echo __FUNCTION__,"\n";
- }
- const constname = "global";
- $a = 'classname';
- $obj = new $a; // prints classname::__construct
- $b = 'funcname';
- $b(); // prints funcname
- echo constant('constname'), "\n"; // prints global
- ?>
复制代码
必须使用完全限定名称(包括命名空间前缀的类名称)。注意因为在动态的类名称、函数名称或常量名称中,限定名称和完全限定名称没有区别,因此其前导的反斜杠是不必要的。
动态访问命名空间的元素
- <?php
- namespace namespacename;
- class classname
- {
- function __construct()
- {
- echo __METHOD__,"\n";
- }
- }
- function funcname()
- {
- echo __FUNCTION__,"\n";
- }
- const constname = "namespaced";
- include 'example1.php';
- $a = 'classname';
- $obj = new $a; // 输出 classname::__construct
- $b = 'funcname';
- $b(); // 输出函数名
- echo constant('constname'), "\n"; // 输出 global
- /* 如果使用双引号,使用方法为 "\\namespacename\\classname"*/
- $a = '\namespacename\classname';
- $obj = new $a; // 输出 namespacename\classname::__construct
- $a = 'namespacename\classname';
- $obj = new $a; // 输出 namespacename\classname::__construct
- $b = 'namespacename\funcname';
- $b(); // 输出 namespacename\funcname
- $b = '\namespacename\funcname';
- $b(); // 输出 namespacename\funcname
- echo constant('\namespacename\constname'), "\n"; // 输出 namespaced
- echo constant('namespacename\constname'), "\n"; // 输出 namespaced
- ?>
复制代码
namespace关键字和__NAMESPACE__常量
PHP支持两种抽象的访问当前命名空间内部元素的方法,__NAMESPACE__ 魔术常量和namespace关键字。
常量__NAMESPACE__的值是包含当前命名空间名称的字符串。在全局的,不包括在任何命名空间中的代码,它包含一个空的字符串。
__NAMESPACE__ 示例, 在命名空间中的代码
- <?php
- namespace MyProject;
- echo '"', __NAMESPACE__, '"'; // 输出 "MyProject"
- ?>
- __NAMESPACE__ 示例,全局代码
- <?php
- echo '"', __NAMESPACE__, '"'; // 输出 ""
- ?>
复制代码
常量 __NAMESPACE__ 在动态创建名称时很有用,例如:
使用__NAMESPACE__动态创建名称
- <?php
- namespace MyProject;
- function get($classname)
- {
- $a = __NAMESPACE__ . '\\' . $classname;
- return new $a;
- }
- ?>
复制代码
关键字 namespace 可用来显式访问当前命名空间或子命名空间中的元素。它等价于类中的 self 操作符。
namespace操作符,命名空间中的代码
- <?php
- namespace MyProject;
- use blah\blah as mine; // see "Using namespaces: importing/aliasing"
- blah\mine(); // calls function blah\blah\mine()
- namespace\blah\mine(); // calls function MyProject\blah\mine()
- namespace\func(); // calls function MyProject\func()
- namespace\sub\func(); // calls function MyProject\sub\func()
- namespace\cname::method(); // calls static method "method" of class MyProject\cname
- $a = new namespace\sub\cname(); // instantiates object of class MyProject\sub\cname
- $b = namespace\CONSTANT; // assigns value of constant MyProject\CONSTANT to $b
- ?>
复制代码
namespace操作符, 全局代码
- <?php
- namespace\func(); // calls function func()
- namespace\sub\func(); // calls function sub\func()
- namespace\cname::method(); // calls static method "method" of class cname
- $a = new namespace\sub\cname(); // instantiates object of class sub\cname
- $b = namespace\CONSTANT; // assigns value of constant CONSTANT to $b
- ?>
复制代码
使用命名空间:别名/导入
PHP 命名空间支持 有两种使用别名或导入方式:为类名称使用别名,或为命名空间名称使用别名。
在PHP中,别名是通过操作符 use 来实现的. 下面是一个使用所有可能的三种导入方式的例子:
1、使用use操作符导入/使用别名
- <?php
- namespace foo;
- use My\Full\Classname as Another;
- // 下面的例子与 use My\Full\NSname as NSname 相同
- use My\Full\NSname;
- // 导入一个全局类
- use \ArrayObject;
- $obj = new namespace\Another; // 实例化 foo\Another 对象
- $obj = new Another; // 实例化 My\Full\Classname 对象
- NSname\subns\func(); // 调用函数 My\Full\NSname\subns\func
- $a = new ArrayObject(array(1)); // 实例化 ArrayObject 对象
- // 如果不使用 "use \ArrayObject" ,则实例化一个 foo\ArrayObject 对象
- ?>
复制代码
2、 一行中包含多个use语句
- <?php
- use My\Full\Classname as Another, My\Full\NSname;
- $obj = new Another; // 实例化 My\Full\Classname 对象
- NSname\subns\func(); // 调用函数 My\Full\NSname\subns\func
- ?>
复制代码
导入操作是在编译执行的,但动态的类名称、函数名称或常量名称则不是。
3、导入和动态名称
- <?php
- use My\Full\Classname as Another, My\Full\NSname;
- $obj = new Another; // 实例化一个 My\Full\Classname 对象
- $a = 'Another';
- $obj = new $a; // 实际化一个 Another 对象
- ?>
复制代码
另外,导入操作只影响非限定名称和限定名称。完全限定名称由于是确定的,故不受导入的影响。
4、导入和完全限定名称
- <?php
- use My\Full\Classname as Another, My\Full\NSname;
- $obj = new Another; // 实例化 My\Full\Classname 类
- $obj = new \Another; // 实例化 Another 类
- $obj = new Another\thing; // 实例化 My\Full\Classname\thing 类
- $obj = new \Another\thing; // 实例化 Another\thing 类
- ?>
复制代码
使用命名空间:后备全局函数/常量
在一个命名空间中,当 PHP 遇到一个非限定的类、函数或常量名称时,它使用不同的优先策略来解析该名称。类名称总是解析到当前命名空间中的名称。因此在访问系统内部或不包含在命名空间中的类名称时,必须使用完全限定名称,例如:
1、在命名空间中访问全局类
- <?php
- namespace A\B\C;
- class Exception extends \Exception {}
- $a = new Exception('hi'); // $a 是类 A\B\C\Exception 的一个对象
- $b = new \Exception('hi'); // $b 是类 Exception 的一个对象
- $c = new ArrayObject; // 致命错误, 找不到 A\B\C\ArrayObject 类
- ?>
复制代码
对于函数和常量来说,如果当前命名空间中不存在该函数或常量,PHP 会退而使用全局空间中的函数或常量。
2、 命名空间中后备的全局函数/常量
- <?php
- namespace A\B\C;
- const E_ERROR = 45;
- function strlen($str)
- {
- return \strlen($str) - 1;
- }
- echo E_ERROR, "\n"; // 输出 "45"
- echo INI_ALL, "\n"; // 输出 "7" - 使用全局常量 INI_ALL
- echo strlen('hi'), "\n"; // 输出 "1"
- if (is_array('hi')) { // 输出 "is not array"
- echo "is array\n";
- } else {
- echo "is not array\n";
- }
- ?>
复制代码
全局空间
如果没有定义任何命名空间,所有的类与函数的定义都是在全局空间,与 PHP 引入命名空间概念前一样。在名称前加上前缀 \ 表示该名称是全局空间中的名称,即使该名称位于其它的命名空间中时也是如此。
使用全局空间说明
- <?php
- namespace A\B\C;
- /* 这个函数是 A\B\C\fopen */
- function fopen() {
- /* ... */
- $f = \fopen(...); // 调用全局的fopen函数
- return $f;
- }
- ?>
复制代码
命名空间的顺序
自从有了命名空间之后,最容易出错的该是使用类的时候,这个类的寻找路径是什么样的了。
- <?php
- namespace A;
- use B\D, C\E as F;
- // 函数调用
- foo(); // 首先尝试调用定义在命名空间"A"中的函数foo()
- // 再尝试调用全局函数 "foo"
- \foo(); // 调用全局空间函数 "foo"
- my\foo(); // 调用定义在命名空间"A\my"中函数 "foo"
- F(); // 首先尝试调用定义在命名空间"A"中的函数 "F"
- // 再尝试调用全局函数 "F"
- // 类引用
- new B(); // 创建命名空间 "A" 中定义的类 "B" 的一个对象
- // 如果未找到,则尝试自动装载类 "A\B"
- new D(); // 使用导入规则,创建命名空间 "B" 中定义的类 "D" 的一个对象
- // 如果未找到,则尝试自动装载类 "B\D"
- new F(); // 使用导入规则,创建命名空间 "C" 中定义的类 "E" 的一个对象
- // 如果未找到,则尝试自动装载类 "C\E"
- new \B(); // 创建定义在全局空间中的类 "B" 的一个对象
- // 如果未发现,则尝试自动装载类 "B"
- new \D(); // 创建定义在全局空间中的类 "D" 的一个对象
- // 如果未发现,则尝试自动装载类 "D"
- new \F(); // 创建定义在全局空间中的类 "F" 的一个对象
- // 如果未发现,则尝试自动装载类 "F"
- // 调用另一个命名空间中的静态方法或命名空间函数
- B\foo(); // 调用命名空间 "A\B" 中函数 "foo"
- B::foo(); // 调用命名空间 "A" 中定义的类 "B" 的 "foo" 方法
- // 如果未找到类 "A\B" ,则尝试自动装载类 "A\B"
- D::foo(); // 使用导入规则,调用命名空间 "B" 中定义的类 "D" 的 "foo" 方法
- // 如果类 "B\D" 未找到,则尝试自动装载类 "B\D"
- \B\foo(); // 调用命名空间 "B" 中的函数 "foo"
- \B::foo(); // 调用全局空间中的类 "B" 的 "foo" 方法
- // 如果类 "B" 未找到,则尝试自动装载类 "B"
- // 当前命名空间中的静态方法或函数
- A\B::foo(); // 调用命名空间 "A\A" 中定义的类 "B" 的 "foo" 方法
- // 如果类 "A\A\B" 未找到,则尝试自动装载类 "A\A\B"
- \A\B::foo(); // 调用命名空间 "A" 中定义的类 "B" 的 "foo" 方法
- // 如果类 "A\B" 未找到,则尝试自动装载类 "A\B"
- ?>
复制代码
名称解析遵循下列规则:
对完全限定名称的函数,类和常量的调用在编译时解析。例如 new \A\B 解析为类 A\B。
所有的非限定名称和限定名称(非完全限定名称)根据当前的导入规则在编译时进行转换。例如,如果命名空间 A\B\C 被导入为 C,那么对 C\D\e() 的调用就会被转换为 A\B\C\D\e()。
在命名空间内部,所有的没有根据导入规则转换的限定名称均会在其前面加上当前的命名空间名称。例如,在命名空间 A\B 内部调用 C\D\e(),则 C\D\e() 会被转换为 A\B\C\D\e() 。
非限定类名根据当前的导入规则在编译时转换(用全名代替短的导入名称)。例如,如果命名空间 A\B\C 导入为C,则 new C() 被转换为 new A\B\C() 。
在命名空间内部(例如A\B),对非限定名称的函数调用是在运行时解析的。例如对函数 foo() 的调用是这样解析的:
在当前命名空间中查找名为 A\B\foo() 的函数
尝试查找并调用 全局(global) 空间中的函数 foo()。
在命名空间(例如A\B)内部对非限定名称或限定名称类(非完全限定名称)的调用是在运行时解析的。下面是调用 new C() 及 new D\E() 的解析过程: new C()的解析:
在当前命名空间中查找A\B\C类。
尝试自动装载类A\B\C。
new D\E()的解析:
在类名称前面加上当前命名空间名称变成:A\B\D\E,然后查找该类。
尝试自动装载类 A\B\D\E。
为了引用全局命名空间中的全局类,必须使用完全限定名称 new \C()。
我已经实在不想看这一章了,对新手来说这一章说的都看不懂,书上可以说啥都没说 |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
|