从PHP函数参数为数组和对象的区别说开去

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/SchopenhauerZhang/article/details/79605550

从PHP函数参数为数组和对象的区别说开去

从PHP函数形式参数为数组和对象的区别说开去,首先比较一下函数的参数为数组和对象的区别,然后由此说开,谈谈变量的底层结构,对象的底层结构,hashTable的底层结构。

  • 函数参数为数组和对象的区别
  • 变量的底层结构
  • 对象的底层结构
  • *HashTable —PHP的灵魂

这里写图片描述

函数参数为数组和对象的区别

先写一个不算很好的例子但是足够说明问题了。

这里写图片描述

class Test {
   public $b = 0;

}

function change( $a   )
{
    if(is_array($a)  ){

       $a['a'] = 1;
    }else{
       $a->b= 1;
    }
    return $a ;
}

$array = [ 1, 2, 3,'a'=>2];
$b = new  Test();
var_dump(($array));
echo 'up array dowm class ';
var_dump(($b));
echo 'no cchange  ';
change($array);
var_dump($array);
echo 'up array dowm class ';
change($b);
var_dump(($b));

打印结果如下:

array(4) {
 [0]=>
 int(1)
 [1]=>
 int(2)
 [2]=>
 int(3)
 ["a"]=>
 int(2)
}
up array dowm class object(Test)#1 (1) {
 ["b"]=>
 int(0)
}
no cchange  array(4) {
 [0]=>
 int(1)
 [1]=>
 int(2)
 [2]=>
 int(3)
 ["a"]=>
 int(2)
}
up array dowm class object(Test)#1 (1) {
 ["b"]=>
 int(1)
}

数组在函数中被改变了,但是并不影响原本的数组,这说明数组的传递是一个值传递(副本),而对象的传递是引用传递所以函数中改变了对象的属性,所以原本的对象也被改变了。而且据说“因为php内置函数默认就是传的值而不是引用”。笔者目前没有看见任何资料上有这句话,又看到的朋友欢迎留言指证。很明显上面的例子说明了问题,不管对象的传递默认是传值还是传引用对对象来说都变了。

点击链接进入页面可以看到:“因为php内置函数默认就是传的值而不是引用”。 在array_map函数讲解那块!!!—— 重剑无锋 大巧不工

变量有作用的存在就没有这个问题了,这里不再赘述。

变量的底层结构

都知道PHP内核是C写的。
下面根据PHP变量的结构开始介绍一系列PHP底层的C代码。
变量是PHP的源泉。笔者认为的!!!

因为数组是一种变量,而对象也是一种变量。—— PHP核心技术与最佳实践第四页

变量的结构C代码如下:

扫描二维码关注公众号,回复: 3767230 查看本文章
typedef struct _zvalue_value{
long lval;// 存放long、bool和resource
double dval;
struct {
    char *val;
    int len;
}str;
 HashTable   * ht;// 本文后面会讲
 zend_object_value obj;

}zvalue_value;

值得注意的是所有变量的底层类型都是zvalue_value。
看到这里大家似乎有点理解为什么PHP中变量可以存储各种类型了。
别急,变量还需要一个结构去存储引用等信息,也就是有名的zval结构体。

struct _zval_struct{
zvalue_value val;// 存放PHP变量的值
zend_uint refcount; // 变量引用计数
zend_uchar is_ref; // 变量是否被引用
zend_uchar type; // 变量的类型
}zval_struct;

typedef _zval_struct zval;

结构体重的type的取值如下表

PHP类型
IS_NULL 0 NULL
IS_LONG 1 整数
IS_DOUBLE 2 浮点
IS_STRING 3 字符串
IS_ARRAY 4 数组
IS_OBJECT 5 对象
IS_BOOL 6 布尔
IS_RESOURCE 7 资源(句柄)

这里写图片描述

zval结构体是PHP变量在内核中表示方式。—— PHP核心技术与最佳实践第260页


 $a = 1;
$b = '0';
var_dump($a);
var_dump($b);
$a = $b ; 
var_dump($a);

$a = (int) $b ; 
var_dump($a);

打印如下:

int(1)
string(1) "0"
string(1) "0"
int(0)

PHP变量有一个节省内存的方案:写时复制

也就是在变量赋值给另一个变量时并不会马上分配一个新的内存块,而是当这2个变量不一样时(其中有人变化了),才会重新分配一块内存去单独存放(内存的复制)。

//比如 
$a =2;
$a = $b;
// 此时$a$b相同,都指向一块相同的内存块,没有分配一个多余的内存块去
// 单独存放$a或者$b,下面改变$b
$b = 1;// 此时发现$b变化了,在分配一块内存去存放$b

