關於一些php面試之物件導向的相關知識

MC811-MM118發表於2021-05-14

物件導向分上下篇,這裡上篇涉及到的內容有:一、物件導向與程式導向有什麼區別? 二、物件導向有什麼特徵? 三、什麼是建構函式和解構函式? 四、物件導向的作用域範圍有哪幾種? 五、PHP 中魔術方法有哪些?

一、物件導向與程式導向有什麼區別?
物件導向是當今軟體開發方法的主流方法之一,它是把資料及對資料的操作方法放在一起,作為一個相互依存的整體,即物件。對同類物件抽象出其共性,即類,類中的大多數資料,只能被本類的方法進行處理。類通過一個簡單的外部介面與外界發生關係,物件與物件之間通過訊息進行通訊。程式流程由使用者在使用中決定。例如,站在抽象的角度,人類具有身高、體重、年齡、血型等一些特稱,人類會勞動、會直立行走、會吃飯、會用自己的頭腦去創造工具等這些方法,人類僅僅只是一個抽象的概念,它是不存在的實體,但是所有具備人類這個群體的屬性與方法的物件都稱為人,這個物件人是實際存在的實體,每個人都是人這個群體的一個物件。

而程式導向是一種以事件為中心的開發方法,就是自頂向下順序執行,逐步求精,其程式結構是按功能劃分為若干個基本模組,這些模組形成一個樹狀結構,各模組之間的關係也比較簡單,在功能上相對獨立,每一模組內部一般都是由順序、選擇和迴圈三種基本結構組成,其模組化實現的具體方法是使用子程式,而程式流程在寫程式時就已經決定。例如五子棋,程式導向的設計思路就是首先分析問題的步驟:第一步,開始遊戲;第二步,黑子先走;第三步,繪製畫面;第四步,判斷輸贏;第五步,輪到白子;第六步,繪製畫面;第七步,判斷輸贏;第八步,返回步驟二;第九步,輸出最後結果。把上面每個步驟用分別的函式來實現,就是一個程式導向的開發方法。

具體而言,二者主要有以下幾個方面的不同之處。
1)出發點不同。物件導向是用符合常規思維方式來處理客觀世界的問題,強調把問題域的要領直接對映到物件及物件之間的介面上。而程式導向方法則不然,它強調的是過程的抽象化與模組化,它是以過程為中心構造或處理客觀世界問題的。

2)層次邏輯關係不同。物件導向方法則是用計算機邏輯來模擬客觀世界中的物理存在,以物件的集合類作為處理問題的基本單位,儘可能地使計算機世界向客觀世界靠攏,以使問題的處理更清晰直接,物件導向方法是用類的層次結構來體現類之間的繼承和發展。程式導向方法處理問題的基本單位是能清晰準確地表達過程的模組,用模組的層次結構概括模組或模組間的關係與功能,把客觀世界的問題抽象成計算機可以處理的過程。

3)資料處理方式與控制程式方式不同。物件導向方法將資料與對應的程式碼封裝成一個整體,原則上其他物件不能直接修改其資料,即物件的修改只能由自身的成員函式完成,控制程式方式上是通過“事件驅動”來啟用和執行程式。而程式導向方法是直接通過程式來處理資料,處理完畢後即可顯示處理結果,在控制程式方式上是按照設計呼叫或返回程式,不能自由導航,各模組之間存在著控制與被控制、呼叫與被呼叫。

4)分析設計與編碼轉換方式不同。物件導向方法貫穿軟體生命週期的分析、設計及編碼之間是一種平滑過程,從分析到設計再到編碼是採用一致性的模型表示,即實現的是一種無縫連線。而程式導向方法強調分析、設計及編碼之間按規則進行轉換,貫穿軟體生命週期的分析、設計及編碼之間,實現的是一種有縫的連線。

二、物件導向有什麼特徵?
物件導向的主要特徵有抽象、繼承、封裝和多型。
1)抽象。抽象就是忽略一個主題中與當前目標無關的那些方面,以便更充分地注意與當前目標有關的方面。抽象並不打算了解全部問題,而只是選擇其中的一部分,暫時不用部分細節。抽象包括兩個方面,一是過程抽象,二是資料抽象。

2)繼承。繼承是一種聯結類的層次模型,並且允許和鼓勵類的重用,它提供了一種明確表述共性的方法。物件的一個新類可以從現有的類中派生,這個過程稱為類繼承。新類繼承了原始類的特性,新類稱為原始類的派生類(子類),而原始類稱為新類的基類(父類)。派生類可以從它的基類那裡繼承方法和例項變數,並且子類可以修改或增加新的方法使之更適合特殊的需要。

3)封裝。封裝是指將客觀事物抽象成類,每個類對自身的資料和方法實行保護。類可以把自己的資料和方法只讓可信的類或者物件操作,對不可信的資訊進行隱藏。

