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相關的面試知識、考題能夠關注公衆號獲取:琉憶編程庫


對本文有什麼問題或建議均可以進行留言,我將不斷完善追求極致,感謝大家的支持。

相關文章
相關標籤/搜索