对象的底层结构

对象的底层由属性数组和方法数组共同组成。
这里写图片描述

对象在zend引擎中的定义如下:

typedef struct _zend_object {
zend_class_entry *ce; // 类指针
HashTable  * properties;// 对象的属性
HashTable * guards; // 阻止递归调用
}zend_object;

值得注意的是,根据_zend_object结构体可以看到对象的属性和类指针是2个指针,也就是说一个类的对象和它的属性是分开了。
怎么说?解释一下:加入$a 是类test的对象,那么ce指针指向test类,也就是test类有的对象a都有,而对象特有的(比如动态创建的成员属性或者方法)在properties指针中,而不在ce指针中,这就导致了说为什么PHP对象可以动态创建成员。

class tests {

    public $a = '123';

} 
echo "class";
$a = new tests();
var_dump(new tests());
var_dump($a);
$a->b = 'abf';
echo "class";
var_dump(new tests());
var_dump($a);

打印如下:

classobject(tests)#2 (1) {
 ["a"]=>
 string(3) "123"
}
object(tests)#1 (1) {
 ["a"]=>
 string(3) "123"
}
classobject(tests)#2 (1) {
 ["a"]=>
 string(3) "123"
}
object(tests)#1 (2) {
 ["a"]=>
 string(3) "123"
 ["b"]=>
 string(3) "abf"
}

这个例子完美的证明了,当给对象动态创建成员属性时,并不影响类。当再次实例化类时,新的对象并没有以前对象动态创建的属性。精髓就在指针properties上。
指针ce也是对象与数组的区别的显著特征。对象有一个指向所属类的指针,而数组没有。

HashTable —PHP的灵魂

HashTable由HashTable和Bucket组成。为了不弄混,也为了不让读者蒙圈笔者尽量换个角度解释HashTable,尽量浅显易懂一点。

这里写图片描述

HashTable数据结构由2部分组成,它本身和外部的一个叫Bucket的结构体。
先看完整的HashTable结构体定义。

typedef  struct _hashtable {
uint nTableSize;// 记录Bucket数组的大小
uint nTableMask; // 等于nTableSize-1
uint nNumberOfElements;// 记录HashTable中元素的个数
ulong nNextFreeElement;// 下一个可用Bucket位置
Bucket * pInternalPointer;// 遍历HashTable元素
Bucket * pListHead;// 双链表表头
Bucket * pListTail;// 双链表表尾
Bucket * * arBuckets;// Bucket数组
dtor_func_t pDestructor;// pDestructor
zend_bool persistent;// persistent,是否使用PHP的内存管理默认是否则使用操作系统
unsigned char nApplyCount;// 0
zend_bool bApplyProtection;// 1
}HashTable;

// Bucket
typedef struct  bucket{
ulong h;// hash之后的hash值
uint  nKeyLength;// 索引的长度
void  *pData;// 保存的内存块地址
void  *pDataPtr;// 指针数据
struct  bucket *pListNext; // 指向双向链表的下一个元素 
struct  bucket *pListLast;// 指向双向链表的上一个元素
struct  bucket *pNext;// 指向相同hash值的下一个元素--拉链法
struct  bucket *pLast;// 指向相同hash值的上一个元素--拉链法
char    arKey[1]; // 保存索引,而且必须是最后一个成员???
}Bucket;

这里说明以下几点:
1 hashtable之所以维护双向链表是为了有序遍历hashtable中的元素;
2 nTableSize的大小不是PHP实际申请的内存的大小,PHP申请的内存大小是不小于nSize的2的n次方。比如要求申请7 那么 申请的大小是 2^n = 8>=7;
3 最好使用PHP的内存管理,操作系统去管理内存容易造成内存泄漏,persistent设置为true;

下面分析hashtable的插入过程:
1 申请内存(Bucket结构体保存索引和值);
2 索引复制到arkey中;
3 pData指向值;
4 把新Bucket添加具有相同hash值的双向链表中,没有就是该双向链表的第一个元素(主要通过宏 CONNECT_TO_BUCKET_DLLIST完成);
5 把新Bucket添加到Hashtable双向链表中(通过CONNECT_TO_GLOBAL_DLLIST宏完成);
6 插入完成后,进行统计,如果HashTable元素的个数大于arBuckets数组的大小(nNumOfElements大于nTableSize),将hashtable的arBuckets数组大小翻倍,始终保持nNumOfElements小于nTableSize。

以上是个人总结和读书笔记整理而来,有些地方可能有笔误望指正!!!
这里写图片描述

留个随堂练习:请问hashtable中有几个双向链表?

猜你喜欢

转载自blog.csdn.net/SchopenhauerZhang/article/details/79605550