理解PHP的变量,值与引用的关系

---

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

http://php.net/manual/zh/features.gc.refcounting-basics.php

- PHP: 引用的解释 - Manual

http://php.net/manual/zh/language.references.php

来自为知笔记(Wiz)

时间: 2024-10-12 21:09:11

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

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

转自:https://mp.weixin.qq.com/s?__biz=MzI4Njc5NjM1NQ==&mid=2247486882&idx=2&sn=0056f7c027b0db429650ea4df03985e5&chksm=ebd6328edca1bb9804927cb66bad1bb9d54e542bfea5b7ff9f9432603b919460ee601f12baf6&mpshare=1&scene=23&srcid=0113FJUlz

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

0 左值和右值 一个左值表达式代表的是对象本身,而右值表达式代表的是对象的值:变量也是左值. 1 右值引用作用 为了支持移动操作(包括移动构造函数和移动赋值函数),C++才引入了一种新的引用类型——右值引用,可以自由接管右值引用的对象内容. 2 右值引用绑定的对象 返回非引用类型的函数,产生右值的表达式(算术表达式.关系表达式.位.后置递增递减) 3 和左值引用的区别 绑定的对象(引用的对象)不同,左值引用绑定的是返回左值引用的函数.赋值.下标.解引用.前置递增递减 左值持久,右值短暂,右值只能

关于变量的理解和函数内参数的引用

首先理解变量包括两种不同数据类型的值:1.基本类型值 2.引用类型值 基本类型值为简单的数据段,如var name="tom": name.age=20: alert(name.age): //undefined  不能给基本类型的值无法添加属性. 引用类型值为可能由多个值构建的对象,如var person=new Object(): person.age=20: alert(person.age): //20  可以给基本类型的值添加属性. 函数内参数的传递:1.基本类型的值传递:f

java 成员变量、局部变量、静态变量、类变量、非静态变量、实例变量、向前引用、非法向前引用、静态代码块、非静态代码块

①java类的成员变量有俩种: 一种是被static关键字修饰的变量,叫类变量或者静态变量 另一种没有static修饰,为成员变量 ②通俗点说: 类的静态变量在内存中只有一个,java虚拟机在加载类的过程中为静态变量分配内存,静态变量位于方法区,被类的所有实例共享.静态变量可以直接通过类名进行访问,其生命周期取决于类的生命周期. 而实例变量取决于类的实例.每创建一个实例,java虚拟机就会为实例变量分配一次内存,实例变量位于堆区中,其生命周期取决于实例的生命周期. 注意点: 1.JAVA中初始化

C++ Primer 学习笔记_17_从C到C++(3)--引用、const引用、引用传递、引用作为函数返回值、引用与指针区别

欢迎大家阅读参考,如有错误或疑问请留言纠正,谢谢 一.引用 1.引用是给一个变量起别名 变量: 名称 空间 引用: 引用不是变量 引用仅仅是变量的别名 引用没有自己独立的空间 引用要与它所引用的变量共享空间 对引用所做的改变实际上是对它所引用的变量的改变 引用在定义的时候必须要进行初始化 引用一经初始化,不能重新指向其他变量 2.定义引用的一般格式: (1)类型  &引用名 = 变量名: (2)例如: int a=1; int &b=a; // b是a的别名,因此a和b是同一个单元 (3)

js中 原始值和引用值

在 ECMAScript 中,变量可以存在两种类型的值,即原始值和引用值. 原始值-----存储在栈(stack)中的简单数据段,也就是说,它们的值直接存储在变量访问的位置; 引用值-----存储在堆(heap)中的对象,也就是说,存储在变量处的值是一个指针(point),指向存储对象的内存处. 如果一个值是引用类型的,那么它的存储空间将从堆中分配.由于引用值的大小会改变,所以不能把它放在栈中,否则 会降低变量查寻的速度.相反,放在变量的栈空间中的值是该对象存储在堆中的地址.地址的大小是固定的,

javascript值和引用

JavaScript引用指向的是值. 简单值(即标量基本类型值,基本类型值,js中6类,null.undefined.boolean.number.string和symbol)总是通过值复制的方式来赋值/传递. 以数值为例,此时,变量a持有该值的一个复本,变量b持有他的另一个复本,不论b进行什么样的操作,也不再会影响a的值. 复合值——对象(包括数组和封装对象)和函数,则总是通过引用复制的方式来赋值/传递. 以数组为例,a和b分别指向同一个复合值[1,2,3]的两个不同引用,仅仅是指向,而非持有

深入理解数据类型、变量类型属性、内存四区和指针

数据类型可理解为创建变量的模具(模子):是固定内存大小的别名. 数据类型的作用:编译器预算对象(变量)分配的内存空间大小. 既能读又能写的内存对象,称为变量:若一旦初始化后不能修改的对象则称为常量. 变量本质:(一段连续)内存空间的别名. 内存四区 栈区(stack):也叫临时区,由编译器自动分配释放,存放函数的参数值,局部变量的值等. 堆区(heap):一般由程序员分配释放(动态内存申请与释放),若程序员不释放,程序结束时可能由操作系统回收. 全局区(静态区)(static):全局变量和静态变

【我的OOP学习笔记】值与引用(2)语义类型

值与引用 值语义的对象是独立的,语义的对象却是允许共享的.由于Java不支持值类型对象,Java程序员才更需要加强这方面的意识.语法和语义并不总是一致的——语法上的值类型可能在语义上是引用类型,语法上的引用类型可能在语义上是值类型.永远不要忘记一个基本原则:语法只是手段,语义才是目的. 为了判断一个类型的语义,那么简明的‘石蕊测试法’便是一个很好的选择.在不影响程序正确性的前提下,一个对象的复件能否代替原件?如果可以则该对象的类型是值语义的,否则是引用语义的.(这种判断方法与语法无关,完全取决于