变量作用域

变量的作用域是定义它的上下文。在大多数情况下,所有 PHP 变量只有一个作用域。这个单一作用域跨越 includerequire 的文件。例如

<?php
$a
= 1;
include
'b.inc';
?>

在这里,$a 变量将在包含的 b.inc 脚本中可用。但是,在用户定义的函数中,会引入局部函数作用域。默认情况下,在函数内部使用的任何变量都局限于局部函数作用域。例如

<?php
$a
= 1; /* 全局作用域 */

function test()
{
echo
$a; /* 引用局部作用域变量 */
}

test();
?>

此脚本将生成一个未定义变量 **E_WARNING**(或在 PHP 8.0.0 之前为 **E_NOTICE**)诊断信息。但是,如果 display_errors INI 设置设置为隐藏此类诊断信息,则将不会输出任何内容。这是因为 echo 语句引用了 $a 变量的局部版本,并且在该作用域内没有为它分配任何值。您可能注意到这与 C 语言略有不同,因为 C 语言中的全局变量会自动对函数可用,除非明确地被局部定义覆盖。这会导致一些问题,因为人们可能会无意中更改全局变量。在 PHP 中,如果要在函数中使用全局变量,则必须在函数内部声明它们为全局变量。

global 关键字

首先,使用 global 的示例

示例 #1 使用 global

<?php
$a
= 1;
$b = 2;

function
Sum()
{
global
$a, $b;

$b = $a + $b;
}

Sum();
echo
$b;
?>

上面的脚本将输出 3。通过在函数内部声明 $a$b 为全局变量,对这两个变量的任何引用都将引用全局版本。函数可以操作的全局变量数量没有限制。

访问全局作用域变量的第二种方法是使用 PHP 定义的特殊 $GLOBALS 数组。前面的示例可以改写为

示例 #2 使用 $GLOBALS 而不是 global

<?php
$a
= 1;
$b = 2;

function
Sum()
{
$GLOBALS['b'] = $GLOBALS['a'] + $GLOBALS['b'];
}

Sum();
echo
$b;
?>

$GLOBALS 数组是一个关联数组,全局变量的名称是键,该变量的内容是数组元素的值。注意 $GLOBALS 在任何作用域中都存在,这是因为 $GLOBALS 是一个 超级全局变量。以下是一个演示超级全局变量和作用域功能的示例

示例 #3 演示超级全局变量和作用域

<?php
function test_superglobal()
{
echo
$_POST['name'];
}
?>

注意:

在函数外部使用 global 关键字不会导致错误。如果从函数内部包含该文件,则可以使用它。

使用 static 变量

变量作用域的另一个重要功能是 *静态* 变量。静态变量仅存在于局部函数作用域中,但在程序执行离开该作用域时不会丢失其值。考虑以下示例

示例 #4 演示对静态变量的需求

<?php
function test()
{
$a = 0;
echo
$a;
$a++;
}
?>

此函数相当无用,因为每次调用它时,它都会将 $a 设置为 0 并打印 0$a++ 用于递增变量,没有任何作用,因为一旦函数退出,$a 变量就会消失。为了创建不会丢失当前计数的实用计数函数,$a 变量被声明为静态的

示例 #5 静态变量的使用示例

<?php
function test()
{
static
$a = 0;
echo
$a;
$a++;
}
?>

现在,$a 只在第一次调用函数时初始化,并且每次调用 test() 函数时,它都会打印 $a 的值并递增它。

静态变量也提供了一种处理递归函数的方法。递归函数是调用自身的函数。编写递归函数时需要小心,因为有可能让它无限递归。您必须确保有足够的方法来终止递归。以下简单函数使用静态变量 $count 递归地计数到 10,以了解何时停止

示例 #6 递归函数中的静态变量

<?php
function test()
{
static
$count = 0;

$count++;
echo
$count;
if (
$count < 10) {
test();
}
$count--;
}
?>

静态变量可以被赋值为常量表达式的结果,但动态表达式(如函数调用)会导致解析错误。

示例 #7 声明静态变量

<?php
function foo(){
static
$int = 0; // 正确
static $int = 1+2; // 正确
static $int = sqrt(121); // 错误 (因为它是函数)

$int++;
echo
$int;
}
?>

从 PHP 8.1.0 开始,当使用静态变量的方法被继承(但没有被覆盖)时,继承的方法现在将与父方法共享静态变量。这意味着方法中的静态变量现在与静态属性的行为相同。

