php中不支持的OOP特性:
1.函数重载。
2.操作符重载。
3.多重继承。但支持实现多个接口。
php中对类对象的赋值号实际上是引用传递:
class a {
public $v = 3;
}
$aObj = new a();
$aCopy = $aObj;
$aCopy->v = 2;
print($aObj->v); // 输出2
但对于标量类型来说是值传递:
$v1 = 2;
$v2 = $v1;
$v2 = 1;
print($v1); // 输出2
可在对象前加clone关键字克隆对象:
class a {
public $v = 3;
}
$aObj = new a();
$aCopy = clone $aObj;
$aCopy->v = 2;
print($aObj->v); // 输出3
可在类中定义一个__clone方法调整对象的克隆行为:
class a {
public $id = 3;
public function __clone() {
++$this->id;
$this->desc = "my id is $this->id.";
}
}
$aObj = new a();
$aCopy = clone $aObj;
print($aCopy->id . "\n");
print($aCopy->desc);
运行它:
类继承:
class a {
public $id = 3;
}
class b extends a {
public $v = 1;
}
$bObj = new b();
print($bObj->id); // 输出3
子类拥有它所有父类的属性和方法。
如果派生类没有构造函数而基类有构造函数,则派生类在实例化时会执行父类的构造函数:
class a {
public $name;
public function __construct($name) {
$this->name = $name;
}
}
class b extends a { }
$bObj = new b("myName");
print($bObj->name); // 输出myName
$anotherBObj = new b(); // 会报错构造函数需要一个参数,而如果基类的构造函数不需要参数或基类没有构造函数,则不会报错
如果子类也有构造函数,当初始化子类时,无论父类是否有构造函数,都会执行子类的构造函数:
class a {
public $name;
public function __construct($name) {
$this->name = $name;
}
}
class b extends a {
public function __construct() {
print("in child's constructor");
}
}
$bObj = new b("myName");
print($bObj->name);
运行它:
可见父类的构造函数没有被执行,name成员的输出为空。派生类的构造函数会完全覆盖基类的构造函数。
php在构造对象时,即使没有显式定义构造函数也会构造成功:
class a {
public $v = 2;
}
$aObj = new a(1);
print($aObj->v); // 输出2
可在派生类中调用基类的构造函数:
class a {
public $name;
public function __construct($name) {
$this->name = $name;
}
}
class b extends a {
public function __construct($name) {
print("in child's constructor\n");
parent::__construct($name);
}
}
$bObj = new b("myName");
print($bObj->name);
运行它:
当遇到parent::__construct($name)
时,php会调用其直接父类的构造函数:
class x {
public function __construct() {
print("in x's constructor\n");
}
}
class a extends x {
public $name;
public function __construct() {
print("in a's constructor\n");
}
}
class b extends a {
public function __construct() {
print("in b's constructor\n");
parent::__construct();
}
}
$bObj = new b();
运行它:
如果x的构造函数更合适:
class x {
public function __construct($name) {
print("in x's constructor\n");
}
}
class a extends x {
public $name;
public function __construct() {
print("in a's constructor\n");
}
}
class b extends a {
public function __construct($name) {
print("in b's constructor\n");
parent::__construct($name);
}
}
$bObj = new b("test");
运行它:
如果上例中,类a的构造函数需要两个参数,则会报错a的构造函数需要两个参数,但只给了一个。
指明要调用的父类构造函数:
class x {
public function __construct($name) {
print("in x's constructor\n");
}
}
class a extends x {
public $name;
public function __construct() {
print("in a's constructor\n");
}
}
class b extends a {
public function __construct($name) {
print("in b's constructor\n");
a::__construct($name);
x::__construct($name);
}
}
$bObj = new b("test");
运行它:
如果创建一个基类的间接派生类对象时,如果此基类的直接派生类和间接派生类都调用了此基类的构造函数,则按间接派生类的构造函数的调用顺序决定最后状态:
class x {
public $name;
public function __construct($name) {
print("in x's constructor\n");
$this->name = $name;
}
}
class a extends x {
public $name;
public function __construct() {
print("in a's constructor\n");
x::__construct("ddd");
}
}
class b extends a {
public function __construct($name) {
print("in b's constructor\n");
x::__construct($name);
a::__construct($name);
}
}
$bObj = new b("test");
print($bObj->name); // 输出test
/* ----------------------another code sequence------------------------*/
class x {
public $name;
public function __construct($name) {
print("in x's constructor\n");
$this->name = $name;
}
}
class a extends x {
public $name;
public function __construct() {
print("in a's constructor\n");
x::__construct("ddd");
}
}
class b extends a {
public function __construct($name) {
print("in b's constructor\n");
a::__construct($name);
x::__construct($name); // 上例代码中先调用的x构造函数再调用的a的构造函数
}
}
$bObj = new b("test");
print($bObj->name); // 输出ddd
可能一个父类方法要与静态类属性交互,但该类属性在一个子类中被覆盖:
class a {
public static $v = 1;
}
class b extends a {
public static $v = 2;
}
$bObj = new b();
var_dump($bObj::$v); // 输出2
但如果使用父类的静态方法:
class a {
public static $v = 1;
public static function func() {
print(self::$v);
}
}
class b extends a {
public static $v = 2;
}
$bObj = new b();
var_dump($bObj::$v);
$bObj::func();
运行它:
这是由于self在编译时确定其作用域而非运行时。php 5.3后可通过static改变此行为:
class a {
public static $v = 1;
public static function func() {
print(static::$v);
}
}
class b extends a {
public static $v = 2;
}
$bObj = new b();
var_dump($bObj::$v);
$bObj::func();
运行它:
基类方法的重写:
class a {
public function func() {
print("in a's func");
}
}
class b extends a {
public function func() {
print("in b's func");
}
}
$bObj = new b();
$bObj->func(); // 调用b的func
接口:
interface Ia { // 接口名最好以I开头以表明它是接口
public function func();
}
class a implements Ia {
public function func() { } // 必须实现接口中的所有函数,否则会报错,但也可将该类声明为抽象类以避免报错
}
接口中也可定义常量:
interface Ia {
const v = 4;
public function func();
}
class a implements Ia {
// const v = 5; // 错误,不能覆盖接口中的常量
public function func() { }
}
$aObj = new a();
var_dump(a::v); // 输出4,const对象访问时与static对象相似,只能通过类名或对象名的::访问
var_dump($aObj::v); // 同上,输出4
var_dump($aObj->v); // 错误,使用->不能访问类常量,这是访问类变量的方法,而类名没有->访问符
派生类也可实现接口:
interface Ia {
const v = 4;
public function func();
}
class a implements Ia {
public function func() {
print("in a");
}
}
class b extends a {
public function func() {
print("in b");
}
}
$bObj = new b();
$bObj->func(); // 调用b的func
一个类可实现多个接口:
interface Ia {
public function func1();
}
interface Ib {
public function func2();
}
interface Ic {
public function func3();
}
class abc implements Ia, Ib, Ic {
// 必须实现三个接口中的全部函数
}
抽象类不能被实例化,只能作为基类:
abstract class a { } // 正确,抽象类中可以没有抽象方法
$aObj = new a(); // 错误,抽象类不能实例化
abstract class a {
abstract public function func() { } // 错误,抽象方法不能有函数体
}
抽象类的非抽象派生类必须实现该抽象类中的所有抽象方法:
abstract class a {
abstract public function func();
}
class b extends a { } // 错误,没有重写抽象方法func
使用抽象类还是接口:
1.如果所有类都会共享一个行为的实现,则使用抽象类并在其中实现该行为,接口中无法实现行为,因为接口中不能包含函数体。
2.如果要从多个来源继承行为,则使用接口,php不能继承自多个类。
随着不断创建类库和使用第三方类库,可能会出现两个类库使用了相同类名的情况,此时如果在脚本中同时require这两个库会报错。
从php 5.3开始,引入了命名空间,可在各库文件的最前面加上:
namespace 命名空间名;
之后在引用库文件时:
require "库文件"
use 命名空间名 as 短名; // 如果命名空间名太长或想更改命名空间名可使用它
$obj = new 短名\类名();