---
title: 理解PHP的变量,值与引用的关系
createdDate: 2015-03-11
category: php
---

PHP的变量与C++中的变量是两种截然不容的概念。如果没有理解清楚,使用C++的方式来思考PHP就会遇到一些问题。

C++中,变量与值是绑定的。值是内存的上的一块内存上的数据,而变量则是操作这块内存的名称。变量消失(比如超出作用域)值也会消失。

而PHP中,变量和值是两个概念。PHP是一种弱类型语言,值在PHP的内部(zend引擎),被存放在一个zval结构体中,这个结构体中,除了包含了值的类型,数据外,还包含两个字节的信息。一个是`is_ref`,是一个bool值,用来标识这个值是否是一个`引用`。第二个额外字节是`refcount`,用来表示指向这个值的变量(也称符号即symbol)的个数。如果refcount为0,那么这个值就可以被回收了。

而变量则另外存放在一个符号表中,每个变量有其作用域。

声明一个变量时:

```
$a = 'hello';
```

在当前作用域新建了一个变量a,并在内存中新建了一个zval,其类型字段为string,数据字段为'hello'。因为a不是一个引用变量,所以is_ref字段为false,因为$a指向了这个zval(注意,虽然是指向这个zval了,但是a依然不是引用变量),所以refcount为1。关系大概类似这样:



注意一点,**当"refcount"的值是1时,"is_ref"的值总是FALSE**。这一点往后就明白了。

## 输出"refcount"和"is_ref"

如果按照了xdebug,可以使用`xdebug_debug_zval()`函数来输出"refcount"和"is_ref"的值。

```
$a = 'hello';
xdebug_debug_zval('a');
```

输出:
```
(refcount=1, is_ref=0),string 'hello' (length=5)
```

我们可以利用这个函数来更好的理解zval。

## 赋值的小细节

先来看那PHP中赋值的例子:

```
$a = "hello";
$b = $a;
xdebug_debug_zval( 'a' );
xdebug_debug_zval( 'b' );
```

输出

```
a:
(refcount=2, is_ref=0),string 'hello' (length=5)
b:
(refcount=2, is_ref=0),string 'hello' (length=5)
```

既然PHP中除了object的赋值外都是复制,那么为什么a的refcount会增加呢?

我们修改b的值看看会不会有什么变化:

```
$a = "hello";
$b = $a;
xdebug_debug_zval( 'a' );
xdebug_debug_zval( 'b' );

$b = 2;
xdebug_debug_zval('a');
xdebug_debug_zval('b');
```

输出:
```
a:
(refcount=2, is_ref=0),string 'hello' (length=5)
b:
(refcount=2, is_ref=0),string 'hello' (length=5)
a:
(refcount=1, is_ref=0),string 'hello' (length=5)
b:
(refcount=1, is_ref=0),int 2
```

看到了么!如果只是赋值而没有修改的话,PHP不会立马拷贝值,而是让b指向a的值,所以a的zval的refcount为2。但是当我们修改b时,PHP就必须进行复制了。所以a的zval的refcount回到1。

用图片表示更直观:

$b = $a:



修改b后:



## 理解引用

理解了PHP中值与引用的关系,再来看PHP的引用就非常简单了。引用变量不会有新开一个zval,而是指向他所引用的变量的zval。并且,zval的`is_ref`置为1,refcount加一。

把上面的例子改为引用赋值,看看区别:
```
a:
(refcount=2, is_ref=1),string 'hello' (length=5)
b:
(refcount=2, is_ref=1),string 'hello' (length=5)
a:
(refcount=2, is_ref=1),int 2
b:
(refcount=2, is_ref=1),int 2
```

输出:

```
a:
(refcount=2, is_ref=1),string 'hello' (length=5)
b:
(refcount=2, is_ref=1),string 'hello' (length=5)
a:
(refcount=2, is_ref=1),int 2
b:
(refcount=2, is_ref=1),int 2
```

可以看出a和b都是指向一个zval的变量。



在代码的最后把a变量删除会有什么效果呢?

```
$a = "hello";
$b = &$a;
xdebug_debug_zval( 'a' );
xdebug_debug_zval( 'b' );

$b = 2;
xdebug_debug_zval('a');
xdebug_debug_zval('b');

unset($a);
xdebug_debug_zval('b');
```

输出:
```
a:
(refcount=2, is_ref=1),string 'hello' (length=5)
b:
(refcount=2, is_ref=1),string 'hello' (length=5)
a:
(refcount=2, is_ref=1),int 2
b:
(refcount=2, is_ref=1),int 2
b:
(refcount=1, is_ref=0),int 2
```

删除a变量后,b的zval的refcount减为1,这个可以理解,这里需要注意的是,b作为一个引用变量,现在他的zval的`is_ref`为0!也就是它不再是引用变量了。

这是因为a变量被删除后,b变为唯一一个指向这个zval的变量了,也因此,他也就摆脱了引用变量这个身份。这也就是上文所说的**当"refcount"的值是1时,"is_ref"的值总是FALSE**



