php对引用的简单理解

背景

  php语言的高度封装和五花八门的库使这门语言很容易上手,而且开发效率比C/C++高出许多。但是也正是由于php封装度很高,一些在c语言中很简单的概念,让php这么一封装,就变得难以琢磨。比如引用,在c语言中的概念很简答, 就是两个变量名指向同一块内存。而且引用必须要你手动操作,哪个变量引用的哪块内存在编写代码的时候心里是一清二楚的。但是在php中,有很多地方使用了隐式的引用,而写代码的时候并不知道这是引用。这就很容易造成问题,而且难以发现。就比如下面的代码我没有使用引用啊?但是name的值确实被改变了。这种类型的错误一旦发生,很可能就应验了一句话,“一个小bug,一查一下午”,还不一定能查出来。

    

  输出:

    

  我没有使用引用啊?但是name的值确实被改变了。这种类型的错误一旦发生,很可能就应验了一句话,“一个小bug,一查一下午”,还不一定能查出来。所以这促使我去仔细了解一下php引用的规则,进而在以后的工作中尽量减少这种没有意义的错误。

  下面在梳理引用规则的时候,先不会管php的写时复制,尽可能从语义的角度来看待php的引用。下面的讨论仅适用于php5.

  

php内置类型的引用

  当使用php内置类型时,php的引用规则和C语言没有区别。例中测试了int, string, array,是预期的结果。只要函数的形参不是引用类型,就不会被莫名其妙的改变。

    

  输出:

    

 php对象的引用

  看下面的例子:

    

  看输出,坑爹的地方来了:

    

  不管函数的形参是引用类型还是非引用类型,obj的name成员都被更改了。

  在某篇博客上看到了这样的解释:“PHP 5 引进了独立于变量容器的『对象存储器』。当一个对象赋值给变量时,变量不再存储整个对象(属性表和其他的『类』信息),而是存储这个对象所在 存储器的引用 —— 当我们复制一个对象变量时,我们复制的是这个『存储器的引用』”。把这句话套到例子中就是,$obj现在是一个引用,引用的就是“new Name()”生成的对象存储器。既然知道了$obj只是个引用,那么解释上面例子为什么会这样输出就很容易了:

  对于fun函数,当实参赋值给形参的时候,相当于 $arg = $obj。当我们复制一个变量对象时,复制的是这个对象存储器的引用。那么现在的情况就是,$arg也引用刚才"new Name()"生成的对象存储器,这个对象存储器的引用计数是2。所以在fun函数中,我们操作$arg->name=“fun”,本质上就是让这个对象存储器中的name属性编程“fun”。当fun函数返回时,$arg被销毁,此对象存储器引用计数减一。但是$obj仍然引用着这个对象存储器,所以我们 echo $obj->name的时候,输出此对象存储器的name属性,即“fun”。

  对于funReference, 当实参赋值给形参时,相当于$arg = &$obj。$arg作为$obj的引用,而$obj作为对象存储器的引用。在解引用的时候,可以简单的认为,$arg->name="funReference"就是在直接操作对象存储器。所以funReference和fun造成了同样额结果。

  但是是不是$arg = $obj 和 $arg = &$obj 就完全等价了?事实证明,不是,举个例子:

    
  输出:

    

  可以看出,$obj2=$obj1,  $obj2复制的是对象存储器的引用,所以后续就算是$obj指向了其他的结构,$obj2仍然引用的是对象存储器。但是$obj3 = &obj1, $obj3是$obj1的引用,按照C语言的引用规则,当$obj1的指向发生改变时,$obj3也应该指向也会发生改变,使之和$obj1指向相同的内存。php也确实这么去实现了这个语义。所以当obj后来指向“abc”时,$obj3也指向了"abc"。php的引用指向在初始化后,还可以随意改变,真实让人脑壳疼。

  另外一个问题,php的对象没法被赋值吗?因为不管怎么弄,我拿到的都只是相当于对象存储器的引用。php开发者也想到这个问题了,我在网上找到了下面的两种办法:

    1. 给类实现一个__clone()方法。然后赋值的时候这样:$obj2 = clone $obj1;

    2. 使用序列化函数。$obj2 = unserialize(serialize($ojb1));

global,$GLOBALS, 引用

  先给个定义:

    $GLOBALS[‘var_name‘], 是全局变量$var本身

    global $val是全局变量的同名引用

  这两句话让人感觉有点云里雾里,举个例子就清楚了:

    

  输出:

    

  可以看到,var1并没有被改变,而var2则被改变了。

  先解释var1, 开头说了,global var 是 全局变量 var 的同名引用。也就是说,在fun()内部,var1只是全局变量var1的引用而已。看看fun()中做了什么?$var1=&$str, 让var1重新引用局部变量str。这个操作只是让fun内部的同名引用var1所引用的对象变了,和全局的var1一点关系都没有,所以var1不会改变。

  $GLOBALS的官方定义是,由所有已定义的全局变量所组成。$GLOBALS["var_name"]就是全局变量var_name本身。套到例子中,$GLOBALS["var2"]也就是全局变量var2本身,让全局变量引用fun中的局部变量str,所以var2的值就发生了改变。

  这两个概念一定要区分清楚,否则在平时编码中,就会发生想改变的全局变量没有被改变,但是不想被改变的全局变量反而被意外改变。而且这种bug还真不好找。

  

  

