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 , mixedvalue )

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只會釋放對象所占有的內存和相關的資源,對于程序員自己申請的資源,需要顯式地去釋放。通??梢园研枰尫刨Y源的操作放在析構方法中,這樣可以保證在對象被釋放的時候,程序員自己申請的資源也能被釋放。

例如,可以在構造函數中打開一個文件,然后在析構函數中關閉文件。

 <?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 新增)。

六、什么是對象克?。?/h2>

對于對象而言,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 = cloneobj1把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和文檔,如果有需要的可以

點擊進入暗號: 簡書

ziliao4.png
ziliao5.png

更多學習內容可以訪問【對標大廠】精品PHP架構師教程目錄大全,只要你能看完保證薪資上升一個臺階(持續更新)

以上內容希望幫助到大家,很多PHPer在進階的時候總會遇到一些問題和瓶頸,業務代碼寫多了沒有方向感,不知道該從那里入手去提升,對此我整理了一些資料,包括但不限于:分布式架構、高可擴展、高性能、高并發、服務器性能調優、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、Mysql優化、shell腳本、Docker、微服務、Nginx等多個知識點高級進階干貨需要的可以免費分享給大家,需要的可以加入我的PHP技術交流群953224940

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 前引 2018年最新PHP面試題大全(干貨),面試之前多看看公司的資料,可以看出面試的公司主要做什么,電商,數據庫...
    洗耳恭聽_kai閱讀 3,367評論 1 3
  • 久違的晴天,家長會。 家長大會開好到教室時,離放學已經沒多少時間了。班主任說已經安排了三個家長分享經驗。 放學鈴聲...
    飄雪兒5閱讀 7,557評論 16 22
  • 今天感恩節哎,感謝一直在我身邊的親朋好友。感恩相遇!感恩不離不棄。 中午開了第一次的黨會,身份的轉變要...
    迷月閃星情閱讀 10,607評論 0 11
  • 可愛進取,孤獨成精。努力飛翔,天堂翱翔。戰爭美好,孤獨進取。膽大飛翔,成就輝煌。努力進取,遙望,和諧家園??蓯塾巫?..
    趙原野閱讀 2,767評論 1 1
  • 在妖界我有個名頭叫胡百曉,無論是何事,只要找到胡百曉即可有解決的辦法。因為是只狐貍大家以訛傳訛叫我“傾城百曉”,...
    貓九0110閱讀 3,343評論 7 3