php面試題之面向物件(二)
繼上一篇“php面試題之面向物件(一)”發表後,今天繼續更新。
整個面向物件文章的結構涉及的內容模組有:
一、面向物件與面向過程有什麼區別?
二、面向物件有什麼特徵?
三、什麼是建構函式和解構函式?
四、面向物件的作用域範圍有哪幾種?
五、PHP 中魔術方法有哪些?
六、什麼是物件克隆?
七、this、self和parent的區別是什麼?
八、抽象類與介面有什麼區別與聯絡?
關於PHP面向物件的內容將會被分為兩篇文章進行講解完整塊內容,第一篇主要講解一到四點內容,第二篇主要講解五到八的內容。
五、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"; ?>
程式的執行結果為
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');
?>
程式的執行結果為
呼叫物件方法 '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 新增)。
六、什麼是物件克隆?
對於物件而言,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也不可見。
七、this、self和parent的區別是什麼?
this、self、parent三個關鍵字從字面上比較好理解,分別是指這、自己、父親。其中,this指的是指向當前物件的指標(暫用C語言裡面的指標來描述),self指的是指向當前類的指標,parent指的是指向父類的指標。
以下將具體對這三個關鍵字進行分析。
1、this關鍵字
<?php
class UserName {
private $name; // 定義成員屬性
function __construct($name) {
$this->name = $name; // 這裡已經使用了this指標
}
// 解構函式
function __destruct() {
}
// 列印使用者名稱成員函式
function printName() {
print ($this->name."\n") ; // 又使用了this指標
}
}
// 例項化物件
$nameObject = new UserName ( "heiyeluren" );
// 執行列印
$nameObject->printName (); // 輸出: heiyeluren
// 第二次例項化物件
$nameObject2 = new UserName ( "PHP5" );
// 執行列印
$nameObject2->printName (); // 輸出:PHP5
?>
上例中,分別在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用來訪問類中的靜態變數。
<?php
class Counter {
// 定義屬性,包括一個靜態變數
private static $firstCount = 0;
private $lastCount;
// 建構函式
function __construct() {
// 使用self來呼叫靜態變數,使用self呼叫必須使用::(域運算子號)
$this->lastCount = ++ selft::$firstCount;
}
// 列印lastCount數值
function printLastCount() {
print ($this->lastCount) ;
}
}
// 例項化物件
$countObject = new Counter ();
$countObject->printLastCount (); // 輸出 1
?>
上述示例中,在第4行定義了一個靜態變數$ firstCount,並且初始值為0,那麼在第9行的時候呼叫了這個值,使用的是self來呼叫,中間使用域運算子“::”來連線,這時候呼叫的就是類自己定義的靜態變數$firstCount,它與下面物件的例項無關,只是與類有關,無法使用this來引用,只能使用 self來引用,因為self是指向類本身,與任何物件例項無關。
3、parent關鍵字
parent是指向父類的指標,一般使用parent來呼叫父類的建構函式。
<?php
// 基類
class Animal {
// 基類的屬性
public $name; // 名字
// 基類的建構函式
public function __construct($name) {
$this->name = $name;
}
}
// 派生類
class Person extends Animal // Person類繼承了Animal類
{
public $personSex; // 性別
public $personAge; // 年齡
// 繼承類的建構函式
function __construct($personSex, $personAge) {
parent::__construct ( "heiyeluren" ); // 使用parent呼叫了父類的建構函式
$this->personSex = $personSex;
$this->personAge = $personAge;
}
function printPerson() {
print ($this->name . " is " . $this->personSex . ",this year " . $this->personAge) ;
}
}
// 例項化Person物件
$personObject = new Person ( "male", "21" );
// 執行列印
$personObject->printPerson (); // 輸出:heiyeluren is male,this year 21
?>
上例中,成員屬性都是public的,特別是父類的,是為了供繼承類通過this來訪問。第18行: parent::__construct( "heiyeluren" ),使用了parent來呼叫父類的建構函式進行對父類的初始化,因為父類的成員都是public的,於是就能夠在繼承類中直接使用 this來訪問從父類繼承的屬性。
八、抽象類與介面有什麼區別與聯絡?
抽象類應用的定義如下:
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 "
";
echo Banana::MAX_WEIGHT; //靜態常量
?>
程式的執行結果為
香蕉
3
介面和抽象類主要有以下區別:
抽象類:PHP5支援抽象類和抽象方法。被定義為抽象的類不能被例項化。任何一個類,如果它裡面至少有一個方法是被宣告為抽象的,那麼這個類就必須被宣告為抽象的。被定義為抽象的方法只是聲明瞭其呼叫方法和引數,不能定義其具體的功能實現。抽象類通過關鍵字abstract來宣告。
介面:可以指定某個類必須實現哪些方法,但不需要定義這些方法的具體內容。在這種情況下,可以通過interface關鍵字來定義一個介面,在介面中宣告的方法都不能有方法體。
二者雖然都是定義了抽象的方法,但是事實上兩者區別還是很大的,主要區別如下:
1)對介面的實現是通過關鍵字implements來實現的,而抽象類繼承則是使用類繼承的關鍵字extends實現的。
2)介面沒有資料成員(可以有常量),但是抽象類有資料成員(各種型別的成員變數),抽象類可以實現資料的封裝。
3)介面沒有建構函式,抽象類可以有建構函式。
4)介面中的方法都是public型別,而抽象類中的方法可以使用private、protected或public來修飾。
5)一個類可以同時實現多個介面,但是隻能實現一個抽象類。
點關注,不迷路
好了各位,以上就是這篇文章的全部內容了,能看到這裡的人呀,都是人才。之前說過,PHP方面的技術點很多,也是因為太多了,實在是寫不過來,寫過來了大家也不會看的太多,所以我這裡把它整理成了PDF和文件,如果有需要的可以
更多學習內容可以訪問【對標大廠】精品PHP架構師教程目錄大全,只要你能看完保證薪資上升一個臺階(持續更新)
以上內容希望幫助到大家,很多PHPer在進階的時候總會遇到一些問題和瓶頸,業務程式碼寫多了沒有方向感,不知道該從那裡入手去提升,對此我整理了一些資料,包括但不限於:分散式架構、高可擴充套件、高效能、高併發、伺服器效能調優、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、Mysql優化、shell指令碼、Docker、微服務、Nginx等多個知識點高階進階乾貨需要的可以免費分享給大家,需要的可以加入我的PHP技術交流群953224940