原文地址:https://www.cnblogs.com/MyOnlyBook/p/9688789.html

时间: 2024-10-22 01:20:34

php对引用的简单理解的相关文章

C++:引用的简单理解

前言:引用是C++一个很重要的特性,最近看了很多有关引用的资料和博客,故在此对引用的相关知识进行总结 一.什么是引用 引用,顾名思义是某一个变量或对象的别名,对引用的操作与对其所绑定的变量或对象的操作完全等价 语法:类型 &引用名=目标变量名: 特别注意: 1.&不是求地址运算符,而是起标志作用 2.引用的类型必须和其所绑定的变量的类型相同 1 #include<iostream> 2 using namespace std; 3 int main(){ 4 double a=

关于引用和指针的简单理解

指针和引用一直是C++中难懂的部分,为此我也困惑了很久,下面说说我对引用和指针的简单理解. 在使用函数时我们常常会纠结实参传递到形参后,函数对形参的操作会不会改变实参的值(也就是主函数的相应变量),最典型的例子是swap(int a,int b).我们都知道,函数在调用时会给变量重新开辟一个存储空间,而这个存储空间会暂时存储形参的值,如果函数在调用完毕后,其相应存储空间的值会被释放.例如下面一段代码: void swap1(int a, int b) { int temp = 0; temp =

Javascript闭包简单理解

提到闭包,想必大家都早有耳闻,下面说下我的简单理解.说实话平时工作中实际手动写闭包的场景并不多,但是项目中用到的第三方框架和组件或多或少用到了闭包.所以,了解闭包是非常必要的.呵呵... 一.什么是闭包简而言之,就是能够读取其他函数内部变量的函数.由于JS变量作用域的特性,外部不能访问内部变量,内部可以外部变量. 二.使用场景1. 实现私有成员.2. 保护命名空间,避免污染全局变量.3. 缓存变量. 先看一个封装的例子: var person = function () { // 变量作用域为函

对数据类型封装和数据抽象的简单理解

请特别关注程序设计技术,而不是各种语言特征. --<C++程序设计语言> Bjarne Stroustrup 本文是<C++程序设计语言>(Bjarne Stroustrup )的第二章的读书笔记,例子来源于这本书的第二章. 在程序设计之中,我们倾向于将数据结构(也可以说是数据类型)以及一组对其操作的相关过程组织在一起,在逻辑上可以称将其为模块.此时程序分为一些模块,模块包括一组对数据的操作,数据隐藏于模块之中.以下以栈的设计为例,使用C和C++进行设计,简单理解模块化设计中的数据

对象序列化原因的简单理解

序列化和反序列化我们可能经常会听到,其实通俗一点的解释,序列化就是把一个对象保存到一个文件或数据库字段中去,其最终目的都是将内存中的对象持久化或者是在网络上传输.反序列化就是在适当的时候把这个文件再转化成原来的对象使用. 使用序列化的原因 a. 一个原因是将对象的状态保持在存储媒体中,以便可以在以后重新创建精确的副本.我们经常需要将对象的字段值保存到磁盘中,并在以后检索此数据.尽管不使用序列化也能完成这项工作,但这种方法通常很繁琐而且容易出错,并且在需要跟踪对象的层次结构时,会变得越来越复杂.可

大话设计模式总结(28种设计模式定义+简单理解)

大话设计模式这本书写的非常有创意,非常适合我这种新手.用了大约两个星期的时间看完了这本书,代码全部都敲了一遍,虽然没有一点基础,但是还是领略到了面向对象的威力.看完之后再也不想使用面向过程的语言了,比如VB,想当初我也是VB狂热者,但是现在我几乎不想再使用了.现在只想着写点什么用上它几种设计模式. 可能是第一次接触这些东西,有些感觉看懂了,但是很难应用到实际编程中:有些感觉没看懂,但是还能说出那么点东西来.听七期学长说他们当初看了两遍,要求能背着写出代码,不知道这次我们八期要求怎么这么低,我只看

我们为之奋斗过的C#-----C#的一个简单理解

我们首先来简单叙述一下什么是.NET,以及C#的一个简单理解和他们俩的一个区别. 1 .NET概述 .NET是Microsoft.NET的简称,是基于Windows平台的一种技术.它包含了能在.NET Framework平台运行的所有编程语言. 2 C#概述 他是专门为.NET平台设计的一种语言. 3 .NET与C#的区别 .NET是一种平台,这种平台可以编译多种语言例如:VB,J#,而C#只是一种语言. 4 IDE IDE全称(Itergrated Developer Environment)

Js 职责链模式 简单理解

js 职责链模式 的简单理解.大叔的代码太高深了,不好理解. function Handler(s) { this.successor = s || null; this.handle = function () { if (this.successor) { this.successor.handle(); } } } var app = new Handler({ handle: function () { console.log('app handle'); } }); var dialo

简单理解IoC与DI

为了理解Spring的IoC与DI从网上查了很多资料,作为初学者,下面的描述应该是最详细,最易理解的方式了. 首先想说说IoC(Inversion of Control,控制倒转).这是spring的核心,贯穿始终.所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系.这是什么意思呢,举个简单的例子,我们是如何找女朋友的?常见的情况是,我们到处去看哪里有长得漂亮身材又好的mm,然后打听她们的兴趣爱好.qq号.电话号.ip号.iq号………,想办法认识她们,