PHP面试常考内容之面向对象(2)

PHP面试专栏正式起更,每周1、3、五更新,提供最好最优质的PHP面试内容。
继上一篇“PHP面试常考内容之面向对象(1)”发表后,今天更新(2),须要(1)的能够直接点击文字进行跳转获取。
整个面向对象文章的结构涉及的内容模块有:php

1、面向对象与面向过程有什么区别?
2、面向对象有什么特征?
3、什么是构造函数和析构函数?
4、面向对象的做用域范围有哪几种?
5、PHP 中魔术方法有哪些?
6、什么是对象克隆?
7、this、self和parent的区别是什么?
8、抽象类与接口有什么区别与联系?
9、PHP面向对象的常考面试题讲解

关于PHP面向对象的内容将会被分为三篇文章进行讲解完整块内容,第一篇主要讲解一到四点内容,第二篇主要讲解五到八的内容,第三篇围绕第九点进行讲解。html


如下正文的内容都来自《PHP程序员面试笔试宝典》书籍,若是转载请保留出处:mysql


5、PHP种魔术方法有哪些?

在PHP中,把全部以__(两个下画线)开头的类方法保留为魔术方法。因此在定义类方法时,不建议使用 __ 做为方法的前缀。下面分别介绍每一个魔术方法的做用。程序员

1.__get、__set、__isset、__unset

这四个方法是为在类和它们的父类中没有声明的属性而设计的。
1)在访问类属性的时候,若属性能够访问,则直接返回;若不能够被访问,则调用__get 函数。
方法签名为:public mixed __get ( string $name )
2)在设置一个对象的属性时,若属性能够访问,则直接赋值;若不能够被访问,则调用__set 函数。
方法签名为:public void __set ( string $name , mixed $value )
3)当对不可访问的属性调用 isset() 或 empty() 时,__isset() 会被调用。
方法签名为:public bool __isset ( string $name )
4)当对不可访问属性调用 unset() 时,__unset() 会被调用。
方法签名为:public bool _unset ( string $name )
须要注意的是,以上存在的不可访问包括属性没有定义,或者属性的访问控制为proteced或private(没有访问权限的属性)。
下面经过一个例子把对象变量保存在另一个数组中。面试

<?php class Test { /* 保存未定义的对象变量 */ private $data = array(); public function __set($name, $value){ $this->data[$name] = $value; } public function __get($name){ if(array_key_exists($name, $this->data)) return $this->data[$name]; return NULL; } public function __isset($name){ return isset($this->data[$name]); } public function __unset($name){ unset($this->data[$name]); } } $obj = new Test; $obj->a = 1; echo $obj->a . "\n"; ?>

程序的运行结果为sql

1

2.__construct、__destruct

1)__construct 构造函数,实例化对象时被调用。
2)__destruct 析构函数,当对象被销毁时调用。一般状况下,PHP只会释放对象所占有的内存和相关的资源,对于程序员本身申请的资源,须要显式地去释放。一般能够把须要释放资源的操做放在析构方法中,这样能够保证在对象被释放的时候,程序员本身申请的资源也能被释放。
例如,能够在构造函数中打开一个文件,而后在析构函数中关闭文件。数据库

<?php class Test { protected $file = NULL; function __construct(){ $this->file = fopen("test","r"); } function __destruct(){ fclose($this->file); } } ?>

3.__call()和__callStatic()

1)__call( $method, $arg_array ):当调用一个不可访问的方法时会调用这个方法。
2)__callStatic的工做方式与 __call() 相似,当调用的静态方法不存在或权限不足时,会自动调用__callStatic()。
使用示例以下:编程

<?php class Test { public function __call ($name, $arguments) { echo "调用对象方法 '$name' ". implode(', ', $arguments). "\n"; } public static function __callStatic ($name, $arguments) { echo "调用静态方法 '$name' ". implode(', ', $arguments). "\n"; } } $obj = new Test; $obj->method1('参数1'); Test::method2('参数2'); ?>

程序的运行结果为segmentfault

调用对象方法 'method1' 参数1 
调用静态方法 'method2' 参数2

4.__sleep()和__wakeup()

1)__sleep 串行化的时候调用。
2)__wakeup 反串行化的时候调用。
也就是说,在执行serialize()和unserialize()时,会先调用这两个函数。例如,在序列化一个对象时,若是这个对象有一个数据库链接,想要在反序列化中恢复这个链接的状态,那么就能够经过重载这两个方法来实现。示例代码以下:数组

