- 实现链表过程中的疑惑
- PHP对象数据类型的引用传递
本文代码较多,如果不对/疑惑之处请提出
链表
PHP实现
这几天在写链表相关的代码,以前都是用C++打的,没有考虑过PHP实现方式。可能是我深受C++的影响吧,一时竟然不知道PHP中如何表示指针。大家可以试着先实现一下链表,再来看下面这段PHP实现的链表代码:
1 |
|
可以看到其实就是一个单链表的实现过程。先创建了一个头节点,$p
是指向 $linkList
的指针,接着使用尾插法插入数据。
当我一步步阅读代码的时候,疑惑产生了。就在这一行 $p = $p->next;
不知道你有没有理解我在说什么,首先一个前提是:PHP的对象是引用传递的。所以我们才能在下面这行代码中改变$linkList
的值
1 | $p->next = $t; |
接下来是修改变量p的值
1 | $p = $p->next; |
很幸运,linkList
变量没有被修改,我们实现了链表的功能
why???
在我的理解中引用传递就是使用不同变量名访问同一个zval容器。并且,使用引用时,是关闭了变量的写时复制,正如下面这段经典的代码:
1 | $a = 1; |
C++的实现
接着我开始思考指针和引用的区别,再来看看C++中利用指针的实现吧!
1 | struct ListNode { |
指针,就是指向数据内存地址,指针变量就是用来保存这些地址的变量。利用指针,可以轻松的访问修改被指向变量的数据。也就是浅拷贝,修改了指针就等于修改了该内存地址上的数据。然而深拷贝,就像PHP中的写时复制,修改数据互不影响。
搞清了指针的概念,我们好像看出点什么来了,尽管在PHP中,某些时候引用可以实现指针的功能,但指针和引用并不是一个东西。
接着这里提一句,如果对PHP7对象引用传递有怀疑的,大家可以自行实现一下。这里仅提供一份代码,不再赘述。
1 |
|
调试一探究竟
我们用gdb来调试下
1 |
|
此时我们来查看变量$linkList(zval)
中的 zend_object
的内存地址
1 | # 以下是运行到此处的gdb调试结果 |
此时的$p
和$linkList
其中的zend_object
地址是相同的,修改了$p->val
就相当于修改了$linkList
中的zend_object
,并且zend_object
的 引用计数为3。这就是对象的引用传递。在zval中的obj指针指向对象的实际地址。
代码继续执行:
1 | $p = $p->next; //next 为 null |
此时变量$p
变为null
,那么$linkList
会不会也变成null
呢?我们来看看$p
和$linkList
的zval结构:
1 | # $p的zval |
可以看到,$p
已经变成 type=0
的NULL
类型,而$linkList
还是原先的结构,保留着之前对象。这时发生了分离。
至此,我们可以想到,对象的引用传递是不发生在zval上的,当你修改zval的值,那么zval结构当然会产生变化。当你修改zval中的zend_object时,保存该对象的所有zval也发生改变。(把它说成是指针传递会不会更好理解?)
自问自答
Q:既然zval和对象是分开的,是否意味着对象一旦创建就无法真正的销毁?
A:不会的,PHP7中对象本身自带引用计数,不再是PHP5中的双重引用了。当zend_object的引用计数为0时,也就进入了垃圾回收管理。
总结
- PHP对象是引用传递
- 引用不等于指针,引用可以看作是变量的别名
- 这些内容平时开发相对少用到,但使用时需要注意数据的严谨性