ホームページ  >  記事  >  バックエンド開発  >  PHP 面接でよく使用されるオブジェクト指向の知識の概要 (例付き)

PHP 面接でよく使用されるオブジェクト指向の知識の概要 (例付き)

不言
不言転載
2019-02-13 15:30:148347ブラウズ

この記事は、PHP の面接でよく使われるオブジェクト指向の知識をまとめたもの (例付き) です。一定の参考価値があります。困っている友人は参考にしてください。お役に立てれば幸いです。

関連おすすめ:「2019年PHP面接質問まとめ(集)

前回記事「オブジェクト指向php面接」に続き、関連知識はじめに 』が公開され、本日更新されます。

オブジェクト指向記事全体の構造に関係するコンテンツ モジュールは次のとおりです:

1. オブジェクト指向とプロセス指向の違いは何ですか?
2. オブジェクト指向の特徴は何ですか?
3. コンストラクターとデストラクターとは何ですか?
4. オブジェクト指向スコープにはどのような種類がありますか?
5. PHP の魔法のメソッドとは何ですか?
6. オブジェクトのクローン作成とは何ですか? ####7. 自分と親の違いは何でしょうか?
8. 抽象クラスとインターフェイスの違いと関係は何ですか?
9. PHP オブジェクト指向の面接でよくある質問の解説

PHP オブジェクト指向に関する内容を 3 つの記事に分けて全内容を説明します。最初の記事では主に 1 ~ 4 つのポイントについて説明します。 2 部では主に 5 から 8 までの内容を説明し、3 部では 9 番目の内容を中心に説明します。

次のテキストの内容は、

「PHP プログラマー面接筆記試験ガイド」 書籍から引用しています。転載する場合は、出典を保管してください:

5 . PHP のマジックメソッドはどれですか?

PHP では、__ (アンダースコア 2 つ) で始まるすべてのクラス メソッドをマジック メソッドとして保持します。したがって、クラス メソッドを定義する場合、メソッドの接頭辞として __ を使用することはお勧めできません。各マジックメソッドの機能を以下に紹介します。

1. __get、__set、__isset、__unset


これら 4 つのメソッドは、クラスおよびその親クラスで宣言されていないプロパティ用に設計されています。



#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)
上記のアクセス不能には、定義されていない属性、または属性のアクセス制御が保護またはプライベート (アクセス権のない属性) が含まれていることに注意してください。 )。
次は、オブジェクト変数を別の配列に保存する例です。

<?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 はオブジェクトが占有しているメモリと関連リソースのみを解放しますが、プログラマ自身が要求したリソースは明示的に解放する必要があります。通常、リソースを解放する必要がある操作はデストラクター メソッドに配置できます。これにより、オブジェクトが解放されるときに、プログラマが申請したリソースも解放できるようになります。
たとえば、コンストラクターでファイルを開いてから、デストラクターでファイルを閉じることができます。
rreeee

3. __call() および __callStatic()

1) __call( $method, $arg_array ): このメソッドは、アクセスできないメソッドが呼び出されたときに呼び出されます。


2) __callStatic は __call() と同様に機能します。呼び出された静的メソッドが存在しないか、権限が不十分な場合、__callStatic() が自動的に呼び出されます。

使用例は次のとおりです。

<?php  
  class Test
  {
     protected $file = NULL;
     function __construct(){
        $this->file = fopen("test","r");
     }
     function __destruct(){
        fclose($this->file);
     }
  }
?>
プログラムの実行結果は次のとおりです。

オブジェクト メソッド 'method1' パラメータ 1 を呼び出します。
静的メソッド 'method2' パラメータ 2 を呼び出します。

4. __sleep() および __wakeup()

1) __sleep はシリアル化中に呼び出されます。

2) __wakeup は逆シリアル化中に呼び出されます。
つまり、serialize() と unserialize() を実行すると、これら 2 つの関数が最初に呼び出されます。たとえば、オブジェクトをシリアル化するときに、オブジェクトにデータベース接続があり、逆シリアル化中に接続のステータスを復元したい場合は、これら 2 つのメソッドをオーバーロードすることでこれを実現できます。サンプル コードは次のとおりです:

    <?php       class Test 
      {
         public function __call ($name, $arguments) {
            echo "调用对象方法 &#39;$name&#39; ". implode(&#39;, &#39;, $arguments). "\n";
    }
         public static function __callStatic ($name, $arguments) {
            echo "调用静态方法 &#39;$name&#39; ". implode(&#39;, &#39;, $arguments). "\n";
         }
      }
      $obj = new Test;
      $obj->method1('参数1');
      Test::method2('参数2');
    ?>

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 &#39;Hello world&#39;;
    }
  }
?>

index.php:

<?php   function __autoload( $class ) {
    $file = $class . &#39;.php&#39;;  
    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 . &#39;.php&#39;;  
    if (is_file($file)) {  
        require_once($file);  
    } 
  } 
