变量一般由变量名、变量值、变量类型组成。PHP中变量名与变量值可以简单的对应为:zend、zend_value。

   PHP中变量的内存是通过引用计数进行管理的,在PHP7中引用计数已从之前的zval结构转移到了具体的value结构中,变量之间的赋值与传递通常也针对zend_value。PHP中通过$符号定义一个变量,在定义的同时可以初始化,这样在变量使用前不需要提前声明。

变量类型

PHP中变量类型也就是数据类型,宏观角度可分为如下几种:

  • 标量类型:字符串、整型、浮点型、布尔型;
  • 复合类型:数组、对象;
  • 特殊类型:资源、NULL;

当然也有一些基于基础数据类型产生的特殊类型,比如引用。

内部实现

   PHP中通过zval这个结构体来表示一个变量,而不同类型的变量值则通过zval嵌入的一个联合体zend_value来表示,除此之外zval还有两个特殊的union。

u1: 主要解决字节顺序问题,这里不细追究了,说实话我也看不懂;
u2: 这个结构纯粹辅助;value+u1占用空间为12byte,但系统进行字节对齐,分配16byte,于是浪费的那一点空间4byte利用起来,干点别的事情,如在散列表解决哈希冲突等;

   PHP中整型,浮点型的值直接存储在zend_value中,其他类型则是指针,指向具体类型的结构。另外zend_value没有布尔型,在PHP7中拆分为了true,false两种类型,通过type类型区分,不需要具体的value了;

字符串

   PHP中没有使用char来表示字符串,而是为字符串单独定义了:zend_string 结构,在zend_value中通过str指向具体的结构;

数组

   这可谓是PHP最牛逼的存在,底层实现为散列表;PHP中散列表映射关系:散列函数、中间映射表、元素数组;

哈希冲突

   为了解决散列表中不同元素key可能计算得到相同的哈希值,造成散列表插入冲突,PHP中把冲突的Bucket串成链表,这样一来中间映射表映射出的是一个Bucket链表,查找时需要遍历这个链表,逐个比较key,从而找到元素。Bucket会记录冲突元素在arData数组中的存储位置,在设置映射表时,如果发现中间映射表中要设置的位置已经被之前插入的元素占用了(值不是初始化的-1),那么会把已经存在的值保存到新插入的Bucket中,然后将映射表的值更新为新Bucket的存储位置,即每次会把冲突的元素插到开头。冲突的元素的保存位置没有直接放在Bucket中,而是保存在存储元素zval的u2结构中。

引用

   引用并不是一种独立的类型而是一种指向其他数据类型的结构,类似C语言中的指针概念。当修改引用类型变量时,其实就是在改实际的变量。在PHP中通过&操作符生成一个引用变量:

$a = &$b

执行时首先为&操作的变量分配一个zend_reference结构,这个结构就是引用类型的结构体,内嵌了一个zval,这个zval的value指向原来zval的value,然后将原来的zval类型修改为IS_REFERENCE,原zval的value指向新创建的zend_reference结构。也就是&是将变量的类型转化为了引用,新生成的引用结构指向原来的value。

需要注意的是:引用只能通过&产生,无法通过赋值传递

1
2
3
4
$a = date("Y-m-d");
$b = &$a;
//如果想让$c也可以引用$a的值,则:$c=&$b$c=&$a
$c = $b; //这里传的是value值,而不是引用本身

PHP中的引用只有一级,不会出现一个引用指向另外一个引用的情况,没有类似与C语言中多级指针的概念。

类型变换

   PHP是弱类型语言,使用时不需要明确定义变量类型,Zend虚拟机在执行PHP代码时会根据具体的应用场景进行装换,出了这种自动转换,PHP也提供了一种强制转换方式:

  • (int) :装换为整型
  • (bool):装换为布尔类型
  • (float):转换为浮点型
  • (string):转换为字符串
  • (array):转换为数组
  • (object):转换为对象
  • (unset):转换为NULL

当然也有些类型是不能转换的,如资源类型。