## 参考网址
- PHP: 引用计数基本知识 - Manual
- PHP: 引用的解释 - Manual

理解PHP的变量,值与引用的关系的更多相关文章

  1. 理解--->Java中的值传递&引用传递

    转自:http://url.cn/5tL9F5D 值传递和引用传递 值传递(pass by value)是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际 ...

  2. Javascript参数传递中值和引用的一种理解

    值(value)和引用(reference)是各种编程语言老生常谈的话题,js也不例外. 我将剖析一个例子的实际运行过程,跟大家分享我对js参数传递中的值和引用的理解. 参考官网数据类型的两种分类,本 ...

  3. 【原创】深入理解c++的右值引用

    0 左值和右值     一个左值表达式代表的是对象本身,而右值表达式代表的是对象的值:变量也是左值.   1 右值引用作用 为了支持移动操作(包括移动构造函数和移动赋值函数),C++才引入了一种新的引 ...

  4. javascript值和引用

    JavaScript引用指向的是值. 简单值(即标量基本类型值,基本类型值,js中6类,null.undefined.boolean.number.string和symbol)总是通过值复制的方式来赋 ...

  5. PHP javascript 值互相引用(不用刷新页面)

    PHP javascript 值互相引用的问题   昨天通过EMAIL给一些公司投了简历,希望他们能给我一份工作,今天其中一家公司的人给我打电话,大意是要我做一点东西(与AJAX有关) 给他们看,我听 ...

  6. C++ : 从栈和堆来理解C#中的值类型和引用类型

    C++中并没有值类型和引用类型之说,标准变量或者自定义对象的存取默认是没有区别的.但如果深入地来看,就要了解C++中,管理数据的两大内存区域:栈和堆. 栈(stack)是类似于一个先进后出的抽屉.它的 ...

  7. JS中原始值和引用值的储存方式

    在ECMAscript中,变量可以存放两种类型的值,即原始值和引用值 原始值指的是代表原始数据类型的值,也叫基本数据类型,包括:Number.Stirng.Boolean.Null.Underfine ...

  8. C/C++-左值、右值及引用

    目录 1.左值and右值 2.引用 3.左值引用的用途 4.std::move和std::swap C和C++中定义了引用类型(reference type),存在左值引用(lvalue refere ...

  9. 理解Javascript的变量提升

    前言 本文2922字,阅读大约需要8分钟. 总括: 什么是变量提升,使用var,let,const,function,class声明的变量函数类在变量提升的时候都有什么区别. 参考文章:Hoistin ...

随机推荐

  1. spring boot&&cloud干货系列

    接触spring boot也有些时日了,刚开始博主还想参照官方参考指南自己写一个系列的入门式的教程,包含spring boot的所有模块的用法,后来发现,有一大波优秀的系列文章和项目了,所以就没班门弄 ...

  2. 简单理解Hash算法的作用

    什么是Hash Hash算法,简称散列算法,也成哈希算法(英译),是将一个大文件映射成一个小串字符.与指纹一样,就是以较短的信息来保证文件的唯一性的标志,这种标志与文件的每一个字节都相关,而且难以找到 ...

  3. Centos7 环境准备

    Centos7 环境准备 #关闭防火墙 systemctl stop firewalld systemctl disable firewalld #关闭selinux sed -i 's/SELINU ...

  4. php上传文件限制

    客户端限制(客户端限制在实际上是无法阻止上传): 通过表单隐藏域限制上传文件的最大值 <input type=’hidden’ name=’MAX_FILE_SIZE’ value=’字节数’ ...

  5. 《深入浅出MyBatis技术原理与实战》——6. MyBatis的解析和运行原理

    MyBatis的运行分为两大部分,第一部分是读取配置文件缓存到Configuration对象,用以创建SqlSessionFactory,第二部分是SqlSession的执行过程. 6.1 涉及的技术 ...

  6. 《java并发编程实战》读书笔记3--对象的组合

    希望将一些现有的线程安全组件组合为更大规模的组件或程序 设计线程安全的类 如果对象中所有的域是基本类型变量,那么这些域将构成对象的全部状态.例如,LinkedList的状态就包括该链表中所有节点对象的 ...

  7. python 几种循环性能测试: while, for, 列表生成式, map等

    直接上代码: #!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2018/07/24 16:23 import itertools imp ...

  8. ubuntu16.04下安装docker

    原文地址:传送门 本文开发环境为Ubuntu 16.04 LTS 64位系统,通过apt的docker官方源安装最新的Docker CE(Community Edition),即Docker社区版,是 ...

  9. javascript大神修炼记(5)——OOP思想(封装)

    读者朋友们好,前面我们已经讲解了有关javascript的基础,从今天的内容开始,我们就要开始讲有关封装的内容了,这里,我们就一点一点地接触到OOP(面向对象编程)了,如果作为一门语言使用的程序员连O ...

  10. eclipse开发文档模板

    从files依次向下:Window->Preference->Java->Code Style->Code Template 然后展开Comments节点就是所有需设置注释的元 ...