## PHP類和面向對象
1. PHP類和對象
2. 創建一個對象
3. 類的屬性
4. 定義類的方法
5. 構造函數和析構函數
6. Static靜態關鍵字
7. 訪問控制
8. 對象繼承
9. 重載
10. 對象的高級特性
* https://github.com/webjust/php
* http://www.imooc.com/learn/26
### 1. PHP類和對象 ###
* * * * *
類是面向對象程序設計的基本概念,通俗的理解類就是對現實中某一個種類的東西的抽象, 比如汽車可以抽象為一個類,汽車擁有名字、輪胎、速度、重量等屬性,可以有換擋、前進、后退等操作方法。
手冊:[類](http://php.net/manual/zh/language.oop5.basic.php)
通常定義一個汽車類的方法為:
~~~
<?php
// 定義1個汽車類
class Car {
public $name = '汽車';
public function getName() {
return $this->name;
}
}
~~~
效果:

類是一類東西的結構描述,而對象則是一類東西的一個具體實例,例如汽車這個名詞可以理解為汽車的總類,但這輛汽車則是一個具體的汽車對象。
對象通過new關鍵字進行實例化:
~~~
$car = new Car();
echo $car->getName();
~~~
類與對象看起來比較相似,但實際上有本質的區別,類是抽象的概念,對象是具體的實例。類可以使程序具有可重用性。
~~~
<?php
// $c1 = new Car(); // 在定義類前面new也可以
// 定義1個汽車類
class Car {
public $name = '汽車';
public function getName() {
return $this->name;
}
}
$c1 = new Car();
var_dump($c1);
echo $c1->getName();
$c1->name = '火車';
var_dump($c1);
echo $c1->getName();
~~~
### 2. 創建一個對象 ###
* * * * *
上一節,我們使用汽車舉例來認識了類與對象,本節我們來了解一下類的定義方法,類通過關鍵字class開頭,然后是類名與花括號,在花括號中定義類的屬性與方法。類名必須是字母或下劃線開頭,后面緊跟若干個字母、數字或下劃線,類名最好能夠表意,可以采用名詞或者英文單詞。
~~~
<?php
// 定義一個類
class Car
{
// 定義屬性
var $name = '汽車';
// 定義方法
public function getName()
{
// $this是一個偽變量,表示對象自己,用來調用對象的屬性或者方法
return $this->name;
}
}
~~~
要創建一個類的實例,可以使用new關鍵字創建一個對象。
~~~
<?php
// 定義一個類
class Car
{
// 定義屬性
var $name = '汽車';
// 定義方法
public function getName()
{
// $this是一個偽變量,表示對象自己,用來調用對象的屬性或者方法
return $this->name;
}
}
// 實例化一個類,使用new關鍵字生成一個對象。也支持使用變量生成一個對象。
$c1 = new Car();
$className = 'Car';
$c2 = new $className();
var_dump($c1);
var_dump($c2);
~~~
輸出結果:
~~~
object(Car)[1]
public 'name' => string '汽車' (length=6)
object(Car)[2]
public 'name' => string '汽車' (length=6)
~~~
### 3. 類的屬性 ###
* * * * *
在類中定義的變量稱之為屬性,通常屬性跟數據庫中的字段有一定的關聯,因此也可以稱作“字段”。屬性聲明是由關鍵字 public,protected 或者 private 開頭,后面跟一個普通的變量聲明來組成。屬性的變量可以設置初始化的默認值,默認值必須是常量。
訪問控制的關鍵字代表的意義為:
* public:公開的
* protected:受保護的
* private:私有的
~~~
class Car
{
// 定義公有屬性
public $name = '汽車';
// 定義受保護的屬性
protected $color = '#FFF';
// 定義私有屬性
private $price = '100000';
}
~~~
默認都為 `public` ,外部可以訪問。一般通過 `->` 對象操作符來訪問對象的屬性或者方法,對于靜態屬性則使用 `::` 雙冒號進行訪問。當在類成員方法內部調用的時候,可以使用 `$this` 偽變量調用當前對象的屬性。
~~~
$c1 = new Car();
echo $c1->name; // 汽車
echo $c1->color; // 語法錯誤
~~~
輸出結果:
~~~
汽車
Fatal error: Cannot access protected property Car::$color
Fatal error: Cannot access private property Car::$price
~~~
受保護的屬性與私有屬性不允許外部調用,在類的成員方法內部是可以調用的。
~~~
<?php
class Car
{
// 定義私有屬性
private $price = '100000';
// 定義1個方法,訪問私有屬性
public function getPrice()
{
// 內部訪問私有屬性,使用$this
return $this->price;
}
}
$c1 = new Car();
echo $c1->getPrice(); // 10000
~~~
### 4. 定義類的方法 ###
* * * * *
方法就是在類中的 function ,很多時候我們分不清方法與函數有什么差別,在面向過程的程序設計中 function 叫做函數,在面向對象中 function 則被稱之為方法。
同屬性一樣,類的方法也具有public,protected 以及 private 的訪問控制。
訪問控制的關鍵字代表的意義為:
* public:公開的
* protected:受保護的
* private:私有的
我們可以這樣定義方法:
使用關鍵字 `static` 修飾的,稱之為靜態方法,靜態方法不需要實例化對象,可以通過類名直接調用,操作符為雙冒號::。
~~~
<?php
class Car
{
public $speed = 0;
// 定義1個公共方法
public function getName()
{
return '汽車';
}
public static function getPrice()
{
return '100000';
}
public function speedUp()
{
$this->speed += 10;
}
}
// 實例化對象
$c1 = new Car();
echo $c1->getName(); // 公共方法,使用對象操作符 -> 訪問
echo $c1::getPrice(); // 靜態防范,需要使用操作符 ::
$c1->speedUp();
$c1->speedUp();
echo $c1->speed; // 20
~~~
### 5. 構造函數和析構函數 ###
* * * * *
PHP5可以在類中使用 `__construct()` 定義一個構造函數,具有構造函數的類,會在每次對象創建的時候調用該函數,因此常用來在對象創建的時候進行一些初始化工作。
~~~
<?php
class Car
{
private $num = 1;
public function __construct()
{
echo "構造函數被調用{$this->num}<br/>";
$this->num += 1;
}
public function objNum()
{
return $this->num ++;
}
}
// 實例化的時候,會自動調用構造函數 `__construct` , 這里會輸出字符串
$c1 = new Car(); // 構造函數被調用1
new Car(); // 構造函數被調用1
new Car(); // 構造函數被調用1
echo $c1->objNum(); // 2
echo $c1->objNum(); // 3
echo $c1->objNum(); // 4
~~~
在子類中如果定義了 `__construct` 則不會調用父類的 `__construct` ,如果需要同時調用父類的構造函數,需要使用 `parent::__construct()` 顯式的調用。
~~~
<?php
class Car
{
private $num = 1;
public function __construct()
{
echo "構造函數被調用{$this->num}<br/>";
$this->num += 1;
}
}
class Trunk extends Car
{
function __construct()
{
echo "子類構造函數被調用了<br/>";
parent::__construct();
}
}
new Trunk();
~~~
輸出內容:
~~~
子類構造函數被調用了
構造函數被調用1
~~~
同樣,PHP5支持析構函數,使用 `__destruct()` 進行定義,析構函數指的是當某個對象的所有引用被刪除,或者對象被顯式的銷毀時會執行的函數。
~~~
class Train
{
public function __construct()
{
echo "構造方法被調用了<br/>";
}
public function __destruct()
{
echo "析構方法被調用了<br/>";
}
}
$t1 = new Train();
echo "--------------<br/>";
unset($t1);
~~~
當PHP代碼執行完畢以后,會自動回收與銷毀對象,因此一般情況下不需要顯式的去銷毀對象。
### 6. Static靜態關鍵字 ###
* * * * *
靜態屬性與方法可以在不實例化類的情況下調用,直接使用 `類名::方法名` 的方式進行調用。靜態屬性不允許對象使用 `->` 操作符調用。
~~~
class Car
{
private static $speed = 100;
public static function getSpeed()
{
return self::$speed;
}
public static function speedUp()
{
return self::$speed += 10;
}
}
$c1 = new Car();
echo $c1::getSpeed(); // 100 調用靜態方法
~~~
靜態方法也可以通過變量來進行動態調用
~~~
$func = 'getSpeed';
$className = 'Car';
echo $className::$func(); // 100 動態調用靜態方法
~~~
靜態方法中,`$this` 偽變量不允許使用。可以使用 `self`,`parent` ,`static` 在內部調用靜態方法與屬性。
~~~
class bigCar extends Car
{
public static function start()
{
return parent::speedUp();
}
}
echo bigCar::start(); // 110 使用 :: 調用靜態方法
echo $c1::getSpeed(); // 110
~~~
### 7. 訪問控制 ###
* * * * *
前面的小節,我們已經接觸過訪問控制了,訪問控制通過關鍵字 `public` ,`protected` 和 `private` 來實現。被定義為公有的類成員可以在任何地方被訪問。被定義為受保護的類成員則可以被其自身以及其子類和父類訪問。被定義為私有的類成員則只能被其定義所在的類訪問。
類屬性必須定義為公有、受保護、私有之一。為兼容PHP5以前的版本,如果采用 var 定義,則被視為公有。
~~~
class Car
{
$speed = 100; // 錯誤提示,必須定義訪問控制
/*
Parse error: syntax error, unexpected '$speed' (T_VARIABLE),
expecting function (T_FUNCTION)
*/
public $name = '汽車';
}
~~~
類中的方法可以被定義為公有、私有或受保護。如果沒有設置這些關鍵字,則該方法默認為公有。
如果構造函數定義成了私有方法,則不允許直接實例化對象了,這時候一般通過靜態方法進行實例化,在設計模式中會經常使用這樣的方法來控制對象的創建,比如單例模式只允許有一個全局唯一的對象。
~~~
class Car
{
private function __construct()
{
echo "實例化對象<br/>";
}
private static $_obj = null;
public static function getInstance()
{
if (empty(self::$_obj)) {
return self::$_obj = new Car();
}
return self::$_obj;
}
}
// $c1 = new Car(); // 提示不允許直接實例化一個私有訪問控制構造函數的對象。
// Fatal error: Call to private Car::__construct() from invalid context
$c2 = Car::getInstance();
$c3 = Car::getInstance();
$c4 = Car::getInstance();
$c4 = Car::getInstance();
var_dump($c2);
var_dump($c2);
var_dump($c4);
~~~
輸出效果:
~~~
實例化對象
object(Car)[1]
object(Car)[1]
object(Car)[1]
~~~
### 8. 對象繼承 ###
* * * * *
繼承是面向對象程序設計中常用的一個特性,汽車是一個比較大的類,我們也可以稱之為基類,除此之外,汽車還分為卡車、轎車、東風、寶馬等,因為這些子類具有很多相同的屬性和方法,可以采用繼承汽車類來共享這些屬性與方法,實現代碼的復用。
建立一個Truck類,擴展Car類,并覆蓋speedUp方法,使速度累加50。
~~~
<?php
// 建立一個Truck類,擴展Car類,并覆蓋speedUp方法,使速度累加50
class Car
{
protected $speed = 100;
public function speedUp()
{
return $this->speed += 10;
}
}
// 繼承
class Truck extends Car
{
// 子類覆蓋父類的方法
public function speedUp()
{
return $this->speed += 50;
}
}
$car = new Car();
echo $car->speedUp(); //110
$truck = new Truck();
echo $truck->speedUp(); //150
~~~
### 9. 重載 ###
* * * * *
PHP中的重載指的是動態的創建屬性與方法,是通過魔術方法來實現的。
屬性的重載通過 `__set` , `__get` , `__isset` , `__unset` 來分別實現對不存在屬性的賦值、讀取、判斷屬性是否設置、銷毀屬性。
~~~
<?php
class Car
{
protected $ary = array();
public function __set($name, $value)
{
$this->ary[$name] = $value;
}
public function __get($name)
{
if (isset($this->ary[$name])) {
return $this->ary[$name];
}
return null;
}
public function __isset($name)
{
if (isset($this->ary[$name])) {
return true;
}
return false;
}
public function __unset($name)
{
unset($this->ary[$name]);
}
}
$car = new Car();
$car->name = '汽車';
echo $car->name; // 汽車
~~~
方法的重載通過 `__call` 來實現,當調用不存在的方法的時候,將會轉為參數調用 `__call` 方法,當調用不存在的靜態方法時會使用 `__callStatic` 重載。
~~~
class Truck
{
public $speed = 0;
public function __call($name, $args)
{
if ($name == 'speedUp') {
return $this->speed += 10;
}
}
}
$truck = new Truck();
echo $truck->speed; // 0
$truck->speedUp();
echo $truck->speed; // 10
~~~
### 10. 對象的高級特性 ###
* * * * *
對象比較,當同一個類的兩個實例的所有屬性都相等時,可以使用比較運算符==進行判斷,當需要判斷兩個變量是否為同一個對象的引用時,可以使用全等運算符===進行判斷。
~~~
<?php
class Car
{
//
}
$c1 = new Car();
$c2 = new Car();
echo $c1==$c2 ? "==" : "!="; // ==
echo $c1===$c2 ? "===" : "!=="; // !==
~~~
對象復制,在一些特殊情況下,可以通過關鍵字clone來復制一個對象,這時__clone方法會被調用,通過這個魔術方法來設置屬性的值。
~~~
class Truck
{
public $name = "Truck";
public function __clone()
{
$obj = new Truck();
$obj->name = $this->name;
}
}
$a = new Truck();
$a->name = "new Truck";
$b = clone $a;
var_dump($b);
~~~
輸出效果:
~~~
object(Truck)[4]
public 'name' => string 'new Truck' (length=9)
~~~
對象序列化,可以通過 `serialize` 方法將對象序列化為字符串,用于存儲或者傳遞數據,然后在需要的時候通過 `unserialize` 將字符串反序列化成對象進行使用。
~~~
class Bike
{
public $name = "自行車";
}
$bike = new Bike();
$str = serialize($bike);
var_dump($str);
$str = unserialize($str);
var_dump($str);
~~~
輸出效果:
~~~
string 'O:4:"Bike":1:{s:4:"name";s:9:"自行車";}' (length=42)
object(Bike)[6]
public 'name' => string '自行車' (length=9)
~~~