PHP对象的使用
对象是系统中用来描述客观事物的一个实体,它是构成系统的一个基本单位。一个对象由一组属性和对这组属性进行操作的一组服务组成。
类与对象的关系就如模具和铸件的关系,类的实例化结果就是对象,而对一类对象的抽象就是类
对象继承
PHP 的对象模型使用了继承。继承将会影响到类与类,对象与对象之间的关系。
比如,当扩展一个类,子类就会继承父类所有公有的和受保护的方法。除非子类覆盖了父类的方法,被继承的方法都会保留其原有功能。
除非使用了自动加载,否则一个类必须在使用之前被定义。如果一个类扩展了另一个,则父类必须在子类之前被声明。此规则适用于类继承其它类与接口。
继承示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
<?php class foo { public function printItem( $string ) { echo 'Foo: ' . $string . PHP_EOL; } public function printPHP() { echo 'PHP is great.' . PHP_EOL; } } class bar extends foo { public function printItem( $string ) { echo 'Bar: ' . $string . PHP_EOL; } } $foo = new foo(); $bar = new bar(); $foo ->printItem( 'baz' ); // Output: 'Foo: baz' $foo ->printPHP(); // Output: 'PHP is great' $bar ->printItem( 'baz' ); // Output: 'Bar: baz' $bar ->printPHP(); // Output: 'PHP is great' ?> |
对象接口
使用接口(interface),可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容。
接口是通过 interface 关键字来定义的,就像定义一个标准的类一样,但其中定义所有的方法都是空的。
接口中定义的所有方法都必须是公有,这是接口的特性。
实现(implements)
要实现一个接口,使用 implements 操作符。类中必须实现接口中定义的所有方法,否则会报一个致命错误。类可以实现多个接口,用逗号来分隔多个接口的名称。
实现多个接口时,接口中的方法不能有重名。
接口也可以继承,通过使用 extends 操作符。
类要实现接口,必须使用和接口中所定义的方法完全一致的方式。否则会导致致命错误。
接口中也可以定义常量。接口常量和类常量的使用完全相同,但是不能被子类或子接口所覆盖。
接口示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
<?php // 声明一个'iTemplate'接口 interface iTemplate { public function setVariable( $name , $var ); public function getHtml( $template ); } // 实现接口 // 下面的写法是正确的 class Template implements iTemplate { private $vars = array (); public function setVariable( $name , $var ) { $this ->vars[ $name ] = $var ; } public function getHtml( $template ) { foreach ( $this ->vars as $name => $value ) { $template = str_replace ( '{' . $name . '}' , $value , $template ); } return $template ; } } // 下面的写法是错误的,会报错,因为没有实现 getHtml(): // Fatal error: Class BadTemplate contains 1 abstract methods // and must therefore be declared abstract (iTemplate::getHtml) class BadTemplate implements iTemplate { private $vars = array (); public function setVariable( $name , $var ) { $this ->vars[ $name ] = $var ; } } ?> |
可扩充的接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
<?php interface a { public function foo(); } interface b extends a { public function baz(Baz $baz ); } // 正确写法 class c implements b { public function foo() { } public function baz(Baz $baz ) { } } // 错误写法会导致一个致命错误 class d implements b { public function foo() { } public function baz(Foo $foo ) { } } ?> |
继承多个接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
<?php interface a { public function foo(); } interface b { public function bar(); } interface c extends a, b { public function baz(); } class d implements c { public function foo() { } public function bar() { } public function baz() { } } ?> |
使用接口常量
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<?php interface a { const b = 'Interface constant' ; } // 输出接口常量 echo a::b; // 错误写法,因为常量不能被覆盖。接口常量的概念和类常量是一样的。 class b implements a { const b = 'Class constant' ; } ?> |
接口加上类型约束,提供了一种很好的方式来确保某个对象包含有某些方法。
遍历对象
PHP 5 提供了一种定义对象的方法使其可以通过单元列表来遍历,例如用 foreach 语句。默认情况下,所有可见属性都将被用于遍历。
简单的对象遍历
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<?php class 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 1
var2 => value 2
var3 => value 3
MyClass::iterateVisible:
var1 => value 1
var2 => value 2
var3 => value 3
protected => protected var
private => private var
如上所示,foreach 遍历了所有其能够访问的可见属性。
更进一步,可以实现 Iterator 接口。可以让对象自行决定如何遍历以及每次遍历时那些值可用。
实现 Iterator 接口的对象遍历
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
<?php class MyIterator implements Iterator { private $var = array (); public function __construct( $array ) { if ( is_array ( $array )) { $this -> var = $array ; } } public function rewind () { echo "rewinding\n" ; reset( $this -> var ); } public function current() { $var = current( $this -> var ); echo "current: $var\n" ; return $var ; } public function key() { $var = key( $this -> var ); echo "key: $var\n" ; return $var ; } public function next() { $var = next( $this -> var ); echo "next: $var\n" ; return $var ; } public function valid() { $var = $this ->current() !== false; echo "valid: {$var}\n" ; return $var ; } } $values = array (1,2,3); $it = new MyIterator( $values ); foreach ( $it as $a => $b ) { print "$a: $b\n" ; } ?> |
以上例程会输出:
rewinding
current: 1
valid: 1
current: 1
key: 0
0: 1
next: 2
current: 2
valid: 1
current: 2
key: 1
1: 2
next: 3
current: 3
valid: 1
current: 3
key: 2
2: 3
next:
current:
valid:
可以用 IteratorAggregate 接口以替代实现所有的 Iterator 方法。IteratorAggregate 只需要实现一个方法 IteratorAggregate::getIterator(),其应返回一个实现了 Iterator 的类的实例。
通过实现 IteratorAggregate 来遍历对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
<?php class MyCollection implements IteratorAggregate { private $items = array (); private $count = 0; // Required definition of interface IteratorAggregate public function getIterator() { return new MyIterator( $this ->items); } public function add( $value ) { $this ->items[ $this -> count ++] = $value ; } } $coll = new MyCollection(); $coll ->add( 'value 1' ); $coll ->add( 'value 2' ); $coll ->add( 'value 3' ); foreach ( $coll as $key => $val ) { echo "key/value: [$key -> $val]\n\n" ; } ?> |
以上例程会输出:
rewinding
current: value 1
valid: 1
current: value 1
key: 0
key/value: [0 -> value 1]
next: value 2
current: value 2
valid: 1
current: value 2
key: 1
key/value: [1 -> value 2]
next: value 3
current: value 3
valid: 1
current: value 3
key: 2
key/value: [2 -> value 3]
next:
current:
valid:
对象复制
在多数情况下,我们并不需要完全复制一个对象来获得其中属性。但有一个情况下确实需要:如果你有一个 GTK 窗口对象,该对象持有窗口相关的资源。你可能会想复制一个新的窗口,保持所有属性与原来的窗口相同,但必须是一个新的对象(因为如果不是新的对象,那么一个窗口中的改变就会影响到另一个窗口)。还有一种情况:如果对象 A 中保存着对象 B 的引用,当你复制对象 A 时,你想其中使用的对象不再是对象 B 而是 B 的一个副本,那么你必须得到对象 A 的一个副本。
对象复制可以通过 clone 关键字来完成(如果可能,这将调用对象的 __clone() 方法)。对象中的 __clone() 方法不能被直接调用。
$copy_of_object = clone $object;
当对象被复制后,PHP 5 会对对象的所有属性执行一个浅复制(shallow copy)。所有的引用属性 仍然会是一个指向原来的变量的引用。
void __clone ( void )
当复制完成时,如果定义了 __clone() 方法,则新创建的对象(复制生成的对象)中的 __clone() 方法会被调用,可用于修改属性的值(如果有必要的话)。
复制一个对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
<?php class SubObject { static $instances = 0; public $instance ; public function __construct() { $this ->instance = ++self:: $instances ; } public function __clone() { $this ->instance = ++self:: $instances ; } } class MyCloneable { public $object1 ; public $object2 ; function __clone() { // 强制复制一份this->object, 否则仍然指向同一个对象 $this ->object1 = clone $this ->object1; } } $obj = new MyCloneable(); $obj ->object1 = new SubObject(); $obj ->object2 = new SubObject(); $obj2 = clone $obj ; print ( "Original Object:\n" ); print_r( $obj ); print ( "Cloned Object:\n" ); print_r( $obj2 ); ?> |
以上例程会输出:
Original Object:
MyCloneable Object
(
[object1] => SubObject Object
(
[instance] => 1
)
[object2] => SubObject Object
(
[instance] => 2
)
)
Cloned Object:
MyCloneable Object
(
[object1] => SubObject Object
(
[instance] => 3
)
[object2] => SubObject Object
(
[instance] => 2
)
)
对象比较
PHP 5 中的对象比较要比 PHP 4 中复杂,所期望的结果更符合一个面向对象语言。
当使用比较运算符(==)比较两个对象变量时,比较的原则是:如果两个对象的属性和属性值 都相等,而且两个对象是同一个类的实例,那么这两个对象变量相等。
而如果使用全等运算符(===),这两个对象变量一定要指向某个类的同一个实例(即同一个对象)。
通过下面的示例可以理解以上原则。
PHP 5 的对象比较
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
<?php function bool2str( $bool ) { if ( $bool === false) { return 'FALSE' ; } else { return 'TRUE' ; } } function compareObjects(& $o1 , & $o2 ) { echo 'o1 == o2 : ' . bool2str( $o1 == $o2 ) . "\n" ; echo 'o1 != o2 : ' . bool2str( $o1 != $o2 ) . "\n" ; echo 'o1 === o2 : ' . bool2str( $o1 === $o2 ) . "\n" ; echo 'o1 !== o2 : ' . bool2str( $o1 !== $o2 ) . "\n" ; } class Flag { public $flag ; function Flag( $flag = true) { $this ->flag = $flag ; } } class OtherFlag { public $flag ; function OtherFlag( $flag = true) { $this ->flag = $flag ; } } $o = new Flag(); $p = new Flag(); $q = $o ; $r = new OtherFlag(); echo "Two instances of the same class\n" ; compareObjects( $o , $p ); echo "\nTwo references to the same instance\n" ; compareObjects( $o , $q ); echo "\nInstances of two different classes\n" ; compareObjects( $o , $r ); ?> |
以上例程会输出:
Two instances of the same class
o1 == o2 : TRUE
o1 != o2 : FALSE
o1 === o2 : FALSE
o1 !== o2 : TRUE
Two references to the same instance
o1 == o2 : TRUE
o1 != o2 : FALSE
o1 === o2 : TRUE
o1 !== o2 : FALSE
Instances of two different classes
o1 == o2 : FALSE
o1 != o2 : TRUE
o1 === o2 : FALSE
o1 !== o2 : TRUE
对象和引用
在php5 的对象编程经常提到的一个关键点是“默认情况下对象是通过引用传递的”。但其实这不是完全正确的。下面通过一些例子来说明。
php的引用是别名,就是两个不同的变量名字指向相同的内容。在php5,一个对象变量已经不再保存整个对象的值。只是保存一个标识符来访问真正的对象内容。 当对象作为参数传递,作为结果返回,或者赋值给另外一个变量,另外一个变量跟原来的不是引用的关系,只是他们都保存着同一个标识符的拷贝,这个标识符指向同一个对象的真正内容。
引用和对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<?php class A { public $foo = 1; } $a = new A; $b = $a ; // $a ,$b都是同一个标识符的拷贝 // ($a) = ($b) = <id> $b ->foo = 2; echo $a ->foo. "\n" ; $c = new A; $d = & $c ; // $c ,$d是引用 // ($c,$d) = <id> $d ->foo = 2; echo $c ->foo. "\n" ; $e = new A; function foo( $obj ) { // ($obj) = ($e) = <id> $obj ->foo = 2; } foo( $e ); echo $e ->foo. "\n" ; ?> |
以上例程会输出:
2
2
2
序列化对象 - 在会话中存放对象
所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示。unserialize()函数能够重新把字符串变回php原来的值。 序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。
为了能够unserialize()一个对象,这个对象的类必须已经定义过。如果序列化类A的一个对象,将会返回一个跟类A相关,而且包含了对象所有变量值的字符串。 如果要想在另外一个文件中解序列化一个对象,这个对象的类必须在解序列化之前定义,可以通过包含一个定义该类的文件或使用函数spl_autoload_register()来实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<?php // classa.inc: class A { public $one = 1; public function show_one() { echo $this ->one; } } // page1.php: include ( "classa.inc" ); $a = new A; $s = serialize( $a ); // 把变量$s保存起来以便文件page2.php能够读到 file_put_contents ( 'store' , $s ); // page2.php: // 要正确了解序列化,必须包含下面一个文件 include ( "classa.inc" ); $s = file_get_contents ( 'store' ); $a = unserialize( $s ); // 现在可以使用对象$a里面的函数 show_one() $a ->show_one(); ?> |
当一个应用程序使用函数session_register()来保存对象到会话中时,在每个页面结束的时候这些对象都会自动序列化,而在每个页面开始的时候又自动解序列化。 所以一旦对象被保存在会话中,整个应用程序的页面都能使用这些对象。但是,session_register()在php5.4.0之后被移除了。
在应用程序中序列化对象以便在之后使用,强烈推荐在整个应用程序都包含对象的类的定义。 不然有可能出现在解序列化对象的时候,没有找到该对象的类的定义,从而把没有方法的类__PHP_Incomplete_Class_Name作为该对象的类,导致返回一个没有用的对象。
所以在上面的例子中,当运行session_register("a"),把变量$a放在会话里之后,需要在每个页面都包含文件classa.inc,而不是只有文件page1.php和page2.php。
相关推荐
深度学习 -- 损失函数
深度残差网络(Deep Residual Networks (ResNets))
深度学习 -- 激活函数
神经网络训练 -- 调整学习速率
生成对抗网络(GAN)改进与发展
生成对抗网络(GAN)优点与缺点
生成对抗网络(GAN)的训练
生成对抗网络(GAN)基本原理
生成模型与判别模型