当前位置: 首页 > 文章教程  > 计算机与互联网 > 网络编程

15.2PHP中的类和对象

9/17/2020 9:31:19 PM 人评论

15.2PHP中的类和对象

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(),这里不再赘述。

相关教程

共有条评论 网友评论

验证码: 看不清楚?