<?php class Test { public $conn; private $server, $user, $pwd, $db; public function __construct($server, $user, $pwd, $db) { $this->server = $server; $this->user = $user; $this->pwd = $pwd; $this->db = $db; $this->connect(); } private function connect() { $this->conn = mysql_connect($this->server, $this->user, $this->pwd); mysql_select_db($this->db, $this->conn); } public function __sleep() { return array('server', 'user', 'pwd', 'db'); } public function __wakeup() { $this->connect(); } public function __destruct(){ mysql_close($conn); } } ?>

5.__toString()

__toString 在打印一个对象时被调用,能够在这个方法中实现想要打印的对象的信息,使用示例以下:

<?php class Test { public $age; public function __toString() { return "age:$this->age"; } } $obj = new Test(); $obj->age=20; echo $obj; ?>

程序的运行结果为

age:20

6.__invoke()

在引入这个魔术方法后,能够把对象名看成方法直接调用,它会间接调用这个方法,使用示例以下:

<?php class Test { public function __invoke() { print "hello world"; } } $obj = new Test; $obj(); ?> 

程序的运行结果为

hello world

7.__set_state()

调用 var_export 时被调用,用__set_state的返回值做为var_export 的返回值。使用示例以下:

<?php class People { public $name; public $age; public static function __set_state ($arr) { $obj = new People; $obj->name = $arr['name']; $obj->age = $arr['aage']; return $obj; } } $p = new People; $p->age = 20; $p->name = 'James'; var_dump(var_export($p)); ?>

程序的运行结果为

People::__set_state(array( 'name' => 'James', 'age' => 20, )) NULL

8.__clone()

这个方法在对象克隆的时候被调用,php提供的__clone()方法对一个对象实例进行浅拷贝,也就是说,对对象内的基本数值类型经过值传递完成拷贝,当对象内部有对象成员变量的时候,最好重写__clone方法来实现对这个对象变量的深拷贝。使用示例以下:

<?php class People { public $age; public function __toString() { return "age:$this->age \n"; } } class MyCloneable { public $people; function __clone() { $this->people = clone $this->people; //实现对象的深拷贝 } } $obj1 = new MyCloneable(); $obj1->people = new People(); $obj1->people->age=20; $obj2 = clone $obj1; $obj2->people->age=30; echo $obj1->people; echo $obj2->people; ?>

程序的运行结果为

age:20 age:30

因而可知,经过对象拷贝后,对其中一个对象值的修改不影响另一个对象。

9.__autoload()

当实例化一个对象时,若是对应的类不存在,则该方法被调用。这个方法常常的使用方法为:在方法体中根据类名,找出类文件,而后require_one 导入这个文件。由此,就能够成功地建立对象了,使用示例以下:
Test.php:

<?php class Test { function hello() { echo 'Hello world'; } } ?>

index.php:

<?php function __autoload( $class ) { $file = $class . '.php'; if ( is_file($file) ) { require_once($file); //导入文件 } } $obj = new Test(); $obj->hello(); ?>

程序的运行结果为

Hello world

在index.php中,因为没有包含Test.php,在实例化Test对象的时候会自动调用__autoload方法,参数$class的值即为类名Test,这个函数中会把Test.php引进来,由此Test对象能够被正确地实例化。
这种方法的缺点是须要在代码中文件路径作硬编码,当修改文件结构的时候,代码也要跟着修改。另外一方面,当多个项目之间须要相互引用代码的时候,每一个项目中可能都有本身的__autoload,这样会致使两个__autoload冲突。固然能够把__autoload修改为一个。这会致使代码的可扩展性和可维护性下降。由此从PHP5.1开始引入了spl_autoload,能够经过spl_autoload_register注册多个自定义的autoload方法,使用示例以下:
index.php

<?php function loadprint( $class ) { $file = $class . '.php'; if (is_file($file)) { require_once($file); } } spl_autoload_register( 'loadprint' ); //注册自定义的autoload方法从而避免冲突 $obj = new Test(); $obj->hello(); ?>

spl_autoload是_autoload()的默认实现,它会去include_path中寻找$class_name(.php/.inc) 。除了经常使用的spl_autoload_register外,还有以下几个方法:
1)spl_autoload:_autoload()的默认实现。
2)spl_autoload_call:这个方法会尝试调用全部已经注册的__autoload方法来加载请求的类。
3)spl_autoload_functions:获取全部被注册的__autoload方法。
4)spl_autoload_register:注册__autoload方法。
5)spl_autoload_unregister:注销已经注册的__autoload方法。
6)spl_autoload_extensions:注册而且返回spl_autoload方法使用的默认文件的扩展名。