示例 #8 继承方法中静态变量的使用

<?php
class Foo {
public static function
counter() {
static
$counter = 0;
$counter++;
return
$counter;
}
}
class
Bar extends Foo {}
var_dump(Foo::counter()); // int(1)
var_dump(Foo::counter()); // int(2)
var_dump(Bar::counter()); // int(3), 在 PHP 8.1.0 之前为 int(1)
var_dump(Bar::counter()); // int(4), 在 PHP 8.1.0 之前为 int(2)
?>

注意:

静态声明在编译时解析。

使用 globalstatic 变量的引用

PHP 使用 staticglobal 修饰符来实现函数中的变量引用,例如,使用 global 语句导入到函数范围内的全局变量实际上会创建对全局变量的引用。这会导致以下示例中提到的意外行为。

<?php
function test_global_ref() {
global
$obj;
$new = new stdClass;
$obj = &$new;
}

function
test_global_noref() {
global
$obj;
$new = new stdClass;
$obj = $new;
}

test_global_ref();
var_dump($obj);
test_global_noref();
var_dump($obj);
?>

以上示例的输出结果为

NULL
object(stdClass)#1 (0) {
}

类似的行为也适用于 static 语句。引用不会被静态存储。

<?php
function &get_instance_ref() {
static
$obj;

echo
'Static object: ';
var_dump($obj);
if (!isset(
$obj)) {
$new = new stdClass;
// 将引用分配给静态变量
$obj = &$new;
}
if (!isset(
$obj->property)) {
$obj->property = 1;
} else {
$obj->property++;
}
return
$obj;
}

function &
get_instance_noref() {
static
$obj;

echo
'Static object: ';
var_dump($obj);
if (!isset(
$obj)) {
$new = new stdClass;
// 将对象分配给静态变量
$obj = $new;
}
if (!isset(
$obj->property)) {
$obj->property = 1;
} else {
$obj->property++;
}
return
$obj;
}

$obj1 = get_instance_ref();
$still_obj1 = get_instance_ref();
echo
"\n";
$obj2 = get_instance_noref();
$still_obj2 = get_instance_noref();
?>

以上示例的输出结果为

Static object: NULL
Static object: NULL

Static object: NULL
Static object: object(stdClass)#3 (1) {
  ["property"]=>
  int(1)
}

此示例演示了将引用分配给静态变量时,第二次调用 &get_instance_ref() 函数时不会 *记住* 引用。

添加说明

用户贡献说明 9 条说明

dodothedreamer at gmail dot com
12 年前
请注意,与 Java 和 C++ 不同,在循环或 if 语句等块中声明的变量在块外部也能够被识别和访问,例如:
<?php
for($j=0; $j<3; $j++)
{
if(
$j == 1)
$a = 4;
}
echo
$a;
?>

将打印 4。
warhog at warhog dot net
18 年前
一些有趣的行为(使用 PHP5 测试),在类方法中使用 static 范围关键字。

<?php

class sample_class
{
public function
func_having_static_var($x = NULL)
{
static
$var = 0;
if (
$x === NULL)
{ return
$var; }
$var = $x;
}
}

$a = new sample_class();
$b = new sample_class();

echo
$a->func_having_static_var()."\n";
echo
$b->func_having_static_var()."\n";
// 这将输出(预期):
// 0
// 0

$a->func_having_static_var(3);

echo
$a->func_having_static_var()."\n";
echo
$b->func_having_static_var()."\n";
// 这将输出:
// 3
// 3
// 您可能期望:
// 3
// 0

?>

您可能会认为 "3 0" 应该被输出,因为您可能认为 `$a->func_having_static_var(3);` 只改变了函数 "在" `$a` 中的静态 `$var` 的值 - 但是正如名称所暗示的,这些是类方法。拥有一个对象只是一个属性集合,函数仍然在类中。因此,如果您在函数内部声明一个变量为静态,它对于整个类及其所有实例都是静态的,而不是对于每个对象都是静态的。

也许发布这个毫无意义.. 因为如果你想要我期望的行为,你可以简单地使用对象本身的变量。

<?php
class sample_class
{ protected $var = 0;
function
func($x = NULL)
{
$this->var = $x; }
}
?>

我相信所有正常思维的人永远不会尝试使用 static 关键字来实现这一点,对于那些尝试过的人(像我一样),这个说明可能会有所帮助。
andrew at planetubh dot com
15 年前
花了我比预期的更长的时间才弄清楚这一点,并且认为其他人可能会发现它有用。