spl_autoload_register( &#39;loadprint&#39; );   //注册自定义的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 面接でよく使用されるオブジェクト指向の知識の概要 (例付き)

在很多情况下,希望通过一个对象复制出一个一样的但是独立的对象。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克隆后,它们在内存中的关系如下图所示。

PHP 面接でよく使用されるオブジェクト指向の知識の概要 (例付き)

此时对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"
?>

在这种情况下,这两个对象在内存中的关系如下图所示。

PHP 面接でよく使用されるオブジェクト指向の知識の概要 (例付き)

从上图中可以看出,虽然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所示。

PHP 面接でよく使用されるオブジェクト指向の知識の概要 (例付き)

通过在__clone方法中对对象的引用变量color进行拷贝,使obj1与obj2完全占用两块独立的存储空间,对obj2的修改对obj1也不可见。

七、this、self和parent的区别是什么?

this、self、parent三个关键字从字面上比较好理解,分别是指这、自己、父亲。其中,this指的是指向当前对象的指针(暂用C语言里面的指针来描述),self指的是指向当前类的指针,parent指的是指向父类的指针。
以下将具体对这三个关键字进行分析。

##1.this关键字##
  1 <?php   2     class UserName {
  3         private $name;    // 定义成员属性
  4         function __construct($name) {
  5             $this->name = $name; // 这里已经使用了this指针
  6         }
  7         // 析构函数
  8         function __destruct() {
  9         }
 10          // 打印用户名成员函数
 11          function printName() {
 12              print ($this->name."\n") ; // 又使用了this指针
 13          }
 14     }
 15     // 实例化对象
 16     $nameObject = new UserName ( "heiyeluren" );
 17     // 执行打印
 18     $nameObject->printName (); // 输出: heiyeluren
 19     // 第二次实例化对象
 20     $nameObject2 = new UserName ( "PHP5" );
 21     // 执行打印
 22     $nameObject2->printName (); // 输出:PHP5
 23 ?>

上例中,分别在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用来访问类中的静态变量。

 1 <?php   2      class Counter {
  3          // 定义属性,包括一个静态变量
  4          private  static  $firstCount = 0;
  5          private  $lastCount;
  6          // 构造函数
  7          function __construct() {
  8              // 使用self来调用静态变量,使用self调用必须使用::(域运算符号)
  9              $this->lastCount = ++ selft::$firstCount;
 10          }
 11          // 打印lastCount数值
 12          function printLastCount() {
 13              print ($this->lastCount) ;
 14          }
 15      }
 16       // 实例化对象
 17      $countObject = new Counter ();
 18      $countObject->printLastCount (); // 输出 1
 19 ?>

上述示例中,在第4行定义了一个静态变量$firstCount,并且初始值为0,那么在第9行的时候调用了这个值,使用的是self来调用,中间使用域运算符“::”来连接,这时候调用的就是类自己定义的静态变量$firstCount,它与下面对象的实例无关,只是与类有关,无法使用this来引用,只能使用 self来引用,因为self是指向类本身,与任何对象实例无关。

3.parent关键字

parent是指向父类的指针,一般使用parent来调用父类的构造函数。

 1 <?php   2       // 基类
  3       class Animal {
  4           // 基类的属性
  5           public $name; // 名字
  6           // 基类的构造函数
  7           public function __construct($name) {
  8              $this->name = $name;
  9           }
 10       }
 11        // 派生类
 12       class Person extends Animal  // Person类继承了Animal类
 13       {
 14           public $personSex; // 性别
 15           public $personAge; // 年龄
 16           // 继承类的构造函数
 17           function __construct($personSex, $personAge) {
 18                parent::__construct ( "heiyeluren" ); // 使用parent调用了父类的构造函数
 19                $this->personSex = $personSex;
 20                $this->personAge = $personAge;
 21           }
 22           function printPerson() {
 23                print ($this->name . " is " . $this->personSex . ",this year " . $this->personAge) ;
 24           }
 25       }
 26       // 实例化Person对象
 27       $personObject = new Person ( "male", "21" );
 28       // 执行打印
 29       $personObject->printPerson (); // 输出:heiyeluren is male,this year 21
 30 ?>

上例中,成员属性都是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 "<br>";
  echo Banana::MAX_WEIGHT;   //静态常量
?>

程序的运行结果为

香蕉
 3

接口和抽象类主要有以下区别:
抽象类:PHP5支持抽象类和抽象方法。被定义为抽象的类不能被实例化。任何一个类,如果它里面至少有一个方法是被声明为抽象的,那么这个类就必须被声明为抽象的。被定义为抽象的方法只是声明了其调用方法和参数,不能定义其具体的功能实现。抽象类通过关键字abstract来声明。
接口:可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容。在这种情况下,可以通过interface关键字来定义一个接口,在接口中声明的方法都不能有方法体。
二者虽然都是定义了抽象的方法,但是事实上两者区别还是很大的,主要区别如下:
1)对接口的实现是通过关键字implements来实现的,而抽象类继承则是使用类继承的关键字extends实现的。
2)接口没有数据成员(可以有常量),但是抽象类有数据成员(各种类型的成员变量),抽象类可以实现数据的封装。
3)接口没有构造函数,抽象类可以有构造函数。
4)接口中的方法都是public类型,而抽象类中的方法可以使用private、protected或public来修饰。
5)一个类可以同时实现多个接口,但是只能实现一个抽象类。


以上がPHP 面接でよく使用されるオブジェクト指向の知識の概要 (例付き)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はsegmentfault.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。