14.2 PHP与对象

14.2 PHP与对象

视频讲解:光盘TMlx14PHP与对象.exe

14.2.1 类的定义

和很多面向对象的语言一样,PHP也是通过class关键字加类名来定义类的。类的格式如下:

<?php class SportObject{ //定义运动类 … } ?>

上述大括号中间的部分是类的全部内容,如上述SportObject就是一个最简单的类。SportObject类仅有一个类的骨架,什么功能都没有实现,但这并不影响它的存在。

注意

一个类,即一对大括号之间的全部内容都要在一段代码段中,即一个“<?php … ?>”之间不能分割成多块,例如:

<?php class SportObject{ //定义运动类 … ?> <?php … } ?>

这种格式是不允许的。

14.2.2 成员方法

类中的函数被称为成员方法。函数和成员方法唯一的区别就是,函数实现的是某个独立的功能,而成员方法是实现类中的一个行为,是类的一部分。

下面就创建在图14.1中编写的运动类,并添加成员方法。将类命名为SportObject类,并添加打篮球的成员方法beatBasketball()。代码如下:

<?php class SportObject{ function beatBasketball()($name, $height, $avoirdupois, $age, $sex){ //声明成员方法 echo"姓名:".$name; //方法实现的功能 echo"身高:".$height; //方法实现的功能 echo"年龄:".$age; //方法实现的功能 } } ?>

该方法的作用是输出申请打篮球人的基本信息,包括姓名、身高和年龄。这些信息是通过方法的参数传进来的。

14.2.3 类的实例化

类的方法已经添加,接下来就使用方法,但使用方法不像使用函数那么简单。首先要对类进行实例化,实例化是通过关键字new来声明一个对象。然后使用如下格式来调用要使用的方法:

对象名 -> 成员方法

在14.1节中已经讲过,类是一个抽象的描述,是功能相似的一组对象的集合。如果想用到类中的方法或变量,首先就要把它具体落实到一个实体,也就是对象上。

【例14.1】以SportObject类为例,实例化一个对象并调用方法beatBasketball。实例代码如下:(实例位置:光盘TMsl141)

<?php class SportObject{ function beatBasketball($name, $height, $avoirdupois, $age, $sex){ //声明成员方法 if($height>180 and $avoirdupois<=100){ return$name.",符合打篮球的要求!"; //方法实现的功能 }else{ return$name.",不符合打篮球的要求!"; //方法实现的功能 } } } $sport=new SportObject(); echo $sport->beatBasketball(’明日’, '185', '80', '20周岁’, ’男’); ?>

结果为:明日,符合打篮球的要求!

说明

实例14.1创建了图14.1中的运动类,同时也完成了图14.2中对类的实例化操作,最终输出方法判断的结果。

14.2.4 成员变量

类中的变量,也称为成员变量(也有称为属性或字段的)。成员变量用来保存信息数据,或与成员方法进行交互来实现某项功能。

定义成员变量的格式为:

关键字 成员变量名

说明

关键字可以使用public、private、protected、static和final中的任意一个。在14.2.9节之前,所有的实例都使用public来修饰。对于关键字的使用,将在14.2.9节和14.2.10节中进行介绍。

访问成员变量和访问成员方法是一样的。只要把成员方法换成成员变量即可,格式为:

对象名 -> 成员变量

【例14.2】以图14.1和图14.2中描述的类和类的实例化为例,将其通过代码实现。首先定义运动类SportObject,声明3个成员变量$name、$height和$avoirdupois。然后定义一个成员方法bootFootBall,用于判断申请的运动员是否适合这个运动项目。最后,实例化类,通过实例化返回对象调用指定的方法,根据运动员填写的参数,判断申请的运动员是否符合要求。实例代码如下:(实例位置:光盘TMsl142)

<?php class SportObject{ public$name; //定义成员变量 public$height; //定义成员变量 public$avoirdupois; //定义成员变量 public function bootFootBall($name, $height, $avoirdupois){ //声明成员方法 $this->name=$name; $this->height=$height; $this->avoirdupois=$avoirdupois; if($this->height<185 and $this->avoirdupois<85){ return$this->name.",符合踢足球的要求!"; //方法实现的功能 }else{ return$this->name.",不符合踢足球的要求!"; //方法实现的功能 } } } $sport=new SportObject(); //实例化类,并传递参数 echo$sport->bootFootBall(’明日’, '185', '80'); //执行类中的方法 ?>

结果为:明日,不符合踢足球的要求!

说明

“$this->”的作用是调用本类中的成员变量或成员方法,这里只要知道含义即可,在14.2.8节中将介绍相关的知识。

注意

无论是使用“$this->”还是使用“对象名->”的格式,后面的变量是没有$符号的,如“$this->beatBasketBall”“$sport-> beatBasketBall”。这是一个出错概率很高的错误。

14.2.5 类常量

既然有变量,当然也会有常量。常量就是不会改变的量,是一个恒值。圆周率是众所周知的一个常量。定义常量使用关键字const,如:

const PI= 3.14159;

【例14.3】本例先声明一个常量,再声明一个变量,实例化对象后分别输出两个值。实例代码如下:(实例位置:光盘TMsl143)

<?php class SportObject{ const BOOK_TYPE=’计算机图书’; //声明常量BOOK_TYPE public$object_name; //声明变量,用来存放商品名称 function setObjectName($name){ //声明方法setObjectName $this->object_name=$name; //设置成员变量值 } function getObjectName(){ //声明方法getObjectName return $this -> object_name; } } $c_book=new SportObject(); //实例化对象 $c_book->setObjectName("PHP类"); //调用方法setObjectName echo SportObject::BOOK TYPE."->"; //输出常量BOOK_TYPE echo$c_book->getObjectName(); //调用方法getObjectName ?>

结果为:计算机图书 -> PHP类

可以发现,常量的输出和变量的输出是不一样的。常量不需要实例化对象,直接由“类名+常量名”调用即可。常量输出的格式为:

类名::常量名

说明

类名和常量名之间的两个冒号“::”称为作用域操作符,使用这个操作符可以在不创建对象的情况下调用类中的常量、变量和方法。关于作用域操作符,将在14.2.8节中进行介绍。

14.2.6 构造方法和析构方法

1.构造方法

当一个类实例化一个对象时,可能会随着对象初始化一些成员变量。如例14.2中的SportObject类,现在再添加一些成员变量,类的形式如下:

class SportObject{ public$name; //定义姓名成员变量 public$height; //定义身高成员变量 public$avoirdupois; //定义体重成员变量 public$age; //定义年龄成员变量 public$sex; //定义性别成员变量 }

声明一个SportObject类的对象,并对这个类的一些成员变量赋初值。代码如下:

$sport=new SportObject(’明日’, '185', '80', '20', ’男’); //实例化类,并传递参数 $sport->name="明日 "; //为成员变量赋值 $sport->height=185; //为成员变量赋值 $sport->avoirdupois=80; //为成员变量赋值 $sport->age=20; //为成员变量赋值 $sport->sex="男"; //为成员变量赋值 echo$sport->bootFootBall(); //执行方法

可以看到,如果赋初值比较多,写起来就比较麻烦。为此,PHP引入了构造方法。构造方法是生成对象时自动执行的成员方法,作用就是初始化对象。该方法可以没有参数,也可以有多个参数。构造方法的格式如下:

void __construct([mixed args [, …]])

注意

函数中的“__”是两条下划线“_”。

【例14.4】本例重写了SportObject类和bootFootBall成员方法,下面通过具体实例查看重写后的对象在使用上有哪些不一样。实例代码如下:(实例位置:光盘TMsl144)

<?php class SportObject{ public$name; //定义成员变量 public$height; //定义成员变量 public$avoirdupois; //定义成员变量 public$age; //定义成员变量 public$sex; //定义成员变量 public function__construct($name, $height, $avoirdupois, $age, $sex){ //定义构造方法 $this->name=$name; //为成员变量赋值 $this->height=$height; //为成员变量赋值 $this->avoirdupois=$avoirdupois; //为成员变量赋值 $this->age=$age; //为成员变量赋值 $this->sex=$sex; //为成员变量赋值 } public function bootFootBall(){ //声明成员方法 if($this->height<185 and $this->avoirdupois<85){ return$this->name.",符合踢足球的要求!"; //方法实现的功能 }else{ return$this->name.",不符合踢足球的要求!"; //方法实现的功能 } } } $sport=new SportObject(’明日’, '185', '80', '20', ’男’); //实例化类,并传递参数 echo$sport->bootFootBall(); //执行类中的方法 ?>

结果为:明日,不符合踢足球的要求!

可以看到,重写后的类,在实例化对象时只需一条语句即可完成赋值。

说明

构造方法是初始化对象时使用的。如果类中没有构造方法,那么PHP会自动生成一个。自动生成的构造方法没有任何参数,没有任何操作。

2.析构方法

析构方法的作用和构造方法正好相反,是对象被销毁时被调用的,作用是释放内存。析构方法的格式为:

void destruct (void)

【例14.5】本例首先声明一个对象car,然后再销毁对象。可以看出,使用析构方法十分简单。实例代码如下:(实例位置:光盘TMsl145)

<?php class SportObject{ public$name; //定义姓名成员变量 public$height; //定义身高成员变量 public$avoirdupois; //定义体重成员变量 public$age; //定义年龄成员变量 public$sex; //定义性别成员变量 public function__construct($name, $height, $avoirdupois, $age, $sex){ //定义构造方法 $this->name=$name; //为成员变量赋值 $this->height=$height; //为成员变量赋值 $this->avoirdupois=$avoirdupois; //为成员变量赋值 $this->age=$age; //为成员变量赋值 $this->sex=$sex; //为成员变量赋值 } public function bootFootBall(){ //声明成员方法 if($this->height<185 and $this->avoirdupois<85){ return$this->name.",符合踢足球的要求!"; //方法实现的功能 }else{ return$this->name.",不符合踢足球的要求!"; //方法实现的功能 } } function__destruct(){ //析构方法 echo "<p><b>对象被销毁,调用析构方法。</b></p>"; } } $sport=new SportObject(’明日’, '185', '80', '20', ’男’); //实例化类,并传递参数 //unset($sport); ?>

结果为:对象被销毁,调用析构方法。

说明

PHP使用的是一种“垃圾回收”机制,自动清除不再使用的对象,释放内存。就是说即使不使用unset()函数,析构方法也会自动被调用,这里只是明确一下析构函数在何时被调用。一般情况下是不需要手动创建析构方法的。

14.2.7 继承和多态的实现

继承和多态最根本的作用就是完成代码的重用。下面就来介绍PHP的继承和多态。

1.继承

子类继承父类的所有成员变量和方法,包括构造函数,当子类被创建时,PHP会先在子类中查找构造方法。如果子类有自己的构造方法,PHP会先调用子类中的方法。当子类中没有时,PHP则去调用父类中的构造方法,这就是继承。

例如,在14.1节中通过图片展示了一个运动类,在这个运动类中包含很多个方法,代表不同的体育项目,各种体育项目的方法中有公共的属性。例如,姓名、性别、年龄……但还会有许多不同之处,例如,篮球对身高的要求、举重对体重的要求……如果都由一个SportObject类来生成各个对象,除了那些公共属性外,其他属性和方法则需自己手动来写,工作效率得不到提高。这时,可以使用面向对象中的继承来解决这个难题。

下面来看如何通过PHP中的继承来解决上述问题。继承是通过关键字extends来声明的,继承的格式如下:

class subClass extends superClass{ … }

说明

subClass为子类名称,superClass为父类名称。

【例14.6】本例用SportObject类生成了两个子类:BeatBasketBall和WeightLifting,两个子类使用不同的构造方法实例化了两个对象beatbasketball和weightlifting,并输出信息。实例代码如下:(实例位置:光盘TMsl146)

<?php /* 父类 */ class SportObject{ public$name; //定义姓名成员变量 public$age; //定义年龄成员变量 public$avoirdupois; //定义体重成员变量 public$sex; //定义性别成员变量 public function__construct($name, $age, $avoirdupois, $sex){ //定义构造方法 $this->name=$name; //为成员变量赋值 $this->age=$age; //为成员变量赋值 $this->avoirdupois=$avoirdupois; //为成员变量赋值 $this->sex=$sex; //为成员变量赋值 } function showMe(){ //在父类中定义方法 echo ’这句话不会显示。'; } } /* 子类BeatBasketBall */ class BeatBasketBall extends SportObject{ //定义子类,继承父类 public$height; //定义身高成员变量 function__construct($name, $height){ //定义构造方法 $this->height=$height; //为成员变量赋值 $this->name=$name; //为成员变量赋值 } function showMe(){ //定义方法 if($this->height>185){ return$this->name.",符合打篮球的要求!"; //方法实现的功能 }else{ return$this->name.",不符合打篮球的要求!"; //方法实现的功能 } } } /* 子类WeightLifting */ class WeightLifting extends SportObject{ //继承父类 function showMe(){ //定义方法 if($this->avoirdupois<85){ return$this->name.",符合举重的要求!"; //方法实现的功能 }else{ return$this->name.",不符合举重的要求!"; //方法实现的功能 } } } //实例化对象 $beatbasketball=new BeatBasketBall(’科技’, '190'); //实例化子类 $weightlifting = new WeightLifting(’明日’, '185', '80', '20', ’男’); echo$beatbasketball->showMe()."<br>"; //输出结果 echo $weightlifting->showMe()."<br>"; ?>

运行结果如图14.3所示。

图14.3 继承的实现

2.多态

多态好比有一个成员方法让大家去游泳,这个时候有的人带游泳圈,有的人拿浮板,还有的人什么也不带。虽是同一种方法,却产生了不同的形态,就是多态。

多态存在两种形式:覆盖和重载。

(1)所谓覆盖就是在子类中重写父类的方法,而在两个子类的对象中虽然调用的是父类中相同的方法,但返回的结果是不同的。例如,在例14.6中,在两个子类中都调用了父类中的方法showMe,但是返回的结果却不同。

(2)重载,是类的多态的另一种实现。函数重载指一个标识符被用作多个函数名,且能够通过函数的参数个数或参数类型将这些同名的函数区分开来,调用不发生混淆。其好处是可实现代码重用,不用为了对不同的参数类型或参数个数而写多个函数。

多个函数使用同一个名字,但参数个数、参数数据类型不同。调用时,虽然方法名称相同,但根据参数个数或者参数数据类型不同自动调用对应的函数。

下面看一个重载的简单实例,根据传递的参数个数不同,调用不同的方法,返回不同的值。

<?php class C{ function__call($name, $num){ //调用不存在的方法 echo"方法名称:". $name. "<p>"; //输出方法名 echo"参数存在个数:". count($num). "<p>"; //输出参数个数 if(count($num)==1){ //根据参数个数调用不同的方法 echo $this->list1($a); } if(count($num)==2){ //根据参数个数调用不同的方法 echo $this->list2($a, $b); } } public function list1($a){ //定义方法 return "这是list1函数"; } public function list2($a, $b){ //定义方法 return "这是list2函数"; } } $a=new C; //类的实例化 $a->listshow(1,2); //调用方法,传递参数 ?>

14.2.8 “$this ->”和“::”的使用

通过例14.6可以发现,子类不仅可以调用自己的变量和方法,也可以调用父类中的变量和方法,那么对于其他不相关的类成员同样可以调用。

PHP是通过伪变量“$this ->”和作用域操作符“::”来实现这些功能的,这两个符号在前面的学习中都有过简单的介绍。本节将详细讲解两者的使用。

1.“$this->”

在14.2.3节“类的实例化”中,对如何调用成员方法有了基本的了解,那就是用对象名加方法名,格式为“对象名->方法名”。但在定义类时(如SportObject类),根本无法得知对象的名称是什么。这时如果想调用类中的方法,就要用伪变量“$this->”。$this的意思就是本身,所以“$this->”只可以在类的内部使用。

【例14.7】当类被实例化后,$this同时被实例化为本类的对象,这时对$this使用get_class()函数,将返回本类的类名。实例代码如下:(实例位置:光盘TMsl147)

<?php class example{ //创建类example function exam(){ //创建成员方法 if(isset($this)){ //判断变量$this是否存在 echo'$this的值为:'.get class($this); //如果存在,输出$this所属类的名字 }else{ echo '$this未定义’; } } } $class_name=new example(); //实例化对象$class_name $class_name->exam(); //调用方法exam ?>

结果为:$this的值为:example

说明

get_class()函数返回对象所属类的名字,如果不是对象,则返回false。

2.操作符“::”

相比伪变量$this只能在类的内部使用,操作符“::”更为强大。操作符“::”可以在没有声明任何实例的情况下访问类中的成员方法或成员变量。使用“::”操作符的通用格式为:

关键字::变量名/常量名/方法名

这里的关键字分为以下3种情况。

parent:可以调用父类中的成员变量、成员方法和常量。

self:可以调用当前类中的静态成员和常量。

类名:可以调用本类中的变量、常量和方法。

【例14.8】本例依次使用了类名、parent和self关键字来调用变量和方法。读者可以观察输出的结果。实例代码如下:(实例位置:光盘TMsl148)

<?php class Book{ const NAME='computer'; //常量NAME function__construct(){ //构造方法 echo’本月图书类冠军为:'.Book::NAME.''; //输出默认值 } } class l_book extends Book{ //Book类的子类 const NAME='foreign language'; //声明常量 function__construct(){ //子类的构造方法 parent::__construct(); //调用父类的构造方法 echo’本月图书类冠军为:'.self::NAME.''; //输出本类中的默认值 } } $obj=new l_book(); //实例化对象 ?>

结果为:本月图书类冠军为:computer本月图书类冠军为:foreign language

说明

关于静态变量(方法)的声明及使用可参考14.2.10节相关内容。

14.2.9 数据隐藏

细心的读者看到这里,一定会有一个疑问:面向对象编程的特点之一是封装性,即数据隐藏。可在前面的学习中并没有突出这一点。对象中的所有变量和方法可以随意调用,甚至不用实例化也可以使用类中的方法、变量。这就是面向对象吗?

这当然不算是真正的面向对象。如果读者是从本章第一节来开始学习的,一定还会记得在14.2.4节讲成员变量时所提到的那几个关键字:public、private、protected、static和final。这就是用来限定类成员(包括变量和方法)的访问权限的。本节先来学习前3个。

说明

成员变量和成员方法在关键字的使用上都是一样的。这里只以成员变量为例说明几种关键字的不同用法。对于成员方法同样适用。

1.public(公共成员)

顾名思义,就是可以公开的、没有必要隐藏的数据信息。可以在程序中的任何位置(类内、类外)被其他的类和对象调用。子类可以继承和使用父类中所有的公共成员。

在本章的前半部分,所有的变量都被声明为public,而所有的方法在默认状态下也是public。所以对变量和方法的调用显得十分混乱。为了解决这个问题,就需要使用第二个关键字private。

2.private(私有成员)

被private关键字修饰的变量和方法,只能在所属类的内部被调用和修改,不可以在类外被访问。在子类中也不可以。

【例14.9】在本例中,对私有变量$name的修改与访问,只能通过调用成员方法来实现。如果直接调用私有变量,将会发生错误。实例代码如下:(实例位置:光盘TMsl149)

<?php class Book{ private$name='computer'; //声明私有变量$name public function setName($name){ //设置私有变量方法 $this -> name = $name; } public function getName(){ //读取私有变量方法 return $this -> name; } } class LBook extends Book{ //Book类的子类 } $lbook=new LBook(); //实例化对象 echo’正确操作私有变量的方法:'; //正确操作私有变量 $lbook -> setName("PHP5从入门到应用开发"); echo $lbook -> getName(); echo'<br>直接操作私有变量的结果:'; //错误操作私有变量 echo Book::$name; ?>

运行结果如图14.4所示。

图14.4 private关键字

说明

对于成员方法,如果没有写关键字,那么默认就是public。从本节开始,以后所有的方法及变量都会带上关键字,这是一种良好的书写习惯。

3.protected(保护成员)

private关键字可以将数据完全隐藏起来,除了在本类外,其他地方都不可以调用,子类也不可以。对于有些变量希望子类能够调用,但对另外的类来说,还要做到封装。这时,就可以使用protected。

被protected修饰的类成员,可以在本类和子类中被调用,其他地方则不可以被调用。

【例14.10】本例首先声明一个protected变量,然后使用子类中的方法调用一次,最后在类外直接调用一次,观察一下运行结果。实例代码如下:(实例位置:光盘TMsl1410)

<?php class Book{ protected$name='computer'; //声明保护变量$name } class LBook extends Book{ //Book类的子类 public function showMe(){ echo ’对于protected修饰的变量,在子类中是可以直接调用的。如:$name = '.$this -> name; } } $lbook=new LBook(); //实例化对象 $lbook -> showMe(); echo'<p>但在其他的地方是不可以调用的,否则:'; //对私有变量进行操作 $lbook -> name = 'history'; ?>

运行结果如图14.5所示。

图14.5 protected关键字

说明

虽然PHP中没有对修饰变量的关键字做强制性的规定和要求,但从面向对象的特征和设计方面考虑,一般使用private或protected关键字来修饰变量,以防止变量在类外被直接修改和调用。

14.2.10 静态变量(方法)

不是所有的变量(方法)都要通过创建对象来调用。可以通过给变量(方法)加上static关键字来直接调用。调用静态成员的格式为:

关键字::静态成员

关键字可以是:

self,在类内部调用静态成员时所使用。

静态成员所在的类名,在类外调用类内部的静态成员时所用。

注意

在静态方法中,只能调用静态变量,不能调用普通变量,而普通方法则可以调用静态变量。

使用静态成员,除了可以不需要实例化对象,另一个作用就是在对象被销毁后,仍然保存被修改的静态数据,以便下次继续使用。这个概念比较抽象,下面结合一个实例说明。

【例14.11】本例首先声明一个静态变量$num,声明一个方法,在方法的内部调用静态变量,然后给变量加1。依次实例化这个类的两个对象,并输出方法。可以发现两个对象中的方法返回的结果有了一些联系。直接使用类名输出静态变量,看有什么效果。实例代码如下:(实例位置:光盘TMsl1411)

<?php class Book{ //Book类 static$num=0; //声明一个静态变量$num,初值为0 public function showMe(){ //声明一个方法 echo’您是第’.self::$num.’位访客’; //输出静态变量 self::$num++; //将静态变量加1 } } $book1=new Book(); //实例化对象$book1 $book1->showMe(); //调用对象$book1的showMe方法 echo "<br>"; $book2=new Book(); //实例化对象$book2 $book2->showMe(); //调用对象$book2的showMe方法 echo "<br>"; echo’您是第’.Book::$num.’位访客’; //直接使用类名调用静态变量 ?>

运行结果如图14.6所示。

图14.6 静态变量的使用

如果将程序代码中的静态变量改为普通变量,如“private $num = 0; ”,那么结果就不一样了。读者可以动手试一试。

说明

静态成员不用实例化对象,当类第一次被加载时就已经分配了内存空间,所以直接调用静态成员的速度要快一些。但如果静态成员声明得过多,空间一直被占用,反而会影响系统的功能。这个尺度只能通过实践积累,才能真正地掌握。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

微信扫一扫

微信扫一扫

微信扫一扫,分享到朋友圈

14.2 PHP与对象