最接地氣的一套PHP面試題(完結)(總結不易,踩坑踩到掛了好多面試)

MC811-MM118發表於2021-05-17

上個星期,宅在家裡碼了下面這些,覺得有必要針對上一篇文字再說名一些面試要求要掌握的知識。(這也許就是我宅的原因,這也許是我沒有Nan朋友的原因,這也許…..嗚嗚!)

什麼是物件克隆?
對於物件而言,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指的是指向父類的指標。
以下將具體對這三個關鍵字進行分析。

##this關鍵字##
<?php
class UserName {
    private $name;    // 定義成員屬性
    function __construct($name) {
        $this->name = $name; // 這裡已經使用了this指標
    }

    // 解構函式
    function __destruct() {

    }

    // 列印使用者名稱成員函式
    function printName() {
        print ($this->name."
") ; // 又使用了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."
";
        }

    }

    $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)一個類可以同時實現多個介面,但是隻能實現一個抽象類。

終於做了個全面的總結:關於面試掌握的基礎技能。

本作品採用《CC 協議》,轉載必須註明作者和本文連結
如果覺得我寫的不錯,記得和我交流,其實我也有很多不懂,嘻嘻!

相關文章