引伸:PHP有哪些魔术常量?

除了魔术变量外,PHP还定义了以下几个经常使用的魔术常量。
1)__LINE__:返回文件中当前的行号。
2)__FILE__:返回当前文件的完整路径。
3)__FUNCTION__:返回所在函数名字。
4)__CLASS__:返回所在类的名字。
5)__METHOD__:返回所在类方法的名称。与__FUNCTION__不一样的是,__METHOD__返回的是“class::function”的形式,而__FUNCTION__返回“function”的形式。
6)__DIR__:返回文件所在的目录。若是用在被包括文件中,则返回被包括的文件所在的目录(PHP 5.3.0中新增)。
7)__NAMESPACE__:返回当前命名空间的名称(区分大小写)。此常量是在编译时定义的(PHP 5.3.0 新增)。
8)__TRAIT__:返回 Trait 被定义时的名字。Trait 名包括其被声明的做用区域(PHP 5.4.0 新增)。


6、什么是对象克隆?

对于对象而言,PHP用的是引用传递,也就是说,对象间的赋值操做只是赋值了一个引用的值,而不是整个对象的内容,下面经过一个例子来讲明引用传递存在的问题:

<?php class My_Class { public $color; } $obj1 = new My_Class (); $obj1->color = "Red"; $obj2 = $obj1; $obj2->color ="Blue"; //$obj1->color的值也会变成"Blue" ?>

由于PHP使用的是引用传递,因此在执行$obj2 = $obj1后,$obj1和$obj2都是指向同一个内存区(它们在内存中的关系以下图所示),任何一个对象属性的修改对另一个对象也是可见的。

 

在不少状况下,但愿经过一个对象复制出一个同样的可是独立的对象。PHP提供了clone关键字来实现对象的复制。以下例所示:

<?php class My_Class { public $color; } $obj1 = new My_Class (); $obj1->color = "Red"; $obj2 = clone $obj1; $obj2->color ="Blue"; //此时$obj1->color的值仍然为"Red" ?>

$obj2 = clone $obj1把obj1的整个内存空间复制了一份存放到新的内存空间,而且让obj2指向这个新的内存空间,经过clone克隆后,它们在内存中的关系以下图所示。

 

此时对obj2的修改对obj1是不可见的,由于它们是两个独立的对象。
在学习C++的时候有深拷贝和浅拷贝的概念,显然PHP也存在相同的问题,经过clone关键字克隆出来的对象只是对象的一个浅拷贝,当对象中没有引用变量的时候这种方法是能够正常工做的,可是当对象中也存在引用变量的时候,这种拷贝方式就会有问题,下面经过一个例子来进行说明:

<?php class My_Class { public $color; } $c ="Red"; $obj1 = new My_Class (); $obj1->color =&$c; //这里用的是引用传递 $obj2 = clone $obj1; //克隆一个新的对象 $obj2->color="Blue"; //这时,$obj1->color的值也变成了"Blue" ?>

在这种状况下,这两个对象在内存中的关系以下图所示。

 

从上图中能够看出,虽然obj1与obj2指向的对象占用了独立的内存空间,可是对象的属性color仍然指向一个相同的存储空间,所以当修改了obj2->color的值后,意味着c的值被修改,显然这个修改对obj1也是可见的。这就是一个很是典型的浅拷贝的例子。为了使两个对象彻底独立,就须要对对象进行深拷贝。那么如何实现呢,PHP提供了相似于__clone方法(相似于C++的拷贝构造函数)。把须要深拷贝的属性,在这个方法中进行拷贝:使用示例以下:

<?php class My_Class { public $color; public function __clone() { $this->color = clone $this->color; } } $c ="Red"; $obj1 = new My_Class (); $obj1->color =&$c; $obj2 = clone $obj1; $obj2->color="Blue"; //这时,$obj1->color的值仍然为"Red" ?>

