类和对象

来源:互联网 发布:华兴资本的包凡 知乎 编辑:程序博客网 时间:2024/06/06 01:45

class

每个类的定义都以关键字 class 开头,后面跟着类名,后面跟着一对花括号,里面包含有类的属性与方法的定义。
一个类可以包含有属于自己的常量,变量(称为“属性”)以及函数(称为“方法”)。

<?phpclass SimpleClass{    // property declaration    public $var = 'a default value';    // method declaration    public function displayVar() {        echo $this->var;    }}?>

当一个方法在类定义内部被调用时,有一个可用的伪变量 thisthis 是一个到主叫对象的引用(通常是该方法所从属的对象,但如果是从第二个对象静态调用时也可能是另一个对象)。

new 创建一个类的实例
在类定义内部,可以用 new self 和 new parent 创建新对象。
当把一个对象已经创建的实例赋给一个新变量时,新变量会访问同一个实例,就和用该对象赋值一样。此行为和给函数传递入实例时一样。可以用克隆给一个已创建的对象建立一个新实例。

Example #4 对象赋值<?php$instance = new SimpleClass();$assigned   =  $instance;$reference  =& $instance;$instance->var = '$assigned will have this value';$instance = null; // $instance and $reference become nullvar_dump($instance);var_dump($reference);var_dump($assigned);?>以上例程会输出:NULLNULLobject(SimpleClass)#1 (1) {   ["var"]=>     string(30) "$assigned will have this value"}

extends

一个类可以在声明中用 extends 关键字继承另一个类的方法和属性。
注意: PHP不支持多重继承,一个类只能继承一个基类。

属性

类的变量成员叫做“属性”,或者叫“字段”、“特征”,在本文档统一称为“属性”。属性声明是由关键字 public,protected 或者 private 开头,然后跟一个普通的变量声明来组成。属性中的变量可以初始化,但是初始化的值必须是常数,这里的常数是指 PHP 脚本在编译阶段时就可以得到其值,而不依赖于运行时的信息才能求值。

在类的成员方法里面,可以用 ->(对象运算符):this>propertyproperty访::self::property 来访问。

类常量

可以把在类中始终保持不变的值定义为常量。在定义和使用常量的时候不需要使用 $ 符号。
常量的值必须是一个定值,不能是变量,类属性,数学运算的结果或函数调用。

<?phpclass MyClass{    const constant = 'constant value';    function showConstant() {        echo  self::constant . "\n";    }}echo MyClass::constant . "\n";

类的自动加载

spl_autoload_register() 函数可以注册任意数量的自动加载器,当使用尚未被定义的类(class)和接口(interface)时自动去加载。通过注册自动加载器,脚本引擎在 PHP 出错失败前有了最后一个机会加载所需的类。

函数实例化时引用路径文件

<?phpspl_auto_register(function ($class_name)) {    require_once $class_name.'.php';}$obj = new MyClass1();$obj2 = new MyClass2();

构造函数

构造函数
__construct()

析构函数
__destruct()

<?phpclass MyDestructableClass {   function __construct() {       print "In constructor\n";       $this->name = "MyDestructableClass";   }   function __destruct() {       print "Destroying " . $this->name . "\n";   }}$obj = new MyDestructableClass();?>

创建支持多个参数的构造函数

<?phpclass A{    function __construct()    {        $a = func_get_args();        $i = func_num_args();        if (method_exists($this,$f='__construct'.$i)) {            call_user_func_array(array($this,$f),$a);        }    }    function __construct1($a1)    {        echo('__construct with 1 param called: '.$a1.PHP_EOL);    }    function __construct2($a1,$a2)    {        echo('__construct with 2 params called: '.$a1.','.$a2.PHP_EOL);    }    function __construct3($a1,$a2,$a3)    {        echo('__construct with 3 params called: '.$a1.','.$a2.','.$a3.PHP_EOL);    }}$o = new A('sheep');$o = new A('sheep','cat');$o = new A('sheep','cat','dog');// results:// __construct with 1 param called: sheep// __construct with 2 params called: sheep,cat// __construct with 3 params called: sheep,cat,dog?>

访问控制

对属性或方法的访问控制,是通过在前面添加关键字 public(公有),protected(受保护)或 private(私有)来实现的。被定义为公有的类成员可以在任何地方被访问。被定义为受保护的类成员则可以被其自身以及其子类和父类访问。被定义为私有的类成员则只能被其定义所在的类访问。
类属性必须定义为公有,受保护,私有之一。如果用 var 定义,则被视为公有。如果没有设置这些关键字,则该方法默认为公有

对象继承

extends

继承已为大家所熟知的一个程序设计特性,PHP 的对象模型也使用了继承。继承将会影响到类与类,对象与对象之间的关系。

比如,当扩展一个类,子类就会继承父类所有公有的和受保护的方法。除非子类覆盖了父类的方法,被继承的方法都会保留其原有功能。

继承对于功能的设计和抽象是非常有用的,而且对于类似的对象增加新功能就无须重新再写这些公用的功能。

Note: 除非使用了自动加载,否则一个类必须在使用之前被定义。如果一个类扩展了另一个,则父类必须在子类之前被声明。此规则适用于类继承其它类与接口。 

后期静态绑定

准确说,后期静态绑定工作原理是存储了在上一个“非转发调用”(non-forwarding call)的类名。当进行静态方法调用时,该类名即为明确指定的那个(通常在 :: 运算符左侧部分);当进行非静态方法调用时,即为该对象所属的类。所谓的“转发调用”(forwarding call)指的是通过以下几种方式进行的静态调用:self::,parent::,static:: 以及 forward_static_call()。可用 get_called_class() 函数来得到被调用的方法所在的类名,static:: 则指出了其范围。

后期静态绑定的用法

后期静态绑定本想通过引入一个新的关键字表示运行时最初调用的类来绕过限制。简单地说,这个关键字能够让你在上述例子中调用 test() 时引用的类是 B 而不是 A。最终决定不引入新的关键字,而是使用已经预留的 static 关键字。

self:: 的限制 ¶使用 self:: 或者 __CLASS__ 对当前类的静态引用,取决于定义当前方法所在的类:Example #1 self:: 用法<?phpclass A {    public static function who() {        echo __CLASS__;    }    public static function test() {        self::who();    }}class B extends A {    public static function who() {        echo __CLASS__;    }}B::test();?>以上例程会输出:A后期静态绑定的用法 ¶后期静态绑定本想通过引入一个新的关键字表示运行时最初调用的类来绕过限制。简单地说,这个关键字能够让你在上述例子中调用 test() 时引用的类是 B 而不是 A。最终决定不引入新的关键字,而是使用已经预留的 static 关键字。Example #2 static:: 简单用法<?phpclass A {    public static function who() {        echo __CLASS__;    }    public static function test() {        static::who(); // 后期静态绑定从这里开始    }}class B extends A {    public static function who() {        echo __CLASS__;    }}B::test();?>以上例程会输出:B

后期静态绑定的解析会一直到取得一个完全解析了的静态调用为止。另一方面,如果静态调用使用 parent:: 或者 self:: 将转发调用信息。

Example #4 转发和非转发调用<?phpclass A {    public static function foo() {        static::who();    }    public static function who() {        echo __CLASS__."\n";    }}class B extends A {    public static function test() {        A::foo();        parent::foo();        self::foo();    }    public static function who() {        echo __CLASS__."\n";    }}class C extends B {    public static function who() {        echo __CLASS__."\n";    }}C::test();?>以上例程会输出:ACC

抽象类

PHP 5 支持抽象类和抽象方法。定义为抽象的类不能被实例化。任何一个类,如果它里面至少有一个方法是被声明为抽象的,那么这个类就必须被声明为抽象的。被定义为抽象的方法只是声明了其调用方式(参数),不能定义其具体的功能实现。
继承一个抽象类的时候,子类必须定义父类中的所有抽象方法;另外,这些方法的访问控制必须和父类中一样(或者更为宽松)。例如某个抽象方法被声明为受保护的,那么子类中实现的方法就应该声明为受保护的或者公有的,而不能定义为私有的。此外方法的调用方式必须匹配,即类型和所需参数数量必须一致。例如,子类定义了一个可选参数,而父类抽象方法的声明里没有,则两者的声明并无冲突。 这也适用于 PHP 5.4 起的构造函数。在 PHP 5.4 之前的构造函数声明可以不一样的。

对象接口

使用接口(interface),可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容。 接口是通过interface关键字来定义的,就像定义一个标准的类一样,但其中定义所有的方法都是空的。
接口中定义的所有方法都必须是公有,这是接口的特性。

实现(implements)

要实现一个接口,使用 implements 操作符。类中必须实现接口中定义的所有方法,否则会报一个致命错误。类可以实现多个接口,用逗号来分隔多个接口的名称。

Note:实现多个接口时,接口中的方法不能有重名。Note:接口也可以继承,通过使用 extends 操作符。Note:类要实现接口,必须使用和接口中所定义的方法完全一致的方式。否则会导致致命错误。 

常量

接口中也可以定义常量。接口常量和类常量的使用完全相同,但是不能被子类或子接口所覆盖。

Trait

自 PHP 5.4.0 起,PHP 实现了一种代码复用的方法,称为 trait。

Trait 是为类似 PHP 的单继承语言而准备的一种代码复用机制。Trait 为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用 method。Trait 和 Class 组合的语义定义了一种减少复杂性的方式,避免传统多继承和 Mixin 类相关典型问题。

Trait 和 Class 相似,但仅仅旨在用细粒度和一致的方式来组合功能。 无法通过 trait 自身来实例化。它为传统继承增加了水平特性的组合;也就是说,应用的几个 Class 之间不需要继承。

总而言之:Trait就是所有的类和接口的集合,即类和接口能用的Trait都可以用

<?phptrait ezcReflectionReturnInfo {    function getReturnType() { /*1*/ }    function getReturnDescription() { /*2*/ }}class ezcReflectionMethod extends ReflectionMethod {    use ezcReflectionReturnInfo;    /* ... */}class ezcReflectionFunction extends ReflectionFunction {    use ezcReflectionReturnInfo;    /* ... */}

优先级

从基类继承的成员会被trait插入的成员所覆盖。优先顺序是来自当前类的成员覆盖率trait的方法,而trait则覆盖了被继承的方法

<?phpclass Base{    public function sayHello(){        echo 'Hello ';    }}trait SayWorld {    public function sayHello(){        parent::sayHello();        echo 'World!';    }}class MyHello extends Base{    use SayWorld;}$o = new MyHelloWorld();$o->sayHello();以上例程会输出:Hello World!

Example #3 另一个优先级顺序的例子

<?phptrait HelloWorld {    public function sayHello() {        echo 'Hello World!';    }}class TheWorldIsNotEnough {    use HelloWorld;    public function sayHello() {        echo 'Hello Universe!';    }}$o = new TheWorldIsNotEnough();$o->sayHello();?>以上例程会输出:Hello Universe!

加深理解trait优先级

注:在trait继承中,优先顺序依次是:来自当前类的成员覆盖了 trait 的方法,而 trait 则覆盖了被继承的方法。理解:即trait会覆盖父类方法,而当前类方法又会覆盖掉trait       For example:     class A{    public function sayHello(){    echo "A";    }    }    trait B{    public function sayHello(){       parent::sayHello();    echo "B";    }    }    class C extends A{    use B;    }    $c=new C();    $c->sayHello();   上述打印结果是:AB    class A{    public function sayHello(){    echo "A";    }    }    trait B{    public function sayHello(){       parent::sayHello();    echo "B";    }    }    class C extends A{    use B;    public function sayHello(){    echo "C";    }    }    $c=new C();    $c->sayHello();    此打印结果为:C

多个 trait

通过逗号分隔,在 use 声明列出多个 trait,可以都插入到一个类中。

Example #4 多个 trait 的用法<?phptrait Hello {    public function sayHello() {        echo 'Hello ';    }}trait World {    public function sayWorld() {        echo 'World';    }}class MyHelloWorld {    use Hello, World;    public function sayExclamationMark() {        echo '!';    }}$o = new MyHelloWorld();$o->sayHello();$o->sayWorld();$o->sayExclamationMark();?>以上例程会输出:Hello World!

冲突的解决
如果两个 trait 都插入了一个同名的方法,如果没有明确解决冲突将会产生一个致命错误。

为了解决多个 trait 在同一个类中的命名冲突,需要使用 insteadof 操作符来明确指定使用冲突方法中的哪一个。

以上方式仅允许排除掉其它方法,as 操作符可以 为某个方法引入别名。 注意,as 操作符不会对方法进行重命名,也不会影响其方法。

Example #5 冲突的解决在本例中 Talker 使用了 trait A 和 B。由于 A 和 B 有冲突的方法,其定义了使用 trait B 中的 smallTalk 以及 trait A 中的 bigTalk。Aliased_Talker 使用了 as 操作符来定义了 talk 来作为 B 的 bigTalk 的别名。 <?phptrait A {    public function smallTalk() {        echo 'a';    }    public function bigTalk() {        echo 'A';    }}trait B {    public function smallTalk() {        echo 'b';    }    public function bigTalk() {        echo 'B';    }}class Talker {    use A, B {        B::smallTalk insteadof A; //使用B中的smallTalk        A::bigTalk insteadof B;   //使用A中的bigTalk    }}class Aliased_Talker {    use A, B {        B::smallTalk insteadof A;        A::bigTalk insteadof B;        B::bigTalk as talk;    }}?>

修改方法的访问控制 ¶

使用 as 语法还可以用来调整方法的访问控制。

Example #6 修改方法的访问控制<?phptrait HelloWorld {    public function sayHello() {        echo 'Hello World!';    }}// 修改 sayHello 的访问控制class MyClass1 {    use HelloWorld { sayHello as protected; }}// 给方法一个改变了访问控制的别名// 原版 sayHello 的访问控制则没有发生变化class MyClass2 {    use HelloWorld { sayHello as private myPrivateHello; }}?>

从 trait 来组成 trait ¶

正如 class 能够使用 trait 一样,其它 trait 也能够使用 trait。在 trait 定义时通过使用一个或多个 trait,能够组合其它 trait 中的部分或全部成员。

Example #7 从 trait 来组成 trait<?phptrait Hello {    public function sayHello() {        echo 'Hello ';    }}trait World {    public function sayWorld() {        echo 'World!';    }}trait HelloWorld {    use Hello, World;}class MyHelloWorld {    use HelloWorld;}$o = new MyHelloWorld();$o->sayHello();$o->sayWorld();?>以上例程会输出:Hello World!

Trait 的抽象成员 ¶

为了对使用的类施加强制要求,trait 支持抽象方法的使用。

Example #8 表示通过抽象方法来进行强制要求<?phptrait Hello {    public function sayHelloWorld() {        echo 'Hello'.$this->getWorld();    }    abstract public function getWorld();}class MyHelloWorld {    private $world;    use Hello;    public function getWorld() {        return $this->world;    }    public function setWorld($val) {        $this->world = $val;    }}?>

Trait 的静态成员 ¶

Traits 可以被静态成员静态方法定义。Example #9 静态变量<?phptrait Counter {    public function inc() {        static $c = 0;        $c = $c + 1;        echo "$c\n";    }}class C1 {    use Counter;}class C2 {    use Counter;}$o = new C1(); $o->inc(); // echo 1$p = new C2(); $p->inc(); // echo 1?>

Example #10 静态方法

<?phptrait StaticExample {    public static function doSomething() {        return 'Doing something';    }}class Example {    use StaticExample;}Example::doSomething();?>

属性 ¶

Trait 同样可以定义属性。

Example #11 定义属性<?phptrait PropertiesTrait {    public $x = 1;}class PropertiesExample {    use PropertiesTrait;}$example = new PropertiesExample;$example->x;?>

Trait 定义了一个属性后,类就不能定义同样名称的属性,否则会产生 fatal error。 有种情况例外:属性是兼容的(同样的访问可见度、初始默认值)。 在 PHP 7.0 之前,属性是兼容的,则会有 E_STRICT 的提醒。

Example #12 解决冲突<?phptrait PropertiesTrait {    public $same = true;    public $different = false;}class PropertiesExample {    use PropertiesTrait;    public $same = true; // PHP 7.0.0 后没问题,之前版本是 E_STRICT 提醒    public $different = true; // 致命错误}?>

重载

Example #1 使用 __get(),__set(),__isset() 和 __unset() 进行属性重载

<?phpclass PropertyTest {    private $data = array();    public $declared = 1;    private $hidden = 2;    public function __set($anme,$value){        echo "Setting '$name' to '$value'\r\n";        $this->data[$name] = $value;    }    public function __get($name)     {        echo "Getting '$name'\n";        if (array_key_exists($name, $this->data)) {            return $this->data[$name];        }        $trace = debug_backtrace();        trigger_error(            'Undefined property via __get(): ' . $name .            ' in ' . $trace[0]['file'] .            ' on line ' . $trace[0]['line'],            E_USER_NOTICE);        return null;    }    /**  PHP 5.1.0之后版本 */    public function __isset($name)     {        echo "Is '$name' set?\n";        return isset($this->data[$name]);    }    /**  PHP 5.1.0之后版本 */    public function __unset($name)     {        echo "Unsetting '$name'\n";        unset($this->data[$name]);    }    /**  非魔术方法  */    public function getHidden()     {        return $this->hidden;    }    echo "<pre>\n";$obj = new PropertyTest;$obj->a = 1;echo $obj->a . "\n\n";var_dump(isset($obj->a));unset($obj->a);var_dump(isset($obj->a));echo "\n";echo $obj->declared . "\n\n";echo "Let's experiment with the private property named 'hidden':\n";echo "Privates are visible inside the class, so __get() not used...\n";echo $obj->getHidden() . "\n";echo "Privates not visible outside of class, so __get() is used...\n";echo $obj->hidden . "\n";?>以上例程会输出:Setting 'a' to '1'Getting 'a'1Is 'a' set?bool(true)Unsetting 'a'Is 'a' set?bool(false)1Let's experiment with the private property named 'hidden':Privates are visible inside the class, so __get() not used...2Privates not visible outside of class, so __get() is used...Getting 'hidden'Notice:  Undefined property via __get(): hidden in <file> on line 70 in <file> on line 29}

Example #2 使用 __call() 和 __callStatic() 对方法重载

<?php

遍历对象

PHP5提供了一种定义对象的方法使其可以通过单元列表来遍历,例如用foreach语句。默认情况下,所有可见属性都将被用于遍历

Example 简单的对象遍历<?php<?phpclass MyClass{    public $var1 = 'value 1';    public $var2 = 'value 2';    public $var3 = 'value 3';    protected $protected = 'protected var';    private   $private   = 'private var';    function iterateVisible() {       echo "MyClass::iterateVisible:\n";       foreach($this as $key => $value) {           print "$key => $value\n";       }    }}$class = new MyClass();foreach($class as $key => $value) {    print "$key => $value\n";}echo "\n";$class->iterateVisible();?>以上例程会输出:var1 => value 1var2 => value 2var3 => value 3MyClass::iterateVisible:var1 => value 1var2 => value 2var3 => value 3protected => protected varprivate => private var如上所示,foreach 遍历了所有其能够访问的可见属性。 更进一步,可以实现 Iterator 接口。可以让对象自行决定如何遍历以及每次遍历时那些值可用。 

魔术方法

__construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __toString(), __invoke(), __set_state(), __clone() 和 __debugInfo() 等方法在 PHP 中被称为”魔术方法”(Magic methods)。在命名自己的类方法时不能使用这些方法名,除非是想使用其魔术功能。

__sleep()和__wakeup()

final 关键字

PHP 5 新增了一个 final 关键字。如果父类中的方法被声明为 final,则子类无法覆盖该方法。如果一个类被声明为 final,则不能被继承。

“`

Example #1 Final 方法示例

对象复制

理解对象复制的两篇文章
PHP中对象的深拷贝与浅拷贝
深入理解php的浅复制与深复制

深拷贝:赋值时值完全复制,完全的copy,对其中一个作出改变,不会影响另一个(深拷贝 即开辟了一个新空间,把值传了过去,改变此对象值时元对象值不变)

浅拷贝:赋值时,引用赋值,相当于取了一个别名。对其中一个修改,会影响另一个(浅拷贝 把引用传递了过去 相当于取了一个别名。对其中一个修改,会影响另一个)

PHP中, = 赋值时,普通对象是深拷贝,但对对象来说,是浅拷贝。也就是说,对象的赋值是引用赋值。(对象作为参数传递时,也是引用传递,无论函数定义时参数前面是否有&符号)

php4中,对象的 = 赋值是实现一份副本,这样存在很多问题,在不知不觉中我们可能会拷贝很多份副本。

php5中,对象的 = 赋值和传递都是引用。要想实现拷贝副本,php提供了clone函数实现。

由于对象的赋值时引用,要想实现值复制,php提供了clone函数来实现复制对象。

但是clone函数存在这么一个问题,克隆对象时,原对象的普通属性能值复制,但是源对象的对象属性赋值时还是引用赋值,浅拷贝。

谈一下自己的理解:普通对象即 $var

但是clone函数存在这么一个问题,克隆对象时,原对象的普通属性能值复制,但是源对象的对象属性赋值时还是引用赋值,浅拷贝。

实现对象深复制的两个方0法
1. 改写__clone() 即把每一个引用值都

<?phpclass Test{    public $a=1;}class TestOne{    public $b=1;    public $obj;    //包含了一个对象属性,clone时,它会是浅拷贝    public function __construct(){        $this->obj = new Test();    }    //方法一:重写clone函数    public function __clone(){        $this->obj = clone $this->obj;    }}$m = new TestOne();$n = clone $m;$n->b = 2;echo $m->b;//输出原来的1echo PHP_EOL;//可以看到,普通属性实现了深拷贝,改变普通属性b,不会对源对象有影响//由于改写了clone函数,现在对象属性也实现了真正的深拷贝,对新对象的改变,不会影响源对象$n->obj->a = 3;echo $m->obj->a;//输出1,不随新对象改变,还是保持了原来的属性?>
  1. 利用序列化反序列化实现
<?phpclass a{public $dd = "123";public $pp=null ;public function setapp(){$this->pp = new app();}public function getapp(){return app();}function __clone(){$this->setapp();}}class app{public $vv=null;}$A = new a();$A->setapp();$B = unserialize(serialize($A));var_dump($B);var_dump($A);运行结果如下:object(a)[3]  public 'dd' => string '123' (length=3)  public 'pp' =>     object(app)[4]      public 'vv' => nullobject(a)[1]  public 'dd' => string '123' (length=3)  public 'pp' =>     object(app)[2]      public 'vv' => null我们从结果可以看出不论是对象本体还是成员变量都是完全不同的东西;

对象比较

当使用比较运算符(==)比较两个对象变量时,比较的原则是:如果两个对象的属性和属性值 都相等,而且两个对象是同一个类的实例,那么这两个对象变量相等。

而如果使用全等运算符(===),这两个对象变量一定要指向某个类的同一个实例(即同一个对象)。

类型约束

函数的参数可以指定必须为对象(在函数原型里面指定类z的名字),接口,数组或者callable。
不过如果使用NULL作为参数的默认值,那么在调用函数的时候依然可以使用NULL作为实参。

如果一个类或接口指定了类型约束,则其所有的子类或实现也都如此。

类型约束不能用于标量类型如 int 或 string。Traits 也不允许。

对象和引用

在php5 的对象编程经常提到的一个关键点是“默认情况下对象是通过引用传递的”。但其实这不是完全正确的。下面通过一些例子来说明。

PHP 的引用是别名,就是两个不同的变量名字指向相同的内容。在 PHP 5,一个对象变量已经不再保存整个对象的值。只是保存一个标识符来访问真正的对象内容。 当对象作为参数传递,作为结果返回,或者赋值给另外一个变量,另外一个变量跟原来的不是引用的关系,只是他们都保存着同一个标识符的拷贝,这个标识符指向同一个对象的真正内容。

对象序列化

serialize()
unserialize()
所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示。
unserialize()函数能够重新把字符串变回php原来的值。序列化

<?php  class A {      public $one = 1;      public function show_one() {          echo $this->one;      }  }  $a = new A;  $s = serialize($a);var_dump($s);  $a = unserialize($s);  // 现在可以使用对象$a里面的函数 show_one()  $a->show_one();?>string(26) "O:1:"A":1:{s:3:"one";i:1;}"1