8.1 类与对象
随着计算机技术的不断发展,计算机所面对的问题也越来越复杂,而面向对象是将一切事物看作对象,从而有利于对复杂系统进行分析。PHP是面向对象的编程语言,类与对象是面向对象程序设计的核心。本节将介绍类与对象的相关概念。
8.1.1 面向对象的概念
面向对象是对现实世界理解与抽象的一种方法,它将世界看作一个庞大的整体,将世界分解成一个个对象来描述,这样世界就成为一个一个对象的集合。通过面向对象的方法,有利于问题的描述与处理。面向对象的程序设计(Object-Oriented Programming,OOP)使程序设计过程更自然与直观,从而提高了编程的效率。面向对象的特征如下。
1.对象唯一性
每个对象都有区别于其他对象的唯一标识。
2.抽象性
抽象性是指将对象的特征(属性)和行为(操作)的对象抽象成类。
3.继承性
继承性是子类自动拥有父类的成员方法与成员属性。该特征提高了代码的重用性。
4.多态性
多态性是指相同的操作或函数作用于多种类型的对象上,可获得不同的结果。
8.1.2 类与对象
现实世界中有很多同类的对象,类是对一个或几个相似的对象进行描述,把不同对象所具有的共性抽象出来,定义某类对象共有的变量与方法,从而实现代码的复用。在PHP中用关键字class和一个任意的类名来定义一个类。类名可以是任意数字和字母的组合,但是不能以数字开头,一般类名首字符大写。类定义的示例如下。
<?php class Student{ //类体 } ?>
这样就定义了一个类名为Student的类。如果把类当作生成对象的模板,那么对象是类的“实例”。
使用Student类作为生成Student对象的模型:
<?php
class Student {
//类体
}
$s = new Student ();
?>
这样就通过使用new这个关键字创建了一个Student的对象。
1.类中的属性
属性指在class中声明的变量,也被称为成员变量,用来存放对象之间互不相同的数据。在PHP 5中,类中的属性和普通变量很相似,必须使用public、private、protected之一进行修饰决定变量的访问权限。
(1)public(公开):可以自由地在类的内部和外部读取、修改。
(2)private(私有):只能在这个当前类的内部读取、修改。
(3)protected(受保护):能够在这个类和类的子类中读取和修改。
以上关键字是PHP 5中引入的,在PHP 4下运行将无法正常工作。
通过使用->符号连接对象和属性名来访问属性变量。在方法内部通过$this->符号访问同一对象的属性。下面通过一个实例来理解属性的使用。
8-1.php
<?php class Student { public $sid="001"; public $sname="tom"; } $s=new Student(); echo "学生的学号:".$s->sid."<br/>"."学生的姓名:".$s->sname; ?>
运行效果如图8-1所示。
Student类有两个属性,$sid和$sname,在实例化后,使用$s->sid和$s->sname访问属性的内容。当然,也可以在属性定义时不设置初始值,那样就打印不出任何结果了。
PHP并没有强制属性必须在类中声明,可以通过对象随时动态增加属性到对象,例如:
$s->sid ="002"; //通过对象动态增加属性
但这种写法不好,建议不要使用。下面通过一个小例子来看如何修改对象属性。
8-2.php
<?php class Student { public $sid="001"; public $sname="tom"; } $s=new Student(); $s->sid="002"; echo "学生的学号:".$s->sid."<br/>"."学生的姓名:".$s->sname; ?>
运行效果如图8-2所示。
采用private关键字修饰的属性,称为私有属性,一个对象的私有属性在该对象以外不能被访问。设置私有属性可对数据进行隐藏,可保证该数据的安全。
看下面的程序,如果我们创建的对象直接访问私有name属性,就会发生错误。
8-3.php
<?php class Student { public $sid="001"; private $sname="tom"; } $s=new Student(); echo"学生的学号:".$s->sid."<br/>"."学生的姓名:".$s->sname; ?>
运行效果如图8-3所示。
运行结果中给出错误信息,提示没有权限访问私有属性,如果使用getName()方法取得name属性则是可以的。在PHP 5中,指向对象的变量是一个引用变量。在这个变量里面存储的是所指向对象的内存地址。引用变量传值时,传递的是这个对象的地址而非复制这个对象。
$s=new Student(); $sOne = $s;
这里是引用传递,$s与$sOne指向的是同一个内存地址,来看下面的例子。
8-4.php
<?php class Student { public $sid="001"; public $sname="tom"; } $s=new Student(); $sOne=new Student(); echo "学生的学号:".$s->sid."学生的姓名:".$s->sname."<br/>"; echo "学生的学号:".$sOne->sid."学生的姓名:".$sOne->sname; ?>
运行效果如图8-4所示。
运行结果显示两个对象的name属性都为tom,说明了$s、$sOne指的是同一个对象。
2.类中的方法
属性可以让对象存储数据,类中的方法则可以让对象执行任务。方法即为类中声明的特殊函数,因此与函数声明相似。function关键字在方法名之前,之后圆括号中的是可选参数列表。
public function myMethod($para1,$para2,…){ //方法体 }
通过方法定义时的参数,可以向方法内部传递变量。如下面的示例,定义方法时定义了方法参数$name,使用这个方法时,可以向方法内传递参数变量。方法内接收到的变量是局部变量,仅在方法内部有效。可以通过向属性传递变量值的方式,让这个变量应用于整个对象。
与访问属性一样,可以使用->连接对象和方法名来调用方法,值得注意的是调用方法时必须带有圆括号(参数可选)。$this是指向当前对象的指针。
8-5.php
<?php class Student { private $sid; private $sname; public function setSid($sid) { $this->sid=$sid; } public function getSid() { return $this->sid; } public function setName($name) { $this->sname=$name; } public function getName() { return $this->sname; } } $s=new Student(); $s->setSid("001"); $s->setName("tom"); echo "学生的学号:".$s->getSid()."学生的姓名:".$s->getName()."<br/>"; ?>
运行效果如图8-5所示。
注意:
(1)如果声明类的方法时带有参数,而调用这个方法时没有传递参数,或者参数数量不足,系统会报出错误。
(2)如果声明类的方法时带有参数,而调用这个方法参数数量超过方法定义参数的数量,PHP会忽略多余的参数,不会报错。
3.构造方法
构造方法是对象被创建时自动调用的方法,用来确保必要的属性被设置,并完成任何需要准备的初始化工作。构造方法和其他函数一样,可以传递参数,也可以设定参数默认值。构造方法可以访问类中属性,可以调用类中方法,同时可以被其他方法显式调用。在PHP 4中使用与类名同名的方法为构造函数。在PHP 5中规定构造方法使用_ _construct()。
8-6.php
<?php class Student { private $sid; private $sname; public function __construct($sid,$sname) { $this->sid=$sid; $this->sname=$sname; } public function getSid() { return $this->sid; } public function getName() { return $this->sname; } } $s=new Student("001","tom"); echo "学生的学号:".$s->getSid()."学生的姓名:".$s->getName()."<br/>"; ?>
运行效果如图8-6所示。
4.析构函数与PHP的垃圾回收机制
析构方法是当某个对象成为垃圾或者当对象被显式销毁时执行的方法。在PHP中,没有任何变量引用这个对象时,该对象就成为垃圾,PHP会自动将其在内存中销毁,这是PHP的垃圾处理机制,防止内存溢出。当一个PHP线程结束时,当前占用的所有内存空间都会被销毁,当前程序中的所有对象同样被销毁。在PHP 5中析构方法规定使用__destruct()。析构函数也可以被显式调用,但不要这样去做。析构函数是由系统自动调用的,不要在程序中调用一个对象的析构函数,析构函数不能带有参数。
8-7.php
<?php class Student { private $sid; private $sname; public function __construct($sid,$sname) { $this->sid=$sid; $this->sname=$sname; } public function getSid() { return $this->sid; } public function getName() { return $this->sname; } public function __destruct () { echo '析构函数在这里执行,这里一般用来放置关闭数据库等收尾工作。'; } } $s=new Student("001","tom"); echo "学生的学号:".$s->getSid()."学生的姓名:".$s->getName()."<br/>"; for($i=1;$i<4;$i++) { echo $i.'<br>'; } ?>
运行效果如图8-7所示。
当对象没有引用时,对象同样被销毁。
8-8.php
<?php class Student { private $sid; private $sname; public function __construct($sid,$sname) { $this->sid=$sid; $this->sname=$sname; } public function getSid() { return $this->sid; } public function getName() { return $this->sname; } public function __destruct () { echo '析构函数在这里执行,这里一般用来放置关闭数据库等收尾工作。<br/>'; } } $s=new Student("001","tom"); echo "学生的学号:".$s->getSid()."学生的姓名:".$s->getName()."<br/>"; $s=null; for($i=1;$i<4;$i++) { echo $i.'<br>'; } ?>
运行效果如图8-8所示。
5.继承
继承是从一个基类得到一个或多个类的机制,是面向对象最重要的特点之一,可以实现对类的复用。
继承自另一个类的类被称为该类的子类,这种关系通常比作父亲和孩子。子类将继承父类的属性和方法,同时可以扩展父类,即增加父类之外的新功能。
以自行车的折叠自行车为例:
自行车的特征(属性)?
(1)两个轮子;
(2)两个脚蹬;
(3)一个车座。
自行车的动作(方法)?
(1)骑行;
(2)刹车。
折叠自行车的特征(属性)?继承自行车:
(1)两个轮子;
(2)两个脚蹬;
(3)一个车座。
自行车的动作(方法)?继承自行车:
(1)骑行;
(2)刹车;
(3)折叠。
继承是面向对象最重要的特点之一,可以实现对类的复用。通过“继承”一个现有的类,可以使用已经定义的类中的方法和属性,继承而产生的类叫作子类,被继承的类叫作父类,也被称为超类。PHP是单继承的,一个类只可以继承一个父类,但一个父类却可以被多个子类所继承。从子类的角度看,它继承自父类;而从父类的角度看,它派生出子类。它们指的都是同一个动作,只是角度不同而已。子类不能继承父类的私有属性和私有方法。在PHP 5中类的方法可以被继承,类的构造函数也能被继承。在8-9.php中的student类继承自Citizen类,当实例化Citizen类的子类Student类时,父类的方法setId()和getId()被继承。可以直接调用父类的方法设置其属性$id,取得其属性$id值。
8-9.php
<?php class Citizen { private $id; public function setId($id) { $this->id=$id; } public function getId() { return $this->id; } } class Student extends Citizen { private $school; public function setSchool($school) { $this->school=$school; } public function getSchool() { return $this->school; } } $s=new Student(); $s->setId("001"); $s->setSchool("hbsi"); echo "学生的身份证号:".$s->getId()."学生的学校:".$s->getSchool(). "<br/>"; ?>
运行结果如图8-9所示。
6.修饰符的使用
在PHP 5中,可以在类的属性和方法前面加上一个修饰符,来对类进行一些访问上的控制,表8-1显示了各修饰符的访问权限。
修饰符 | 同一个类中 | 子类中 | 全局 |
private | Y | N | N |
protected | Y | Y | N |
public | Y | Y | Y |
private:不能直接被外部调用,只能在当前类的内部来调用。
protected:修饰的属性和方法只能被当前类内部或子类调用,外界无法调用。
public:修饰的属性和方法,可以被无限制地调用。
7.重写
如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的重写。当对父类的方法进行重写时,子类中的方法必须和父类中对应的方法具有相同的方法名称,在PHP 5中不限制输入参数类型、参数数量和返回值类型。子类中的覆盖方法不能使用比父类中被覆盖方法更严格的访问权限。声明方法时,如果不定义访问权限,默认权限为public。8-10.php中,子类Student继承了父类Citizen,并对父类中的getId()方法进行了覆盖。
8-10.php
<?php class Citizen { private $id; public function setId($id) { $this->id=$id; } public function getId() { return $this->id; } } class Student extends Citizen { private $school; public function setSchool($school) { $this->school=$school; } public function getSchool() { return $this->school; } public function getId() { return "覆盖父类方法getId"; } } $s=new Student(); $s->setId("001"); $s->setSchool("hbsi"); echo "学生的身份证号:".$s->getId()."学生的学校:".$s->getSchool(). "<br/>"; ?>
运行效果如图8-10所示。
8.parent::关键字
PHP 5中使用parent::来引用父类的方法,同时也可用于调用父类中定义的成员方法。在8-11.php中,子类Student继承了父类Citizen,并对父类中的getId()方法进行了覆盖,并在自身所定义的getId()方法中通过parent::getId()调用了父类的getId()方法。
8-11.php
<?php class Citizen { private $id; public function setId($id) { $this->id=$id; } public function getId() { return $this->id; } } class Student extends Citizen { private $school; public function setSchool($school) { $this->school=$school; } public function getSchool() { return $this->school; } public function getId() { return parent::getId(); } } $s=new Student(); $s->setId("001"); $s->setSchool("hbsi"); echo"学生的身份证号:".$s->getId()."学生的学校:".$s->getSchool()."<br/>"; ?>
运行效果如图8-11所示。
9.重载
当类中的方法名相同时,称为方法的重载,重载是Java等面向对象语言中重要的一部分。但在PHP 5中不支持重载,不支持有多个相同名称的方法,因为调用时给定的实参比声明时形参数量少会报错,而当参数太多的时候,PHP 5会忽略掉后面的多余参数,程序正常运行。
10.静态属性和方法
在上面的内容中把类当作生成对象的模板,把对象作为活动组件,面向对象编程中的操作都是通过类的实例(对象,而不是类本身)完成的。事实并非如此简单,我们不仅可以通过对象来访问方法和属性,还可以通过类本身来访问,这样的方法和属性是“静态的”(static)。
在PHP 5中,static关键字用来声明静态属性和方法。static关键字声明一个属性或方法是和类相关的,而不是和类的某个特定的实例相关,因此,这类属性或方法也称为“类属性”或“类方法”。如果访问控制权限允许,可不必创建该类对象而直接使用类名加两个冒号“::”调用,即不需经过实例化就可以访问类中的静态属性与方法。
静态属性与方法只能访问静态的属性和方法,不能访问类中非静态的属性和方法。因为静态属性和方法被创建时,可能还没有任何这个类的实例可以被调用。static的属性,在内存中只有一份,为所有的实例共用,一个类的所有实例,共用类中的静态属性,也就是说,在内存中即使有多个实例,静态的属性也只有一份。在类内不能用this来引用静态变量或方法,而需要用self。在8-12.php中,在类Student的构造方法中,通过self::$count访问类Student的静态变量$count。
8-12.php
<?php class Student { private static $count=0; public function __construct() { self::$count++; echo self::$count."<br/>"; } } $sOne=new Student(); $sTwo=new Student(); $sThree=new Student(); ?>
运行效果如图8-12所示。
在类外部可以使用:类::静态方法,类::静态变量。在8-13.php中,在Student类外通过Student::say()访问Student的静态变量$count。
8-13.php
<?php class Student { public static $count=1; public static function say() { echo "hello static<br/>"; } } echo Student::$count."<br/>"; Student::say(); ?>
运行效果如图8-13所示。
11.final类和方法
继承为类层次内部带来了巨大的灵活性。通过覆写类或方法,调用同样的类方法可以得到完全不同的结果,但有时候,也可能需要类或方法保持不变的功能,这时就需要使用final关键字了。
(1)final类不能被继承,final关键字可以终止类的继承,final类不能有子类,final方法不能被覆写。
(2)final方法不能被重写。
12.常量属性
有些属性不能改变,比如错误和状态标志,经常需要被硬编码进类中。虽然它们是公共的、可静态访问的,但客户端代码不能改变它们。
在PHP 5中可以使用const关键字定义常量属性,和全局常量一样,定义的这个常量不能被改变。const定义的常量与定义变量的方法不同,不需要加$修饰符,如const PI = 3.14。使用const定义的常量名称一般都大写。
类中的常量使用起来类似静态变量,不同的是常量的值不能被改变。调用常量使用类名::常量名。
8-14.php
<?php class Circle { const PI=3.14; public static function area($r) { return $r*$r*self::PI; } } echo "PI:".Circle::PI."<br/>"; echo "半径为5的圆的面积:".Circle::area(5)."<br/>"; ?>
运行效果如图8-14所示。
13.abstract类和方法
使用abstract关键字来修饰一个类或者方法,称为抽象类或者抽象方法。引入抽象类(abstract class)是PHP 5的一个主要变化。这个新特性正是PHP朝面向对象设计发展的另一个标志。抽象类不能被直接实例化。抽象类中只定义了子类需要的方法,方法只有方法声明,没有方法体。子类可以继承它并且通过实现其中的抽象方法,使抽象类具体化。用abstract修饰的类表示这个类是一个抽象类,抽象类至少包含一个抽象方法,这个类不能被直接实例化。抽象类可以被子类继承,继承抽象类的子类可以被实例化,下面通过一个实例来了解abstract类和方法。
8-15.php
<?php abstract class Citizen { abstract public function say(); } class Student extends Citizen { public function say() { echo "hello"; } } $s=new Student(); $s->say(); ?>
运行效果如图8-15所示。
14.接口
父类可以派生出多个子类,但一个子类只能继承一个父类,PHP不支持多重继承,接口有效地解决了这一问题。接口是一种类似于类的结构,可用于声明实现类所必须声明的方法,它只包含方法原型,不包含方法体。这些方法原型必须被声明为public,不可以为private或protected。
声明接口需要使用interface关键字:
interface Ihuman{}
与继承使用extends关键字不同的是,实现接口使用的是implements关键字。
class man implements Ihuman{}
实现接口的类必须实现接口中声明的所有方法,除非这个类被声明为抽象类。
8-16.php
<?php interface Citizen { public function say(); } class Student implements Citizen { public function say() { echo "hello"; } } $s=new Student(); $s->say(); ?>
运行效果如图8-16所示。
8.1.3 PHP 5中的魔术方法
PHP 5中以两个下划(_ _)线开头的方法都是PHP中保留的魔术方法。魔术方法是系统预定义的方法,但是使用前需要在类内声明,它们的功能、方法名、使用的参数列表和返回值都是预定义好的。如果需要使用这些方法,魔术方法的方法体内容需要编程者按需求编写。魔术方法使用时无须用户显式调用,而是在相应情况下自动被调用。但是需记住,魔法并不总是一件好事情,有时会出意外,有时会改变规则,因此会付出隐性的代价。接下来介绍几¬个常用的魔术方法。
1._ _set方法与_ _get方法
通常为了安全性,总是把类中属性的访问修饰符定义为private,因为这样更符合现实的逻辑。但有关属性读取与赋值的操作是比较频繁的,因此在PHP 5中定义了有关私有属性读取与赋值操作的魔术方法。
_ _set($property,$value)是程序试图给一个不能被访问的属性赋值时会调用的方法。通常在以下情况该方法被调用:①当在程序中给一个未定义的属性赋值时,_ _set函数会被调用,传递的参数分别是被赋值的属性名与属性值;②当试图给一个没有访问权限的属性赋值时,也会调用_ _set方法,传递的参数分别是被赋值的属性名与属性值。
_ _get($property)是程序试图访问一个不能访问的属性时会调用的方法。该方法有一个参数,通过该参数获取属性名,方法会返回属性值。
8-17.php
<?php class Person { private $name; //第一个成员属性$name用于保存人的名字,此属性的访问属性为私有 private $sex; //第二个成员属性$sex用于保存人的性别,此属性的访问属性为私有 private $age; //第三个成员属性$age用于保存人的年龄,此属性的访问属性为私有 //声明魔术方法__set,该方法有两个参数变量 function __set($propertyName, $propertyValue) { //PHP 5中没有指定类成员的 访问修饰符,默认值为public的访问权限 if($propertyName=="sex"){ if(!($propertyValue == "男" || $propertyValue == "女")) return; } if($propertyName=="age"){ if($propertyValue > 150 || $propertyValue <0) return; } $this->$propertyName = $propertyValue; } //声明魔术方法__get function __get($propertyName) { if($propertyName=="sex") { return "保密"; } else if($propertyName=="age") { if($this->age > 30) return $this->age-10; else return $this->$propertyName; } else { return $this->$propertyName; } } } $per=new Person(); $per->age=35; $per->sex="女"; $per->name="marry"; echo "my name:".$per->name; echo "my age:".$per->age; echo "my sex:".$per->sex; ?>
运行效果如图8-17.php所示。
2._ _call方法
_ _call($method,$arg_array)是当程序试图调用一个未定义的方法时被自动调用的。此魔术方法的应用示例如8-18.php所示。
8-18.php
<?php class Test { function __call($function_name, $args) { print "你所调用的函数:$function_name(参数:"; print_r($args); print ")不存在!<br>\n"; } } $test = new Test(); //调用对象里不存在的方法 $test -> hello("one", "two", "three"); //程序不会退出可以执行到这里 echo "这是_ _call方法调用的示例<br>"; ?>
运行效果如图8-18所示。
3._ _toString方法
在PHP 5.2之前打印一个对象,PHP就会把对象解析成一个字符串来输出。但PHP 5.2之后这样做会提示错误,因此可以通过使用_ _toString()方法控制字符串的输出格式。
当程序将一个对象转化成字符串时会自动调用_ _toString方法,返回一个字符串值。比如使用print或echo打印对象时。
8-19.php
<?php class TestToString { public $foo; public function _ _construct($foo) { $this -> foo = $foo; } public function _ _toString() { return $this -> foo; } } $class = new TestToString('测试_ _toString方法'); echo $class; ?>
运行效果如图8-19所示。
共有条评论 网友评论