## 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;
}
}
~~~
效果:
![](https://img.kancloud.cn/f6/e7/f6e705d9639f820bdbe8ea50a0722dff_492x307.png)
类是一类东西的结构描述,而对象则是一类东西的一个具体实例,例如汽车这个名词可以理解为汽车的总类,但这辆汽车则是一个具体的汽车对象。
对象通过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)
~~~