第四章:第34节 PHP函数之静态变量

更新于:2021-05-10 14:02:19

我们普及下计算机底层的一些知识,PHP程序的运行需要借助计算机内存暂时存储数据的。


内存被分为5个区:

201308130909594.jpg


学C语言的需要去学这些知识,程序员可以用C语言操作内存,向内存申请空间。PHP不能直接操作内存,我们可以不用去学,简单地了解还是很有好处的,起码能明白很多事情,我们今天要学的静态变量就和这些知识有关系。


以下是楠神从网上找的简单介绍,里面的内容是说给学C语言的人看的。


从上图可知,程序占用的内存被分了以下几部分。


1、栈区(stack)


由编译器自动分配释放 ,存放函数的参数值,局部变量的值等,内存的分配是连续的,类似于平时我们所说的栈,如果还不清楚,那么就把它想成数组,它的内存分配是连续分配的,即,所分配的内存是在一块连续的内存区域内.当我们声明变量时,那么编译器会自动接着当前栈区的结尾来分配内存。


2、堆区(heap)
一般由程序员分配释放, 若程序员不释放,程序结束时可能由操作系统回收.类似于链表,在内存中的分布不是连续的,它们是不同区域的内存块通过指针链接起来的.一旦某一节点从链中断开,我们要人为的把所断开的节点从内存中释放。


3、全局区(静态区)(static)
全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 程序结束后由系统释放。


4、文字常量区
常量字符串就是放在这里的, 程序结束后由系统释放。


5、程序代码区
存放函数体的二进制代码。


更详细地介绍》》》


栈区很小,完全由系统支配,堆区是非常大的,C程序员可以在代码中申请空间。哪些数据是放在栈区,哪些放在堆区,像C里的变量名和部分值是放在栈区,大部分的数据需要程序员要放在堆区的。


PHP是由C开发出来的,那PHP的变量和C里的变量是一样的吗?推荐看一篇文章:《PHP变量在内存中的表示


PHP变量在C里是有一个结构体zval来表示的,变量的值是有一个联合体zvalue_value来表示的。不管这个结构体是不是存在栈区,就算在堆区栈区也要有一个变量指向这个结构体的地址。


所以说PHP定义的变量(全局的还有函数里的局部变量),都得占用栈区的空间。数据都是临时存储,PHP脚本运行完,占用的空间都会被释放掉。是不是在程序运行时,只会占用不会释放?其实不是这样的,PHP会自行判断哪些变量该释放,哪些不该释放。比如PHP函数里的变量,当一个函数结束时,这个函数里的变量就会提前释放掉


1.png

比如图中的函数变量$a,它在程序运行到第6行,在栈区为这个函数开辟一个栈(一种数据结构,先进后出),为变量$a申请一块空间,到了第9行,函数结束后,这个函数的栈也就从内存里消失了,里面的变量$a数据一块被释放掉了。


如果程序还没有结束,又一次用到这个函数或者新的函数,就又开辟一个栈,运行完函数释放栈。


内存里栈区虽然很小,程序能及时回收内存,不至于内存溢出(内存不够用了)。为什么说C语言难呢,就是程序员可以在堆区申请空间,如果程序员粗心大意,容易内存泄漏(程序运行完了,忘记把内存释放掉),泄漏多了就会溢出。


使用堆还是栈来存储数据是由PHP引擎决定的,PHP开发者不需要关心这些。


有时会有这样的需求,一个函数在程序里多次被调用,每次调用它函数里的变量都是新的,上一次调用时的变量早已被释放掉。如果我想保留其中的一个数据怎么办?意思是数据能够在函数退出后仍然保持不丢失。那这个变量就不能存在栈区了,需要存在另外一个地方——静态区。


在函数里面可以定义静态变量。语法: static 变量名


1.png

得到结果:

1.png


我们来分析下:

第16行第一次调用函数f1

第7行定义了一个静态变量

第10行在浏览器显示静态变量$result = 0

第12行把得到的值赋值给静态变量$result,此时$result的值是5

第13行返回值,函数结束,除了静态变量,其他的局部变量随函数的栈一块被释放掉。


第17行又一次调用函数f1

第7行函数发现$result先前已被定义过静态变量了,不会重新定义。其实第7行这一次不运行了,所以这个静态变量不会被赋值0,不会被重新初始化。

第10行在浏览器显示静态变量$result = 5

第12行把得到的值赋值给静态变量$result,此时$result的值是10.816653826392

第13行返回值,函数结束,除了静态变量,其他的局部变量随函数的栈一块被释放掉。


当整个程序结束,静态变量才会被释放掉。


静态变量在编程中很重要,合理的运用静态变量能看出程序员的编程水平来。它好比是函数里的缓存。


说一个应用场景:

当程序员开发一个超复杂的程序时,可能用一个函数需要从数据库获取一部分数据,这部分数据可能会在多个地方用,所以这个函数会被多次调用。如果调用一次就重新从数据库获取数据,那岂不是白白浪费很多资源。当函数第一次获取了数据,返回前先存在一个静态变量里,第二次调用函数,可以先做个判断,静态变量不为空,就直接返回静态变量的数据,这样就不用去数据库重新获取了。这就像是一种缓存。


下面这个函数是楠神正在开发的项目中定义的一个从数据库获取某个值的通用函数:

1.png

113行定义了一个静态变量是数组,121行判断静态数组变量某个下标的值是否为NULL。为NULL,利用122行代码重新获取值;不为NULL,126行直接返回静态数组变量某个下标的值。


静态变量在函数结束后不回收,就像上面的图中一样return返回,只是把它的值返回了。当把它的内存地址返回,以引用的形式返回:

1.png

结果:

1.png

需要在定义时和调用时都加上“&”,了解下就行,最好不要这样写,程序容易混乱。


说明:静态变量虽是在静态区开辟空间存储数据,也只能是哪个函数定义的静态变量只在那个函数里使用。如果不依靠引用传值相互影响,A函数是不能使用B函数里的静态变量,这是没有道理的事情。


结合这两节关于变量作用域的知识,我们可以推断出来文件加载的那4个语句结构require、require_once、include、include_once,不是函数了吧,它们是可以进行变量传输的。


本节学习代码》》》