我创建了一个函数(safeinclude),我用它来包含文件;它在实际包含文件之前进行处理(确定完整路径,检查文件是否存在等)。

问题:由于包含发生在函数内部,包含文件中的所有变量都继承了函数的变量范围;由于包含的文件可能需要或可能不需要在其他地方声明的全局变量,因此会产生问题。

大多数地方(包括这里)似乎通过以下内容解决了这个问题:
<?php
// 在包含之前声明此变量
global $myVar;
// 或者在包含文件中声明此变量
$nowglobal = $GLOBALS['myVar'];
?>

但是,为了使这在这种情况(标准 PHP 文件包含在一个函数中,从另一个 PHP 脚本调用;重要的是要访问可能存在的任何全局变量)下工作,对于“safeinclude”包含的每个 PHP 文件中的每个变量都采用上述方法是不切实际的,而且静态地命名“global $this”方法中的所有可能的变量也不切实际。(主要是由于代码被模块化,而“safeinclude”应该通用)

我的解决方案:因此,为了使我所有的全局变量对使用 safeinclude 函数包含的文件可用,我必须在我的 safeinclude 函数中添加以下代码(在使用变量或包含文件之前)

<?php
foreach ($GLOBALS as $key => $val) { global $$key; }
?>

因此,完整的代码看起来像下面这样(非常基本的模型)

<?php
function safeinclude($filename)
{
// 这行代码获取所有全局变量,并在函数内设置它们的范围:
foreach ($GLOBALS as $key => $val) { global $$key; }
/* 这里进行预处理:验证文件名输入,确定文件的完整路径
文件,检查文件是否存在,等等。这显然不是
必要的,但我发现有用的步骤。 */
if ($exists==true) { include("$file"); }
return
$exists;
}
?>

在上面,'exists' 和 'file' 在预处理中确定。File 是文件的完整服务器路径,如果文件存在,则 exists 设置为 true。当然,这个基本模型可以扩展。在我自己的模型中,我添加了额外的可选参数,以便我可以调用 safeinclude 来查看文件是否存在,而无需实际包含它(为了利用我的路径/等等预处理,而不是仅仅调用文件存在函数)。

非常简单的方法,我在网上找不到;我唯一能找到的另一种方法是使用 PHP 的 eval()。
randallfstewart at gmail dot com
6 个月前
注意,函数内部的 global 关键字至少做了 2 件事

1) 如手册中所述,它允许函数使用变量的*全局版本*:"……对任一变量的所有引用都将引用*全局版本*。"[强调是我的]

2) 如手册中未提及,如果变量在全局范围内还不存在,它将在全局范围内创建。

例如,在下面的代码中,变量 $A 在全局范围内可用(在调用 functionA 之后),即使它从未在全局范围内声明

<?php

echo "<p>This is A before functionA is called: {$A}.</p>";

functionA();

function
functionA(){
global
$A;
$A = "Declared as global inside functionA";
}
// end fcn callGlobal

echo "<p>This is A after functionA is called: {$A}</p>";
?>

结果
Notice: Undefined variable: A in /home/essma/public_html/global_test.php on line 3

This is A before functionA is called: .

This is A after functionA is called: Declared as global inside functionA
gried at NOSPAM dot nsys dot by
8 年前
实际上,所有变量都代表指向内存区域地址的指针,该内存区域包含分配给该变量的数据。当您通过引用分配某个变量值时,您实际上将源变量的地址写入接收变量。当您在函数中将某个变量声明为全局变量时,也会发生同样的情况,它会收到与函数外部的全局变量相同的地址。如果您考虑上述解释,很明显,将使用关键字 global 声明的相同变量和通过超全局数组同时使用它们混合使用是一个非常糟糕的想法。在某些情况下,它们可能指向不同的内存区域,给您带来麻烦。考虑以下代码

<?php

error_reporting
(E_ALL);

$GLOB = 0;