经过深拷贝后,它们在内存中的关系如图1-4所示。

经过在__clone方法中对对象的引用变量color进行拷贝,使obj1与obj2彻底占用两块独立的存储空间,对obj2的修改对obj1也不可见。


本身整理了一篇 “若是遇到代码怎么改都没效果,怎么办?”的文章,关注公众号:“ 琉忆编程库”,回复:“ 问题”,我发给你。

7、this、self和parent的区别是什么?

this、self、parent三个关键字从字面上比较好理解,分别是指这、本身、父亲。其中,this指的是指向当前对象的指针(暂用C语言里面的指针来描述),self指的是指向当前类的指针,parent指的是指向父类的指针。
如下将具体对这三个关键字进行分析。

##1.this关键字## 1 <?php 2 class UserName { 3 private $name; // 定义成员属性 4 function __construct($name) { 5 $this->name = $name; // 这里已经使用了this指针 6 } 7 // 析构函数 8 function __destruct() { 9 } 10 // 打印用户名成员函数 11 function printName() { 12 print ($this->name."\n") ; // 又使用了this指针 13 } 14 } 15 // 实例化对象 16 $nameObject = new UserName ( "heiyeluren" ); 17 // 执行打印 18 $nameObject->printName (); // 输出: heiyeluren 19 // 第二次实例化对象 20 $nameObject2 = new UserName ( "PHP5" ); 21 // 执行打印 22 $nameObject2->printName (); // 输出:PHP5 23 ?>

上例中,分别在5行和12行使用了this指针,那么this究竟是指向谁呢?其实,this是在实例化的时候来肯定指向谁,例如,第一次实例化对象的时候(16行),当时this就是指向$nameObject 对象,那么执行第12行打印的时候就把print($this->name)变成了print ($nameObject->name),输出"heiyeluren"。
对于第二个实例化对象,print( $this- >name )变成了print( $nameObject2->name ),因而就输出了"PHP5"。
因此,this就是指向当前对象实例的指针,不指向任何其余对象或类。

2.self关键字

先要明确一点,self是指向类自己,也就是self是不指向任何已经实例化的对象,通常self用来访问类中的静态变量。

1 <?php 2 class Counter { 3 // 定义属性,包括一个静态变量 4 private static $firstCount = 0; 5 private $lastCount; 6 // 构造函数 7 function __construct() { 8 // 使用self来调用静态变量,使用self调用必须使用::(域运算符号) 9 $this->lastCount = ++ selft::$firstCount; 10 } 11 // 打印lastCount数值 12 function printLastCount() { 13 print ($this->lastCount) ; 14 } 15 } 16 // 实例化对象 17 $countObject = new Counter (); 18 $countObject->printLastCount (); // 输出 1 19 ?>

上述示例中,在第4行定义了一个静态变量$firstCount,而且初始值为0,那么在第9行的时候调用了这个值,使用的是self来调用,中间使用域运算符“::”来链接,这时候调用的就是类本身定义的静态变量$firstCount,它与下面对象的实例无关,只是与类有关,没法使用this来引用,只能使用 self来引用,由于self是指向类自己,与任何对象实例无关。

3.parent关键字

parent是指向父类的指针,通常使用parent来调用父类的构造函数。

1 <?php 2 // 基类 3 class Animal { 4 // 基类的属性 5 public $name; // 名字 6 // 基类的构造函数 7 public function __construct($name) { 8 $this->name = $name; 9 } 10 } 11 // 派生类 12 class Person extends Animal // Person类继承了Animal类 13 { 14 public $personSex; // 性别 15 public $personAge; // 年龄 16 // 继承类的构造函数 17 function __construct($personSex, $personAge) { 18 parent::__construct ( "heiyeluren" ); // 使用parent调用了父类的构造函数 19 $this->personSex = $personSex; 20 $this->personAge = $personAge; 21 } 22 function printPerson() { 23 print ($this->name . " is " . $this->personSex . ",this year " . $this->personAge) ; 24 } 25 } 26 // 实例化Person对象 27 $personObject = new Person ( "male", "21" ); 28 // 执行打印 29 $personObject->printPerson (); // 输出:heiyeluren is male,this year 21 30 ?>

上例中,成员属性都是public的,特别是父类的,是为了供继承类经过this来访问。第18行: parent::__construct( "heiyeluren" ),使用了parent来调用父类的构造函数进行对父类的初始化,由于父类的成员都是public的,因而就可以在继承类中直接使用 this来访问从父类继承的属性。


8、抽象类与接口有什么区别与联系?

抽象类应用的定义以下:

abstract class ClassName{ }

抽象类具备如下特色:
1)定义一些方法,子类必须实现父类全部的抽象方法,只有这样,子类才能被实例化,不然子类仍是一个抽象类。
2)抽象类不能被实例化,它的意义在于被扩展。
3)抽象方法没必要实现具体的功能,由子类来完成。
4)当子类实现抽象类的方法时,这些方法的访问控制能够和父类中的同样,也能够有更高的可见性,可是不能有更低的可见性。例如,某个抽象方法被声明为protected的,那么子类中实现的方法就应该声明为protected或者public的,而不能声明为private。
5)若是抽象方法有参数,那么子类的实现也必须有相同的参数个数,必须匹配。但有一个例外:子类能够定义一个可选参数(这个可选参数必需要有默认值),即便父类抽象方法的声明里没有这个参数,二者的声明也无冲突。下面经过一个例子来加深理解:

