構造函數以及析構函數在PHP中需要注意的地方

  • 2020 年 2 月 17 日
  • 筆記

構造函數以及析構函數在PHP中需要注意的地方

基本上所有的程式語言在類中都會有構造函數和析構函數的概念。構造函數是在函數實例創建時可以用來做一些初始化的工作,而析構函數則可以在實例銷毀前做一些清理工作。相對來說,構造函數我們使用得非常多,而析構函數則一般會用在釋放資源上,比如資料庫鏈接、文件讀寫的句柄等。

構造函數與析構函數的使用

我們先來看看正常的構造與析構函數的使用:

class A  {      public $name;      public function __construct($name)      {          $this->name = $name;          echo "A:構造函數被調用,{$this->name}", PHP_EOL;      }        public function __destruct()      {          echo "A:析構函數被調用,{$this->name}", PHP_EOL;      }  }    $a = new A('$a');  echo '-----', PHP_EOL;    class B extends A  {      public function __construct($name)      {          $this->name = $name;          parent::__construct($name);          echo "B:構造函數被調用,{$this->name}", PHP_EOL;      }        public function __destruct()      {          parent::__destruct();          echo "B:析構函數被調用,{$this->name}", PHP_EOL;      }  }    class C extends A  {      public function __construct($name)      {          $this->name = $name;          echo "C:構造函數被調用,{$this->name}", PHP_EOL;      }        public function __destruct()      {          echo "C:析構函數被調用,{$this->name}", PHP_EOL;      }  }    class D extends A  {    }  // unset($a); // $a的析構提前  // $a = null; // $a的析構提前  $b = new B('$b');    $c = new C('$c');    $d = new D('$d');    echo '-----', PHP_EOL;exit;    // A:構造函數被調用,$a  // -----  // A:構造函數被調用,$b  // B:構造函數被調用,$b  // C:構造函數被調用,$c  // A:構造函數被調用,$d  // -----  // A:析構函數被調用,$d  // C:析構函數被調用,$c  // A:析構函數被調用,$b  // B:析構函數被調用,$b  // A:析構函數被調用,$a  

上面的程式碼是不是有一些內容和我們的預期不太一樣?沒事,我們一個一個來看:

  • 子類如果重寫了父類的構造或析構函數,如果不顯式地使用parent::__constuct()調用父類的構造函數,那麼父類的構造函數不會執行,如C類
  • 子類如果沒有重寫構造或析構函數,則默認調用父類的
  • 析構函數如果沒顯式地將變數置為NULL或者使用unset()的話,會在腳本執行完成後進行調用,調用順序在測試程式碼中是類似於棧的形式先進後出(C->B->A,C先被析構),但在伺服器環境中則不一定,也就是說順序不一定固定

析構函數的引用問題

當對象中包含自身相互的引用時,想要通過設置為NULL或者unset()來調用析構函數可能會出現問題。

class E  {      public $name;      public $obj;      public function __destruct()      {          echo "E:析構函數被調用," . $this->name, PHP_EOL;          echo '-----', PHP_EOL;      }  }    $e1 = new E();  $e1->name = 'e1';  $e2 = new E();  $e2->name = 'e2';    $e1->obj = $e2;  $e2->obj = $e1;  

類似於這樣的程式碼,$e1和$e2都是E類的對象,他們又各自持有對方的引用。其實簡單點來說的話,自己持有自己的引用都會出現類似的問題。

$e1 = new E();  $e1->name = 'e1';  $e2 = new E();  $e2->name = 'e2';    $e1->obj = $e2;  $e2->obj = $e1;  $e1 = null;  $e2 = null;  // gc_collect_cycles();    $e3 = new E();  $e3->name = 'e3';  $e4 = new E();  $e4->name = 'e4';    $e3->obj = $e4;  $e4->obj = $e3;  $e3 = null;  $e4 = null;    echo 'E destory', PHP_EOL;  

如果我們不打開gc_collect_cycles()那一行的注釋,析構函數執行的順序是這樣的:

// 不使用gc回收的結果  // E destory  // E:析構函數被調用,e1  // -----  // E:析構函數被調用,e2  // -----  // E:析構函數被調用,e3  // -----  // E:析構函數被調用,e4  // -----  

如果我們打開了gc_collect_cycles()的注釋,析構函數的執行順序是:

// 使用gc回收後結果  // E:析構函數被調用,e1  // -----  // E:析構函數被調用,e2  // -----  // E destory  // E:析構函數被調用,e3  // -----  // E:析構函數被調用,e4  // -----  

可以看出,必須要讓php使用gc回收一次,確定對象的引用都被釋放了之後,類的析構函數才會被執行。引用如果沒有釋放,析構函數是不會執行的。

構造函數的低版本兼容問題

在PHP5以前,PHP的構造函數是與類名同名的一個方法。也就是說如果我有一個F類,那麼function F(){}方法就是它的構造函數。為了向低版本兼容,PHP依然保留了這個特性,在PHP7以後如果有與類名同名的方法,就會報過時警告,但不會影響程式執行。

