php一开始是面向过程的语言,到后期才支持面向对象,数组在php中的类型是 “array”:
echo gettype(array());
输出
array
很多操作数组的函数都是以 “array” 开头,第一个参数为要操作的数组。
要实现一个数组类,需要实现ArrayAccess这个接口,这个接口的功能是 “提供像访问数组一样访问对象的能力” ,该接口有四个方法:
abstract public boolean offsetExists ( mixed $offset )
abstract public mixed offsetGet ( mixed $offset )
abstract public void offsetSet ( mixed $offset , mixed $value )
abstract public void offsetUnset ( mixed $offset )
这四个函数的作用如下(假设$obj是一个实现了该接口的类的实例):
看上去有点类似C++的运算符重载。我们可以封装一个类,以一个数组变量作为其私有属性,这四个函数操作数组变量就行。
且看代码:
我们把这个类命名为XArray。
class XArray implements ArrayAccess{
private $container = array();
public function __construct($size = 0, $value = 0) {
if ($size > 0) {
$this->container = array_fill(0, $size, $value);
}
}
public function offsetSet($offset, $value) {
echo "call ", __METHOD__, PHP_EOL;
if(is_null($offset)) {
$this->container[] = $value;
} else {
$this->container[$offset] = $value;
}
}
public function offsetUnset($offset) {
echo "call ", __METHOD__, PHP_EOL;
unset($this->container[$offset]);
}
public function offsetGet($offset) {
echo "call ", __METHOD__, PHP_EOL;
return isset($this->container[$offset]) ? $this->container[$offset] : null;
}
public function offsetExists($offset) {
echo "call ", __METHOD__, PHP_EOL;
return isset($this->container[$offset]);
}
}
测试:
$obj = new XArray(5, 10);
$obj[] = 16;
echo $obj['a'];
isset($obj[3]);
unset($obj['a']);
输出:
call XArray::offsetSet
call XArray::offsetGet
call XArray::offsetExists
call XArray::offsetUnset
正如以上分析所言。
我们可以像操作数组一样操作一个XArray类的实例,可是对于便利操作(foreach),就不行了。
foreach ($obj as $v) {
echo $v;
}
没有输出。
要能让XArray的实例实现遍历操作,得实现Traversable接口,但这是个抽象接口,不过Iterator接口继承了Traversable接口,所以我们可以实现Iterator接口。这个接口有5个方法:
abstract public mixed current ( void )
abstract public scalar key ( void )
abstract public void next ( void )
abstract public void rewind ( void )
abstract public boolean valid ( void )
各方法的作用如下:
在XArray中增加一个$position私有变量,然后增加以下5个方法:
public function rewind() {
echo "call ", __METHOD__, PHP_EOL;
reset($this->container);
$this->position = 0;
}
public function current() {
echo "call ", __METHOD__, PHP_EOL;
return current($this->container);
}
public function next() {
echo "call ", __METHOD__, PHP_EOL;
next($this->container);
$this->position++;
}
public function key() {
echo "call ", __METHOD__, PHP_EOL;
return key($this->container);
}
public function valid() {
echo "call ", __METHOD__, PHP_EOL;
return $this->position < count($this->container);
}
有rewind, current,next,key这4个方法,内部都是通过调用php操作数组的相应方法来实现的。
测试:
$obj = new XArray();
$obj[] = 1;
$obj[] = 2;
$obj[] = 3;
foreach ($obj as $v) {
echo $v, PHP_EOL;
}
输出:
call XArray::offsetSet
call XArray::offsetSet
call XArray::offsetSet
call XArray::rewind
call XArray::valid
call XArray::current
1
call XArray::next
call XArray::valid
call XArray::current
2
call XArray::next
call XArray::valid
call XArray::current
3
call XArray::next
call XArray::valid
由以上输出可知,首次遍历时,依次调用rewind, valid方法, 然后调用current方法得到值;后面都是依次调用next, valid方法,再调用current方法得到值。如果valid方法返回false,说明遍历到了末尾,则不再调用current方法,遍历结束。
可以看到,用 foreach($obj as $v)这种遍历方式,并没有调用key方法。
我们把遍历方式改为foreach($obj as $k => $v)试一下:
$obj = new XArray();
$obj[] = 1;
$obj[] = 2;
$obj[] = 3;
foreach ($obj as $k => $v) {
echo $v, PHP_EOL;
}
call XArray::offsetSet
call XArray::offsetSet
call XArray::offsetSet
call XArray::rewind
call XArray::valid
call XArray::current
call XArray::key
1
call XArray::next
call XArray::valid
call XArray::current
call XArray::key
2
call XArray::next
call XArray::valid
call XArray::current
call XArray::key
3
call XArray::next
call XArray::valid
这次有调用key方法了,而且可以看到, key方法是在current方法调用之后才调用的。
这样的数组类,功能还是有点弱,很多数组的方法都没有,比如,push, pop, slice, 也不能获取数组长度(length属性)。
在XArray类中添加如下代码:
public function all() {
return $this->container;
}
/** $obj->length 获取数组元素个数 */
public function __get($property) {
if ($property == 'length') {
return count($this->container);
}
return null;
}
/** 把一个XArray类的实例,数组或其他类型的变量合并到本实例的数组变量中 */
public function merge($data) {
$class = get_class($this);
if ($data instanceof $class) {
$this->container = array_merge($this->container, $data->all());
} elseif (is_array($data)) {
$this->container = array_merge($this->container, $data);
} else{
$this->container[] = $data;
}
return $this;
}
public function shift() {
return array_shift($this->container);
}
public function pop() {
return array_pop($this->container);
}
public function push($ele) {
foreach (func_get_args() as $v) {
array_push($this->container, $v);
}
return $this;
}
public function unshift($ele) {
foreach (func_get_args() as $v) {
array_unshift($this->container, $v);
}
return $this;
}
public function slice($offset, $length) {
$arr = array_slice($this->container, $offset, $length);
if (empty($arr)) {
return null;
}
$class = get_class($this);
$obj = new $class();
return $obj->merge($arr);
}
/** 打印实例内容 */
public function dump() {
echo "[elements begin]", PHP_EOL;
foreach ($this->container as $k => $v) {
echo "\t",$k, " => ", $v, PHP_EOL;
}
echo "[elements end]", PHP_EOL;
}
大部分函数都是调用php的同名array_系列数组操作函数,其中unshift, push方法最后时return $this,可以实现链式调用, slice方法则是返回一个XArray对象。
测试代码:
$obj = new XArray(2, 6);
$obj[] = 16;
$obj->push(1,2,3)->unshift(5)->pop();
$obj->dump();
echo "len(obj) = ", $obj->length, PHP_EOL;
$obj2 = $obj->slice(0, 3);
$obj2->dump();
输出:
call XArray::offsetSet
[elements begin]
0 => 5
1 => 6
2 => 6
3 => 16
4 => 1
5 => 2
[elements end]
len(obj) = 6
[elements begin]
0 => 5
1 => 6
2 => 6
[elements end]
如预期。
由于时间有限,数组操作的其他功能就不实现了。之前查看Laravel的源码,有个文件也是实现了一个数组类,很多数组操作的方法都实现了。