<?php abstract class A{ abstract protected function greet($name); } class B extends A { public function greet($name, $how="Hello ") { echo $how.$name."\n"; } } $b = new B; $b->greet("James"); $b->greet("James","Good morning "); ?>

程序的运行结果为

Hello James Good morning James

定义抽象类时,一般须要遵循如下规则:
1)一个类只要含有至少一个抽象方法,就必须声明为抽象类。
2)抽象方法不可以含有方法体。
接口能够指定某个类必须实现哪些方法,但不须要定义这些方法的具体内容。在PHP中,接口是经过interface关键字来实现的,与定义一个类相似,惟一不一样的是接口中定义的方法都是公有的并且方法都没有方法体。接口中全部的方法都是公有的,此外接口中还能够定义常量。接口常量和类常量的使用彻底相同,可是不能被子类或子接口所覆盖。要实现一个接口,能够经过关键字implements来完成。实现接口的类中必须实现接口中定义的全部方法。虽然PHP不支持多重继承,可是一个类能够实现多个接口,用逗号来分隔多个接口的名称。下面给出一个接口使用的示例:

<?php interface Fruit { const MAX_WEIGHT = 3; //静态常量 function setName($name); function getName(); } class Banana implements Fruit { private $name; function getName() { return $this->name; } function setName($_name) { $this->name = $_name; } } $b = new Banana(); //建立对象 $b->setName("香蕉"); echo $b->getName(); echo "<br />"; echo Banana::MAX_WEIGHT; //静态常量 ?>

程序的运行结果为

香蕉
3

接口和抽象类主要有如下区别:
抽象类:PHP5支持抽象类和抽象方法。被定义为抽象的类不能被实例化。任何一个类,若是它里面至少有一个方法是被声明为抽象的,那么这个类就必须被声明为抽象的。被定义为抽象的方法只是声明了其调用方法和参数,不能定义其具体的功能实现。抽象类经过关键字abstract来声明。
接口:能够指定某个类必须实现哪些方法,但不须要定义这些方法的具体内容。在这种状况下,能够经过interface关键字来定义一个接口,在接口中声明的方法都不能有方法体。
两者虽然都是定义了抽象的方法,可是事实上二者区别仍是很大的,主要区别以下:
1)对接口的实现是经过关键字implements来实现的,而抽象类继承则是使用类继承的关键字extends实现的。
2)接口没有数据成员(能够有常量),可是抽象类有数据成员(各类类型的成员变量),抽象类能够实现数据的封装。
3)接口没有构造函数,抽象类能够有构造函数。
4)接口中的方法都是public类型,而抽象类中的方法可使用private、protected或public来修饰。
5)一个类能够同时实现多个接口,可是只能实现一个抽象类。


预告:PHP面试常考内容之面向对象(3)将于本周五(2019.2-15)更新。

以上内容摘自《PHP程序员面试笔试宝典》书籍,该书已在天猫京东当当等电商平台销售。

更多PHP相关的面试知识、考题能够关注公众号获取:琉忆编程库


对本文有什么问题或建议均可以进行留言,我将不断完善追求极致,感谢大家的支持。

相关文章
相关标签/搜索