数组重载: 数组重载是指将对象做为数组使用的过程。具备这种功能的对象也称为索引器。php
数组重载是将对象做为数组使用的一个过程。这意味着容许使用 [ ] 数组语法访问数据。git
数组重载的学习令我想起一个问题: 为什么JavaScript中的一切皆对象?或者简单的说是为什么JavaScript的数组是对象,能够以arr.lenth
的方式调属性,以arr.append()
的方式调方法呢?github
固然对于大多的JavaScript Developer,是不会有这个疑问的。由于JavaScript中没有数组这种变量类型。而大多数第一门语言是C或者是PHP的开发者,第一次接触JavaScript的数组时,难免会以为别扭。web
若是你也是一个没法理解JavaScript的数组是对象的PHPer,以为很难以想象,这篇文章或许能帮助你。好了,先把问题放下。咱们来学习SPL的数组重载,说不定学着学着就明白了呢。后端
ArrayAccess Interface:PHP提供的索引器接口,属于预约义接口之一,是数组重载的核心,它提供了挂载到Zend引擎所必须的功能。设计模式
索引器接口:该接口提供了将对象做为数组访问的功能,也称为数组式访问接口或数组重载接口。数组
foreach
。除非该索引器同时实现了迭代器接口。同理不能对该索引器使用count
计数函数,除非其实现计数器接口
ArrayAccess Interface 实现:bash
<?php
//自定义索引器类 实现 ArrayAccess接口
class MyArray implements ArrayAccess{
protected $_arr =array() ; //用于存放数据
//设置或替换给定偏移量上的数据
public function offsetSet($offset,$value){
$this->_arr[$offset] = $value;
}
//返回指定偏移量位置上的数据
public function offsetGet($offset){
return $this->_arr[$offset];
}
//判断指定的偏移量是否存在于数组中
public function offsetExists($offset){
return array_key_exists($offset,$this->_arr);
}
//删除指定偏移量位置上的数据
public function OffsetUnset($offset){
unset($this->_arr[$offset]);
}
}
//使用示例
$myArr = new MyArray;
//赋值
$myArr['first']=1;
$myArr['second']=2;
//使用$myArr['second']
echo $myArr['second'].PHP_EOL;
//改变$myArr['second']值
$myArr['second']='two';
//再次使用$myArr['second']
echo $myArr['second'].PHP_EOL;
//删除$myArr['second']
unset($myArr['second']);
//再次使用$myArr['second']
echo $myArr['second'].PHP_EOL;
复制代码
运行结果:微信
echo __METHOD__,PHP_EOL;
架构
<?php
//自定义索引器类 实现 ArrayAccess接口
class MyArray implements ArrayAccess{
protected $_arr =array() ; //用于存放数据
//设置或替换给定偏移量上的数据
public function offsetSet($offset,$value){
echo __METHOD__,PHP_EOL;
$this->_arr[$offset] = $value;
}
//返回指定偏移量位置上的数据
public function offsetGet($offset){
echo __METHOD__,PHP_EOL;
return $this->_arr[$offset];
}
//判断指定的偏移量是否存在于数组中
public function offsetExists($offset){
echo __METHOD__,PHP_EOL;
return array_key_exists($offset,$this->_arr);
}
//删除指定偏移量位置上的数据
public function OffsetUnset($offset){
echo __METHOD__,PHP_EOL;
unset($this->_arr[$offset]);
}
}
//使用示例
$myArr = new MyArray;
//赋值
$myArr['first']=1;
$myArr['second']=2;
//使用$myArr['second']
echo $myArr['second'].PHP_EOL;
//改变$myArr['second']值
$myArr['second']='two';
//再次使用$myArr['second']
echo $myArr['second'].PHP_EOL;
//删除$myArr['second']
unset($myArr['second']);
//再次使用$myArr['second']
echo $myArr['second'].PHP_EOL;
复制代码
再次运行:
ArrayAccess
接口后,咱们以数组的方式给索引器对象赋值时,PHP自动帮咱们调用了
offsetSet
方法。
echo
这个索引器对象某个键值时,则调用了
offsetGet
方法。
isset
判断该键值时,则自动调用
offsetExists
方法。删除该键值时,则自动调用
offsetUnset
方法。
经过上面的代码,咱们能够看到实现ArrayAccess接口的一个简单形式。它为咱们演示了数组机制是如何运做的。
提供ArrayAccess接口的主要缘由是并不是全部的集合都是基于真实的数组。使用ArrayAccess接口的可能会将请求代理到面向服务的架构(SOA)的后端程序,或者其余形式的离线程序。这容许推迟底层数组的获取时间,直到它被实际访问时才去获取这些数据。
然而,对于大多数状况来讲,可能会使用数组做为底层数据的表达形式。而后,你将会向这个类添加处理这一数据的方法。为实现这一目的,SPL提供了内置的ArrayObject类。
Countable Interface:SPL提供的计数接口,实现该接口的类可被用于**count()**函数计数,并返回数据长度。
咱们知道,当一个类实现了ArrayAccess接口,就能够把它当成一个数组同样操做。可是并不能使用count函数进行计数。若是咱们想让这个对象更像一个数组,就必须实现计数接口,为它提供计数能力。
实现Countable接口很是简单,只须要实现它的抽象方法count方法。由手册咱们知道,count方法必须返回一个int类型的值,实际上这个值就是Array对象的有效元素数目。
代码示例:
//自定义数组对象实现Countable接口
class MyArray implements Countable{
protected $_arr = array(1,2,3); //为演示方便直接赋值
public function count(){
return count($this->_arr);
}
}
//实例化自定义数组对象
$myArr = new MyArray;
//计数
echo count($myArr);
复制代码
运行结果:
一样的,咱们加上一句 echo __METHOD__,PHP_EOL;
<?php
//自定义数组对象实现Countable接口
class MyArray implements Countable{
protected $_arr = array(1,2,3); //为演示方便直接赋值
public function count(){
echo __METHOD__,PHP_EOL;
return count($this->_arr);
}
}
//实例化自定义数组对象
$myArr = new MyArray;
//计数
echo count($myArr);
复制代码
再次运行:
固然,这个有一个问题: 为何SPL内置的一个实现Countable接口的类ArrayObject,实现count方法时,方法体里无实现代码,也没有返回值,可是依然可以计数。而咱们若是没有return,则默认是null。使用Count函数计数时,就会返回零。为何ArrayObject不用返回呢?
好了,虽然存在一点疑问,可是不影响。Countable接口的实现就是怎么简单。
若是说咱们有一个类同时实现了ArrayAccess
,Countable
,甚至还有Iterator
接口,那么这个类的操做几乎和数组无差别了。实现ArrayAccess
接口拥有数组访问,实现Countable
接口拥有计数的能力,实现Iterator
接口拥有迭代访问的能力。
ArrayObject : ArrayObject是SPL内置的一个数组对象类,它同时实现了ArrayAccess
,Countable
,IteratorAggregate
接口。提供了迭代,排序和处理数据很是有用的方法,与数组的使用几乎是无差异的。其中实现IteratorAggregate
接口委托的迭代器是在构造方法传递参数中默认了ArrayIterator
迭代器。
ArrayObject简单使用:
$myArr = new ArrayObject ;
//赋值
$myArr['first']=1;
$myArr['second']=7;
$myArr['third']=5;
$myArr['fourth']=9;
//打印
print_r($myArr);
//删除
unset($myArr['fourth']);
//遍历
foreach($myArr as $key => $val){
echo $key.'=>'.$val.PHP_EOL;
}
复制代码
运行结果:
ArrayObject内部是基于Array实现的,因此它的限制在于只能处理真实的已经彻底填充好的数据。可是它能够为应用程序提供有价值的基类。
也就是说,若是你要自定义一个实现ArrayAccess接口的类,实际上不少时候咱们直接继承ArrayObject就能够了。
ArrayObject的好处在于,你自定义的类能够继承它能够获得与数组使用无差别的功能。
好了,到这里。你想一想实例化一个SPL的ArrayObject对象,获得了什么?
一个与数组使用无差别的对象
记住上面这句话,它意味着什么呢?咱们再想一下为何JavaScript中数组也是对象呢?SPL提供的ArrayObject
不就和JavaScript的数组很类似了吗?打破思惟的藩篱,咱们换一个视角来看这个问题。
假如PHP一开始就和JavaScript同样,没有提供数组这种变量类型,只给咱们提供了ArrayObject
类。甚至array()
函数返回的不是一个数组,而是一个ArrayObject
的实例。咱们不是也能够经过ArrayObject
创造出一个与传统数组概念上使用无差别的**‘新数组’吗。而这种新数组的使用与传统数组无差异,且这种新数组还能调用方法,调用属性。这不正是JavaScript中数组吗?那么这个时候,这种概念的数组,不就是所谓的数组也是对象**吗?
因此说,JavaScript的数组从这个角度上看,就和PHP中SPL提供的ArrayObject
的实例化数组对象同样。JavaScript没有像PHP这种纯粹的数组,它定义的数组就已是一个对象了。是的,PHP的数组相对于JavaScript是底层了,PHP彻底能够封装出JavaScript那种数组对象。
固然,相较于JavaScript,只有PHP的数组不是对象,它就算是纯粹的数组吗?未必吧。PHP的数组与C语言比较呢?C语言的数组是没法动态扩展的,而PHP是能够的。从这个角度上来看,PHP中的数组是否相较与C语言也是一种封装呢?
实现ArrayAccess
接口的索引器,包括ArrayObject
的实例虽与数组使用几乎无差别,但却没法使用PHP提供的sort
,asort
,ksort
等函数进行排序。
$myArr = new ArrayObject ;
//赋值
$myArr['first']=1;
$myArr['second']=7;
$myArr['third']=5;
$myArr['fourth']=9;
//打印
asort($myArr);
复制代码
运行结果:
可是若是你这样写:
$arr = new ArrayObject;
$arr = [1,2,4,5,8,3];
print_r($arr);
sort($arr); //排序
print_r($arr);
复制代码
运行结果:
$arr = [1,2,4,5,8,3];
时,已经为
$arr
从新赋值一个数组了,
$arr
的变量类型已经不是
ArrayObject
对象,而是数组类型了。数组固然能调用
asort
函数咯。
因此,实现ArrayAccess
接口的数组对象没法使用PHP自带的排序函数进行排序,这点你要注意。 不过,方便的是SPL中ArrayObject
类已经拥有asort
,ksort
等方法它让咱们能够实现相似于JavaScript那种操做,调用$arr->asort();
给这个数组对象排序。
代码示例:
$myArr = new ArrayObject ;
//赋值
$myArr['first']=1;
$myArr['second']=7;
$myArr['third']=5;
$myArr['fourth']=9;
//排序
$myArr->asort();
//打印
print_r($myArr);
复制代码
运行结果:
ArrayObject的最多见应用是在web购物车中,令购物车类继承自ArrayObject,这样你实例化的购物车,不只是一个对象并且仍是一个数组。
代码示例:
<?php
//商品类
class Product{
public $_name; //商品名称
public $_price; //商品价格
public function __construct($name,$price){
$this->_name = $name;
$this->_price = $price;
}
}
//购物车类
class Cart extends ArrayObject{
//计算购物车商品总价格
public function sum(){
$num = 0;
foreach($this as $product){
$num+=$product->_price ;
}
return PHP_EOL.'购物车总价 : '.$num.PHP_EOL;
}
}
//实例化商品
$book = new Product('<补刀心法>',57);
$pen = new Product('破铁牌钢笔',2);
//实例化购物车
$cart = new Cart;
$cart[] = $book;
$cart[] = $pen;
//查看打印
print_r($cart);
//查看购物车商品总数
echo count($cart);
//计算购物车商品总价格
echo $cart->sum();
复制代码
运行结果:
固然这里咱们要强调一点,storage
属性是ArrayObject
的私有属性,Cart
做为ArrayObject
的子类,是没法直接访问和使用的。可是因为ArrayObject
的特性,咱们能够在Cart
中使用$this
来访问父类的storage
属性,同理可使用$this[0]
来访问第一个元素。
ArrayObject
实现了ArrayAccess
接口,全部继承它的类实例出来的对象均可以当作数组通常使用。其实,ArrayObject
的构造方法还容许传入一个数组,它可让咱们把一个数组当成对象通常使用。
$arr = [1,8,3,9,5];
$arr = new ArrayObject($arr);
$arr->append(2); //添加一个元素
echo $arr->offsetGet($arr->count()-1); //获取最后一个数据
复制代码
运行结果:
有些时候,咱们或许须要将对象的做为数组键值。在PHP中若是直接这么作,明显是不行的,会获得一个警告。
<?php
class MyObject{}
$a = new MyObject;
$arr = array($a=>'test');
复制代码
运行结果:
幸运地是,SPL为咱们提供一个获取对象散列值的函数spl_object_hash()
<?php
class MyObject{}
$a = new MyObject;
$b = new MyObject;
echo spl_object_hash($a);
echo PHP_EOL;
echo spl_object_hash($b);
复制代码
<?php
class MyObject{}
$a = new MyObject;
//使用对象做为数组键值
$arr = array(spl_object_hash($a)=>'test');
//输出
echo $arr[spl_object_hash($a)];
复制代码
运行结果:
使用对象做为键值的好处是能够在一个数组中避免存放同个对象的多个引用。
<?php
class MyObject{}
$a = new MyObject;
$b = $a;
//赋值
$arr[spl_object_hash($a)] = $a;
$arr[spl_object_hash($b)] = $b;
//打印
print_r($arr);
复制代码
运行结果:
$a
与
$b
是同一个对象实例,
spl_object_hash()
返回值相同,因此key值相同。
$arr[spl_object_hash($b)] = $b;
并无为数组建立新元素。保证了数组中相同对象实例只有一个。
这里咱们还要强调一点:虽然在一个脚本中对一个对象获取屡次散列值,获得的结果是同样的。可是每次运行这个脚本获取这个对象的散列值是不一样的。
<?php
class MyObject{}
$a = new MyObject;
$b = $a;
echo spl_object_hash($a); //第一次获取
echo PHP_EOL;
echo spl_object_hash($b); //第二次获取
复制代码
运行结果:
上一篇:SPL迭代器接口介绍
感谢阅读,因为笔者能力有限,文章不可避免地有失偏颇 后续更新SPL的其余内容,欢迎你们评论指正
我最近的学习总结: