15.2 PHP中的类和对象
PHP支持面向对象的编程,支持类和对象的概念。从数据类型的角度看,对象是一种比较特殊的数据类型。它由一个事先定义好的类生成,而类由用户自己定义,它由一系列数据和对这些数据操作的数个函数组成。
可以这么认为,类是一种用户自定义的数据类型,通过这个类型可以定义一个该类型的变量,这个变量就是该类型的对象。通过如下代码,可以使读者对类和对象有一个感性上的认识。
$a_man = new person;
上面的代码中person是类,而$a_man就是person类型的变量,即$a_man是类person的对象。
类也是一种数据类型,可以把它看作是实际变量(对象)的模板或蓝图。要创建类类型的变量,就必须使用new运算符,就像:“$a_man=new person”,这个语句指定$man为person类型的变量。回忆一下对其他普通类型变量的定义,往往只是直接对变量赋值,并没有指定它的类型,如“$a=10”,即指定了变量$a是个整型变量。这是类类型和普通数据类型在使用上的一个不同之处。
PHP5把对象看成与其他数据类型不同,对象通过引用来传递。但PHP不强求通过引用(reference)显性传递和返回对象。在PHP5中,复制一个对象或者将一个对象当作参数传递给一个函数时,计算机不需要复制数据,而是仅仅保持相同的对象指针。这就是说,在程序中目标对象的任何改变都会影响到源对象。这使得对象看起来就像总是通过引用(reference)来传递,因此在PHP5中,对象默认为通过“引用”传递,不再需要像在PHP4中那样使用&来声明。
注意 PHP可以完成内存管理,自动清除不再需要的对象。当PHP程序中不再需要使用某个对象时,PHP会自动释放其所占的内存空间。
15.2.1 类的创建
在PHP中使用如下语法定义类。
class classname { statement }
class是PHP的保留关键字,表示开始类的定义。classname是类名,由开发人员指定。由花括号“{”和“}”括住的语句statement是类的实体,它一般由数据和对这些数据进行操作的函数组成。类中的数据,一般被称作成员变量(也叫属性),类中的函数一般被称作成员函数(或称作方法)。如下示例代码定义了一个类。
class person { private $name; }
这段代码定义了一个名叫person的类,它只有一个属性$name。这个属性由关键词private限定,它的含义是,该成员变量是私有的,表示该属性不能从类的外部访问。例如如下代码。
$a_man = new person; $a_man->name = 'Jack';
这段代码执行时会产生一个错误,因为语句$a_man->name='Jack'在类的外部访问类的私有成员变量$name。由private声明的成员变量$name不能从类的外部访问,只能在类的内部操作private性质的成员变量。
通常,定义类就是为了将数据加以封装,这样防止数据在封装之外被修改,而对数据的操作以接口形式提供给外部程序使用。也就是说,类通过定义类的成员函数,向外部提供操作类成员变量(即属性)的接口,外部程序通过这些函数完成对类私有成员的操作。请看如下代码。
01 class person 02 { 03 private $name; // 私有成员变量$name 04 public function set_name($name) // 成员函数set_name() 05 { 06 $this->name = $name; 07 } 08 public function get_name() // 成员函数get_name() 09 { 10 echo “user name is: ”.$this->name; 11 } 12 }
【代码解析】现在person类有一个私有成员变量$name,两个成员函数set_name()和get_name(),这两个函数用关键字public声明,表示它们是公有成员,公有成员可以从类的外部访问。函数set_name()通过语句$this->name=$name为类的私有成员变量赋值,因为这是在类的内部操作变量$name,所以是允许的。而函数get_name()用来输出变量$name的值。对象通过运算符“->”操作成员变量和成员函数。该运算符左操作数是对象,右操作数是对象的属性或方法。这里有一个特殊的变量$this,它表示该类实例化成对象时,该对象本身。在使用$this时,它的右操作数如果是成员变量,该成员变量前不再加符号$,只在this前加符号$。这一规则同样适用于对象变量。
说明 类的属性声明为private,操作这些属性的成员函数声明为public。但这并不是绝对的,有时也希望某个成员函数不会在类的外部被调用,此时,就可以将该成员函数声明为private。
注意 在PHP中,类的名称不区分大小写。因此,不能既定义person类又定义PERSON类。
15.2.2 类的实例化——对象
类只是提供了一种数据类型的模板,它本身并不能做具体的某种数据处理。只有将类具体化、实例化,才可以完成数据处理操作。类的实例化就是前面提到的对象,对象是程序执行过程中的类的实体。
在PHP中建立好一个类后,就可以使用new运算符生成一个类的实例,即创建该类的一个对象。代码15-1演示了如何在PHP程序中使用对象。
代码15-1 在程序中使用对象15-1.php
01 <?php 02 class Person // 定义Person 类 03 { 04 private $name; // 私有成员变量$name 05 public function set_name($name) // 成员函数set_name() 06 { 07 $this->name = $name; 08 } 09 public function get_name() // 成员函数get_name() 10 { 11 echo "My name is ".$this->name."<br/>"; 12 } 13 } 14 15 $boy = new Person; //Person 类的实例$boy 16 $boy->set_name("Harry Pottor"); 17 $boy->get_name(); 18 19 $girl = new Person; //Person 类的实例$ girl 20 $girl->set_name("Emma"); 21 $girl->get_name(); 22 ?>
图15-1 在程序中使用对象
【代码解析】该程序中,使用new运算符生成类person的两个实例$boy和$girl,接着为这两个对象的成员变量$name分别赋值为“Harry Potter”和“Emma”,最后使用成员函数get_name()输出各自对象成员变量$name的值。程序执行结果如图15-1所示。
从这个执行结果中可以看出,对象$boy和$girl虽然调用的都是类person的函数get_name(),但它们准确的获得了各自的名字,并没有相互影响。这是因为,当新建一个类的实例(对象)时,内存会被用来存储实例所有属性,每个对象独有自己的一组属性内存空间,但方法是由该类的所有实例共享的。
15.2.3 构造函数和析构函数
在一个类中声明一个名为__construct的函数,这个函数称为构造函数,它在建立一个对象实例时被自动调用。注意,关键字construct之前是两个下划线。就像其他任何函数一样,构造函数可以带有有参数。通常,在构造函数中使用这些参数完成对象属性的初始化等操作。在类中定义一个名为__destruct的函数,它称为析构函数,PHP将在对象被销毁前自动调用这个函数。
另外一种命名构造函数和析构函数的方式是,使用类的名称。比如一个名叫Cat的类,在类定义时命名一个名叫Cat()的函数,那么这个函数就是构造函数。刚才介绍的第一种声明构造函数的方法,可以使构造函数有一个独一无二的名称,而不论它所在的类的名称是什么。这样在改变类的名称时,就不再需要改变构造函数的名称。
不论使用哪种定义构造函数的方式,如果在构造函数中使用了参数,那么在使用new运算符创建该类的对象时,必须传入参数,除非构造函数的参数使用了默认值,示例代码如下。
01 <?php 02 class Student 03 { 04 private $id, $name; // 私有成员变量$name 05 public function __construct($s_id, $s_name) // 构造函数 06 { 07 $this->id = $s_id; 08 $this->name = $s_name; 09 } 10 } 11 12 $stu = new Student(1, 'George Wesley'); // 实例化类的同时会调用构造函数 13 ?>
【代码解析】这段代码定义构造函数时指定了两个参数$id和$name,如代码第05行所示。当使用new操作符创建Student类的对象$stu时,传入了参数,如代码第15行所示。执行代码new Student(1,'George Wesley')时,就会调用类的构造函数__construct($s_id,$s_name),将1传给构造函数的第1个参数$s_id,将‘Weslye’传给构造函数的第2个参数$s_name。
代码15-2进一步演示了构造函数和析构函数的使用方法及调用情况。
代码15-2 使用构造函数和析构函数15-2.php
01 <?php 02 class Cat 03 { 04 private $name; // 私有成员变量$name 05 function __construct() // 构造函数 06 { 07 echo " <b> 构造函数被调用.... </b><br/><br/>"; 08 } 09 function __destruct() // 析构函数 10 { 11 echo " <b> 析构函数被调用.... </b><br/><br/>"; 12 } 13 function set_name($name) // 成员函数set_name() 14 { 15 $this->name = $name; 16 } 17 function get_name() // 构造函数get_name() 18 { 19 echo " 这只猫的名字叫:".$this->name."<br/><br/>"; 20 } 21 } 22 23 $mypet = new Cat; 24 echo "__construct() 调用之后<br/><br/>"; 25 $mypet->set_name(" 小白"); 26 $mypet->get_name(); 27 echo" 类方法get_name() 调用之后<br/><br/>"; 28 ?>
【代码解析】这段代码加入了构造函数和析构函数,执行结果如图15-2所示。从执行结果可以清楚地看出,构造函数和析构函数在PHP程序中分别如何调用。当使用代码$mypet=new Cat创建一个Cat类的对象$mypet时,构造函数__construct()首先被调用,向页面输出一段“构造函数被调用….”的信息。然后使用Cat类的set_name()设置对象$mypet的名字、使用get_name()获取对象的名字。当这一切执行完毕后,PHP自行调用析构函数__destruct()来撤销对象$mypet所占内存资源,同时向页面输出一段构造函数已经调用的信息。
图15-2 调用构造函数和析构函数
构造函数通常用来初始化对象的属性,即为创建的对象分配内存空间。代码15-2只是演示了在PHP程序中构造函数如何调用,因此并没有做Cat类属性的初始化操作,只是输出一段信息,表明该构造函数已经调用。析构函数与构造函数相反,PHP调用它将一个对象从内存中销毁,回收其占用的内存空间。默认情况下,PHP仅仅释放对象属性所占用的内存并销毁对象相关的资源。析构函数允许在使用一个对象之后执行任意代码来清除内存。
提示 当PHP程序不再与对象相关时,析构函数就会被调用。通常,这会发生在执行return语句的时候。对于全局变量,这会发生于PHP脚本结束的时候。如果在程序中想明确地销毁一个对象,可以给指向该对象的变量分配任何其他值,一般将该对象变量的赋值为NULL或者调用unset()。
15.2.4 继承
对于面向对象的编程,通过继承可以增加或重写类的方法,这意味着继承类可以指定更多的属性或方法,而且允许继承类访问基类的方法。继承体现了“is_a”的关系,即“是一个”的关系。例如,Person是一个类Worker类继承自类Person类,那么可以说,Worker类也是Person类。在PHP中,可以通过关键字extends从一个类派生出继承类。如代码15-3所示的程序中,即由Person类派生出Work类。
代码15-3 PHP中的继承15-3.php
01 <?php 02 class Person 03 { 04 private $name; // 私有成员变量$name 05 06 public function set_name($name) // 成员函数set_name() 07 { 08 $this->name = $name; 09 } 10 public function get_name() // 成员函数get_name() 11 { 12 return $this->name; 13 } 14 } 15 16 class Worker extends Person //Worker 类继承自Person ,使用关键字extends 17 { 18 private $salary; 19 20 public function set_salary($salary) 21 { 22 $this->salary = $salary; 23 } 24 public function get_salary() 25 { 26 return $this->salary; 27 } 28 } 29 30 $a_work = new Worker; 31 $a_work->set_name('Paul'); // 这里调用的set_name() 继承自基类Person 32 $a_work->set_salary(3500); 33 34 $name = $a_work->get_name(); 35 $salary = $a_work->get_salary(); 36 echo $name." 的月薪为".$salary; 37 ?>
【代码解析】这段代码首先定义了Person类,然后由该类派生出Worker类。Worker类继承了基类的成员变量$anme、成员函数set_name()和get_name()。因此,由派生类Work生成的对象$a_work可以调用基类的方法set_name()为其设置名字,也可以调用基类的方法get_name()获取其名字,如代码第31行所示。
15.2.5 访问对象的属性和方法
一个对象实例的属性是变量,就像PHP的其他变量一样,只不过必须使用->运算符来引用它们,但不需要在属性前使用符号$。访问方法和访问属性一样,使用->运算符来指向实例的方法。对象的方法执行起来和普通函数几乎相同。
在前面讲述类的继承时讲到,如果一个类从另一类中继承而来,基类中的属性和方法将在派生类中都有效。即使在派生类中没有声明,基类中的方法和属性一样会在派生类中有效。如果要访问一个继承的属性,只需像访问基类自己的属性那样引用即可,使用::运算符。
在PHP的类继承用法中,有两个特殊的命名空间parent和self,parent命名空间指向父类,self命名空间指向当前类。代码15-4演示了它们的用法。
代码15-4 parent和self的用法15-4.php
01 <?php 02 class Animal // 定义动物类(基类) 03 { 04 public $blood; // 动物的热血和冷血属性 05 public $name; 06 07 public function __construct($blood,$name=NULL) // 构造函数 08 { 09 $this->blood = $blood; 10 if($name) 11 { 12 $this->name = $name; 13 } 14 } 15 } 16 17 class Mammal extends Animal // 哺乳动物,由Animal 类派生 18 { 19 public $fur_color; // 哺乳动物皮毛颜色属性 20 public $legs; 21 function __construct($fur_color,$legs,$name=NULL) // 构造函数 22 { 23 parent::__construct("warm", $name); 24 $this->fur_color = $fur_color; 25 $this->legs = $legs; 26 } 27 } 28 29 class Cat extends Mammal //Cat 类,由Mammal 派生 30 { 31 function __construct($fur_color,$name) // 构造函数 32 { 33 parent::__construct($fur_color,4,$name); 34 self::bark(); // 调用该类的另一个方法bark() 35 } 36 37 function bark() // 成员函数bark() 38 { 39 print("$this->name says, ' mew ~ mew ~ '"); 40 } 41 } 42 43 $cat_xiaobai = new Cat("white", "XiaoBai"); 44 ?>
【代码解析】这段代码显示了如何使用parent命名空间在派生类Mammal中来调用父类的构造函数,如代码第23行所示。同时也使用self在Cat类的构造函数中调用该类的另一个方法,如第34行所示。
15.2.6 PHP中类的静态成员
静态成员包括静态方法和静态属性。类的静态成员与类的一般成员不同,静态成员与类的实例无关,只与类本身有关。这意味着,类的静态属性可以由该类所有的对象所共享,它类似于函数的全局变量,只是它只属于某个固定的类,并且有访问限制。类似地,静态方法也与特定的对象无关,它类似于全局函数。静态方法可以完全访问类的属性,也可以由对象的实例来访问。
在实际开发中,如果希望在不存在有效对象的时候调用一个方法,那么就可以使用静态方法。代码15-5演示了如何在PHP的类中使用静态成员,该代码实现了一个简单的计数器。
代码15-5 PHP程序中类的静态成员15-5.php
01 <?php 02 class Counter 03 { 04 private static $count = 0; // 静态成员变量 05 06 function __construct() // 构造函数 07 { 08 echo '<b> 计数开始!</b><br/><br/>'; 09 } 10 function __destruct() // 析构函数 11 { 12 echo '<b> 计数结束!</b><br/><br/>'; 13 } 14 static function get_count() // 静态成员函数 15 { 16 return self::$count; 17 } 18 19 static function counts() 20 { 21 self::$count++; // 注意这里静态成员变量的使用方法,加self:: 22 } 23 } 24 25 $c = new Counter(); 26 $i = 0; 27 28 while($i<5) 29 { 30 Counter::counts(); // 通过限定Count:: 直接调用静态函数counts() ,并没有使用对象$c 来调 用 31 $i = Counter::get_count(); 32 echo Counter::get_count() . "<br/><br/>"; 33 } 34 ?>
图15-3 在PHP程序中使用类的静态成员
【代码解析】这段代码在Counter类中定义了一个静态属性$count,静态方法get_count()和counts()。在类的内部,使用这些成员时并没有使用$this变量,而是通过该类的命名空间,即使用self::,如代码第16行和第21行所示。在类的外部,当调用类的方法时,同样没有使用事先生成的实例对象$c,使用类似$c->get_count()的代码来调用类方法,而是通过Counter::来访问该类的静态方法,如代码第30、第31行。因为这些方法都是静态方法,并不针对每个具体实例所有,而是所有对象共享。这段代码的执行结果如图15-3所示。
15.2.7 PHP中一些和类有关的函数
一些和类有关的PHP函数在PHP中称为Magic Methods,因为它们在PHP是比较特别的,使用它们可以完成很多功能。这些函数如下所述。
·__construct():构造函数,当实例化一个类对象时调用。
·__destruct():析构函数,当一个对象不再使用时调用。
·__get():当访问某个类没有显示定义的属性时,该函数被调用。
·__set():当设置一个不存在的属性时,该函数被调用。
·__call():当访问一个不存在的方法时,该函数被调用。
·__toString():将一个对象转换成字符串。
·__clone():克隆一个对象时使用。
注意 这些函数均以双下划线开头。
前面已经介绍过构造函数__construct()和析构函数__destruct()的用法,下面简单介绍一下其他几个函数的用法。
在PHP中,每一个类都会自动继承__set()和__get()方法,定义如下。
void __set (string $name, mixed $value) mixed __get (string $name)
当程序访问当前类没有显式定义的属性时,被访问的属性名称作为参数传入相应的方法。PHP中任何类都可以定义(即重写)各自__set()和__get()方法,以实现需要的功能。代码15-6演示了这两个函数的用法。
代码15-6 在PHP中实现__set()和__get()方法15-6.php
01 <?php 02 class Test 03 { 04 public function __get($prop_name) //get() 方法 05 { 06 echo " 获取属性:( $prop_namen )<br/>"; 07 } 08 public function __set($prop_name, $value) //set() 方法 09 { 10 echo " 设置属性 $prop_name 的值为 '$value'"; 11 } 12 } 13 14 $test = new Test(); 15 $test->Name; // 对象$test 访问一个不存在的属性Name ,此时调用方法__get() 16 $test->Name = " 测试设置";// 对象$test 为一个不存在的属性Name 设置值,此时会调用__set() 方法 17 ?>
【代码解析】这段代码中,创建类时,没有定义任何属性。Test类重写了方法__get()和__set(),为了说明其用法,重写这两个函数时只是向页面输出了一些信息,并没有做更复杂的操作。图15-4是代码15-6的执行结果。
从这个执行结果可以看出,在程序访问一个不存在的属性Name时,方法__get()会被调用,如代码第15行所示。该方法向页面输出一段信息,包含有所要取得的属性,但因为这个属性没有定义过,因此输出到页面的信息会有一段空白。当程序为一个不存在的属性Name赋值时,会调用__set()方法,同样,该方法会创建一个属性Name,并为其赋值为测试设置。也向也页面输出一段信息,这段信息里包含了所创建的属性名称及其值,它们是由参数传入__set()函数的。
方法__toString()的作用是,当需要输出一个对象时,程序通过重写方法__toString()将对象转换成字符串。代码15-7演示了该函数的用法。
代码15-7 在PHP程序中使用__toString()方法15-7.php
01 <?php 02 class Student 03 { 04 private $id, $name; // 成员变量 05 06 public function __construct($s_id, $s_name) // 构造函数 07 { 08 $this->id = $s_id; 09 $this->name = $s_name; 10 } 11 public function __toString() // __toString() 方法 12 { 13 return "$this->id : $this->name"; 14 } 15 } 16 $stu = new Student(1, 'George Wesley'); 17 18 echo '<b> 以下输出对象时,实际调用了方法__toString()</b><br/><br/>'; 19 echo $stu 20 ?>
【代码解析】第11、14行定义了__toString()方法,虽然在第19行的代码中看不到__toString()的踪迹,但实际上输出对象就是调用__toString()将对象转化为字符串。代码15-7的执行结果如图15-5所示。
PHP5中默认通过引用传递对象,假设$obj1和$obj2是两个对象,使用$obj2=$obj1这样的方法复制出的对象是相互关联的,如果在程序中需要复制出一个值与原来相同的对象,但又不希望目标对象与源对象关联,那么就需要使用clone关键字,类似于,$obj2=clone$obj1。如果还希望在复制的同时,目标对象中的某些属性与源对象的不同,可以在类中定一个__clone()方法,在这个方法中完成为目标对象的属性赋以新值。代码15-8演示了如何使用__clone()。
图15-4 实现__get和__set方法
图15-5 在程序中使用__toString()方法
代码15-8 在PHP程序中使用__clone()方法15-8.php
01 <?php 02 class doClone 03 { 04 private $id,$name,$address; 05 06 public function __construct($id=0,$name='',$address='') // 构造函数 07 { 08 $this->id = $id; 09 $this->name = $name; 10 $this->address = $address; 11 } 12 public function get_id() // 成员函数get_id () 13 { 14 return $this->id; 15 } 16 public function get_name() // 成员函数get_name() 17 { 18 return $this->name; 19 } 20 public function get_address() // 成员函数get_address () 21 { 22 return $this->address; 23 } 24 public function __clone() //__clone() 方法 25 { 26 $this->id = $this->id+1; 27 $this->name = 'Kong'; 28 $this->address = "America"; 29 } 30 } 31 32 $cle = new doClone(99,'King','Island'); 33 echo '<b>clone 之前,对象 $cle 的属性:</b>'; 34 echo '<br/>'; 35 echo 'id = '.$cle->get_id() . "<br/>"; 36 echo 'name = '.$cle->get_name() . "<br/>"; 37 echo 'address = '.$cle->get_address(); 38 echo '<br/>'; 39 echo '<br/>'; 40 41 $cle_cloned = clone $cle; 42 echo '<b>clone 之后,对象 $cle 的属性:</b>'; 43 echo '<br/>'; 44 echo 'id = '.$cle->get_id() . "<br/>"; 45 echo 'name = '.$cle->get_name() . "<br/>"; 46 echo 'address = '.$cle->get_address(); 47 echo '<br/>'; 48 echo '<br/>'; 49 50 echo '<b>clone 之后,对象 $cle_cloned 的属性:</b>'; 51 echo '<br/>'; 52 echo 'id = '.$cle_cloned->get_id() . "<br/>"; 53 echo 'name = '.$cle_cloned->get_name() . "<br/>"; 54 echo 'address = '.$cle_cloned->get_address(); 55 ?>
【代码解析】这段代码的执行结果如图15-6所示。从执行结果可以看出,使用了clone之后,复制出的对象的属性在__clone方法中重新设置,并且源对象$cle和目标对象$cle_cloned之间不再有什么关系。
图15-6 在程序中使用clone
提示 方法__call()会在PHP程序访问一个不存在的方法时被调用,用法类似于__get()和__set(),这里不再赘述。
共有条评论 网友评论