function
test_references() {
global
$GLOB; // 使用关键字 global 获取对全局变量的引用,此时本地变量 $GLOB 指向与全局变量 $GLOB 相同的地址
$test = 1; // 声明一些本地变量
$GLOBALS['GLOB'] = &$test; // 使全局变量引用此本地变量,使用超全局数组,此时全局变量 $GLOB 指向新的内存地址,与本地变量 $test 相同

$GLOB = 2; // 通过先前设置的本地表示为全局变量设置新值,写入旧地址

echo "Value of global variable (via local representation set by keyword global): $GLOB <hr>";
// 通过本地表示检查全局变量 => 2(正常,获得了刚刚写入的值,因为使用了旧地址来获取值)

echo "Value of global variable (via superglobal array GLOBALS): $GLOBALS[GLOB] <hr>";
// 使用超全局数组检查全局变量 => 1(获得了本地变量 $test 的值,使用了新地址)

echo "Value ol local variable \$test: $test <hr>";
// 检查与使用超全局数组链接的全局变量 => 1(其值没有受到影响)

global $GLOB; // 使用关键字 global 更新对全局变量的引用,此时我们将更新本地变量 $GLOB 中保存的地址,它将获得与本地变量 $test 相同的地址
echo "Value of global variable (via updated local representation set by keyword global): $GLOB <hr>";
// 通过本地表示检查全局变量 => 1(也是本地变量 $test 的值,使用了新地址)
}

test_references();
echo
"Value of global variable outside of function: $GLOB <hr>";
// 检查函数外的全局变量 => 1(等于函数中本地变量 $test 的值,全局变量也指向新地址)
?>
larax at o2 dot pl
18 年前
关于使用全局变量的更复杂的情况。

假设我们有两个文件
a.php
<?php
function a() {
include(
"b.php");
}
a();
?>

b.php
<?php
$b
= "something";
function
b() {
global
$b;
$b = "something new";
}
b();
echo
$b;
?>

您可能希望这段脚本返回“something new”,但事实并非如此,它将返回“something”。为了使其正常工作,您必须在 $b 定义中添加 global 关键字,在上面的例子中,它将是

global $b;
$b = "something";
Michael Bailey (jinxidoru at byu dot net)
20 年前
静态变量不通过继承保留。假设类 A 有一个带有静态变量的函数 Z。假设类 B 扩展类 A,其中函数 Z 没有被覆盖。将创建两个静态变量,一个用于类 A,另一个用于类 B。

看看这个例子

<?php
class A {
function
Z() {
static
$count = 0;
printf("%s: %d\n", get_class($this), ++$count);
}
}

class
B extends A {}

$a = new A();
$b = new B();
$a->Z();
$a->Z();
$b->Z();
$a->Z();
?>

这段代码返回

A: 1
A: 2
B: 1
A: 3

正如您所看到的,类 A 和 B 使用不同的静态变量,即使使用了相同的函数。
jameslee at cs dot nmt dot edu
19 年前
需要注意的是,方法内部的静态变量在该类的所有实例中都是静态的,即该类所有对象共享相同的静态变量。例如,代码

<?php
class test {
function
z() {
static
$n = 0;
$n++;
return
$n;
}
}

$a =& new test();
$b =& new test();
print
$a->z(); // 打印 1,因为它应该打印
print $b->z(); // 打印 2,因为 $a 和 $b 具有相同的 $n
?>

有点出乎意料地打印
1
2
dexen dot devries at gmail dot com
7 年前
如果在类的某个方法中定义了静态变量,那么该类所有**直接**实例都会共享该静态变量。

但是,如果创建派生类,则该派生类所有**直接**实例将共享一个**独立**的静态变量副本,该副本位于方法中。

换句话说,方法中的静态变量与类绑定(而不是与实例绑定)。每个子类都有该变量的副本,供其实例共享。

换句话说,创建派生类时,它“似乎”会创建基类方法的副本,从而创建这些方法中静态变量的副本。

已在 PHP 7.0.16 上测试。

<?php

require 'libs.php';
require
'setup.php';

class
Base {
function
test($delta = 0) {
static
$v = 0;
$v += $delta;
return
$v;
}
}

class
Derived extends Base {}

$base1 = new Base();
$base2 = new Base();
$derived1 = new Derived();
$derived2 = new Derived();

$base1->test(3);
$base2->test(4);
$derived1->test(5);
$derived2->test(6);

var_dump([ $base1->test(), $base2->test(), $derived1->test(), $derived2->test() ]);

# => array(4) { [0]=> int(7) [1]=> int(7) [2]=> int(11) [3]=> int(11) }

# $base1 和 $base2 共享静态变量 $v 的一个副本
# derived1 和 $derived2 共享静态变量 $v 的另一个副本
To Top