4)多型。多型是指允許不同類的物件對同一訊息做出響應。多型包括引數化多型和包含多型。多型性語言具有靈活、抽象、行為共享、程式碼共享的優勢,很好地解決了應用程式函式同名問題。

三、什麼是建構函式和解構函式?
1.建構函式
在PHP5之前的版本,建構函式的名字必須與類的名字相同,而從PHP5開始,開發者可以定義一個名為construct的方法作為建構函式。建構函式的作用就是當類被例項化的時候會被自動呼叫,因此建構函式主要用於做一些初始化的工作。使用construct作為建構函式名字的一個好處是,當類名修改的時候,不需要修改建構函式的名字。它的宣告形式為
void __construct ([ mixed $args [, $… ]] )

在C++語言中,子類的建構函式會隱式地呼叫父類的無引數的建構函式。但是在PHP中,子類的建構函式不會隱式地去呼叫父類的建構函式,需要開發者通過parent::__construct()來顯式地去呼叫父類的建構函式。當子類沒有定義建構函式的時候,它會繼承父類的建構函式,但前提是父類的建構函式不能被定義為private。使用示例如下:

<?php    

    class BaseClass {

        function __construct() {

            print "Base constructor
";

        }

    }

    class SubClass extends BaseClass {

        function __construct() {

            parent::__construct();

            print "Sub constructor
";

        }

    }

    // 會呼叫父類建構函式

    $obj = new BaseClass();

    //呼叫子類建構函式,子類建構函式會去呼叫父類建構函式

    $obj = new SubClass();

?>

程式的執行結果為

Base constructor
Base constructor
Sub constructor

從上面的講解中可以發現,從PHP5開始多了一種建構函式定義的方法。為了實現不同版本PHP程式碼的相容,在PHP5的類中找不到 __construct() 函式並且也沒有從父類繼承一個的話,那麼它就會嘗試尋找舊式的建構函式(與類同名的函式)。這種相容的方法存在一個風險:在PHP5之前的版本中開發的類中已有一個名為 __construct() 的方法卻被用於其他用途時,PHP5的類會認為這是一個建構函式,從而當類例項化時自動執行這個方法。

從 PHP 5.3.3 開始,在名稱空間中,與類名同名的方法不再作為建構函式。這一改變不影響不在名稱空間中的類。

2.解構函式
解構函式是在PHP5引入的,它的作用與呼叫時機和建構函式剛好相反,它在物件被銷燬時自動執行。解構函式__destruct()結構形式如下:

function __destruct(){

    /* 類的初始化程式碼*/

}

需要注意的是,解構函式是由系統自動呼叫的,因此,它不需要引數。
預設情況下,系統僅釋放物件屬性所佔用的記憶體,並不銷燬在物件內部申請的資源(例如,開啟檔案、建立資料庫的連線等),而利用解構函式在使用一個物件之後執行程式碼來清除這些在物件內部申請的資源(關閉檔案、斷開與資料庫的連線)。

與建構函式類似,如果想在子類中呼叫父類的解構函式,那麼需要顯式地呼叫:parent::__destruct()。如果子類沒有定義解構函式,那麼它會繼承父類的解構函式。

當物件不再被引用時,將呼叫解構函式。如果要明確地銷燬一個物件,那麼可以給指向物件的變數不分配任何值,通常將變數賦值為NULL或者用unset()函式。示例程式碼如下:

<?php 

    class des{
        function __destruct(){
            echo "物件被銷燬,執行解構函式<br>";
        }

    }

    $p=new des(); /* 例項化類 */
    echo "程式開始<br>";
    unset($p); /* 銷燬變數$p */
    echo "程式結束";

?>

四、物件導向的作用域範圍有哪幾種?
在PHP5中,類的屬性或者方法主要有public、protected和private三種類作用域,它們的區別如下:
1)public(公有型別)表示全域性,類內部、外部和子類都可以訪問。
預設的訪問許可權為public,也就是說,如果一個方法沒有被public、protected或private修飾,那麼它預設的作用域為public。

2)protected(受保護型別)表示受保護的,只有本類或子類可以訪問。
在子類中,可以通過self::var或self::method訪問,也可以通過parent::method來呼叫父類中的方法。
在類的例項化物件中,不能通過$obj->var來訪問protected型別的方法或屬性。

3)private(私有型別)表示私有的,只有本類內部可以使用。
該型別的屬性或方法只能在該類中使用,在該類的例項、子類、子類的例項中都不能呼叫私有型別的屬性和方法。

五、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 . "
";

?>

程式的執行結果為

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). "
";

  }

     public static function __callStatic ($name, $arguments) {
        echo "呼叫靜態方法 '$name' ". implode(', ', $arguments). "
";

     }
  }

  $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 
";

     }

  }

  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 新增)。
以上是我總結的的全部物件導向的內容,希望對新人的學習有幫助;如果覺得有用,記得點個贊。

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

相關文章