class F  {      public function f()      {          // Deprecated: Methods with the same name as their class will not be constructors in a future version of PHP; F has a deprecated constructor          echo "F:這也是構造函數,與類同名,不區分大小寫", PHP_EOL;      }      // function F(){      //     // Deprecated: Methods with the same name as their class will not be constructors in a future version of PHP; F has a deprecated constructor      //     echo "F:這也是構造函數,與類同名", PHP_EOL;      // }      // function __construct(){      //     echo "F:這是構造函數,__construct()", PHP_EOL;      // }  }  $f = new F();  

如果__construc()和類同名方法同時存在的話,會優先走__construct()。另外需要注意的是,函數名不區分大小寫,所以F()和f()方法是一樣的都會成為構造函數。同理,因為不區分大小寫,所以f()和F()是不能同時存在的。當然,我們都不建議使用類同名的函數來做為構造函數,畢竟已經是過時的特性了,說不定哪天就被取消了。

構造函數重載

PHP是不運行方法的重載的,只支援重寫,就是子類重寫父類方法,但不能定義多個同名方法而參數不同。在Java等語言中,重載方法非常方便,特別是在類實例化時,可以方便地實現多態能力。

$r1 = new R(); // 默認構造函數  $r2 = new R('arg1'); // 默認構造函數 一個參數的構造函數重載,arg1  $r3 = new R('arg1', 'arg2'); // 默認構造函數 兩個參數的構造函數重載,arg1,arg2  

就像上述程式碼一樣,如果你嘗試定義多個__construct(),PHP會很直接地告訴你運行不了。那麼有沒有別的方法實現上述程式碼的功能呢?當然有,否則咱也不會寫了。

class R  {      private $a;      private $b;      public function __construct()      {          echo '默認構造函數', PHP_EOL;          $argNums = func_num_args();          $args = func_get_args();          if ($argNums == 1) {              $this->constructA(...$args);          } elseif ($argNums == 2) {              $this->constructB(...$args);          }      }      public function constructA($a)      {          echo '一個參數的構造函數重載,' . $a, PHP_EOL;          $this->a = $a;      }      public function constructB($a, $b)      {          echo '兩個參數的構造函數重載,' . $a . ',' . $b, PHP_EOL;          $this->a = $a;          $this->b = $b;      }  }  $r1 = new R(); // 默認構造函數  $r2 = new R('arg1'); // 默認構造函數 一個參數的構造函數重載,arg1  $r3 = new R('arg1', 'arg2'); // 默認構造函數 兩個參數的構造函數重載,arg1,arg2  

相對來說比Java之類的語言要麻煩一些,但是也確實是實現了相同的功能哦。

構造函數和析構函數的訪問限制

構造函數和析構函數默認都是public的,和類中的其他方法默認值一樣。當然它們也可以設置成private和protected。如果將構造函數設置成非公共的,那麼你將無法實例化這個類。這一點在單例模式被廣泛應用,下面我們直接通過一個單例模式的程式碼看來。

class Singleton  {      private static $instance;      public static function getInstance()      {          return self::$instance == null ? self::$instance = new Singleton() : self::$instance;      }        private function __construct()      {        }  }    $s1 = Singleton::getInstance();  $s2 = Singleton::getInstance();  echo $s1 === $s2 ? 's1 === s2' : 's1 !== s2', PHP_EOL;    // $s3 = new Singleton(); // Fatal error: Uncaught Error: Call to private Singleton::__construct() from invalid context  

當$s3想要實例化時,直接就報錯了。關於單例模式為什麼要讓外部無法實例化的問題,我們可以看看之前的設計模式系統文章中的單例模式

總結

沒想到我們天天用到的構造函數還能玩出這麼多花樣來吧,日常在開發中比較需要注意的就是子類繼承時對構造函數重寫時父類構造函數的調用問題以及引用時的析構問題。

測試程式碼:https://github.com/zhangyue0503/dev-blog/blob/master/php/201912/source/%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0%E4%BB%A5%E5%8F%8A%E6%9E%90%E6%9E%84%E5%87%BD%E6%95%B0%E5%9C%A8PHP%E4%B8%AD%E9%9C%80%E8%A6%81%E6%B3%A8%E6%84%8F%E7%9A%84%E5%9C%B0%E6%96%B9.php

參考文檔:https://www.php.net/manual/zh/language.oop5.decon.php#105368https://www.php.net/manual/zh/language.oop5.decon.php#76446https://www.php.net/manual/zh/language.oop5.decon.php#81458https://www.php.net/manual/zh/language.oop5.decon.php

=========================

關注公眾號:【硬核項目經理】獲取最新文章

添加微信/QQ好友:【DarkMatterZyCoder/149844827】

免費得PHP、項目管理學習資料