[学习]php 变量范围

php 变量范围

在php函数内部不能访问外部的变量(使用global关键字和超全局变量除外)

例子:

$a = 123;
function ttt() {
    echo "\$a = {$a}";
}
ttt(); // 报错,变量a未定义

global关键字

使用global关键字可以让上面的例子正确输出结果。

$a = 123;
function ttt() {
    global $a;
    $a = $a * 10;
    echo "\$a = {$a}";
}
ttt(); // $a = 1230
echo $a; // 1230

函数外部访问函数内部声明的global变量:

function ttt() {
    global $a;
    $a = 10;
    echo "\$a = {$a}";
}
ttt(); // $a = 10
echo $a; // 10

可以用超全局变量$GLOBALS代替global关键字:

$a = 123;
function ttt() {
    $GLOBALS['a'] = $GLOBALS['a'] * 10;
    echo "\$a = {$GLOBALS['a']}";
}
ttt(); // $a = 1230
echo $a; // 1230

例子中的$GLOBALS是一个超全局变量,超全局变量是在全部作用域中始终可用的内置变量。
PHP 中的许多预定义变量都是“超全局的”,这意味着它们在一个脚本的全部作用域中都可用。在函数或方法中无需执行 global $variable; 就可以访问它们。

global关键字的原理

通过global关键字可以在函数内部访问外部的变量,但是官方手册上的例子却输出了意料之外的结果:

function test_global_ref() {
    global $obj;
    $new = new stdclass;
    $obj = &$new;
}

test_global_ref();
var_dump($obj); // 输出NULL

上面代码的输出结果是NULL,官方手册上的解释是:

在一个函数域内部用 global 语句导入的一个真正的全局变量实际上是建立了一个到全局变量的引用。
意思是说在函数内部用global关键字声明的变量与外部的同名变量不是同一个变量,而是对外部变量的一个引用。

用例子验证:

$a = 22;
$c = 33;
$b = &$a;
$b = $c;

var_dump($a); // 输出 int(33)
// 变量b引用变量a,改变b的值,a的值也跟着改变

再看下一个例子:

$a = 22;
$c = 33;
$b = &$a;
$b = &$c;

var_dump($a); // 输出 int(22)
// 变量b先引用变量a,然后又引用变量c,那么变量b与a的引用关系就解除了,b与a之间不再有任何关系,a的值不变

在官方手册的例子中,只是在函数内部用global关键字声明了变量$obj,函数外部并没有声明$obj,但是在外部打印$obj并没有报错,可能是php自动在外部添加了$obj的声明。

另外,在函数内部$obj又与函数内部的变量$new建立了引用关系,从而与函数外部的$obj解除了引用关系,所以在函数外部打印$obj为null。

再看一个使用$GLOBALS的例子:

function test_global_ref() {
    $new = new stdclass;
    $GLOBALS['obj'] = &$new;
}

test_global_ref();
var_dump($obj); // 

上面代码的输出符合预期:

class stdClass#1 (0) {
}

说明$GLOBALS['obj']$obj是一个变量,不是引用关系(注意:在函数外部并没有声明$obj,但是打印$obj时并没有报错)。

相关文章

静态变量

静态变量可以在三个地方声明:

  • 全局(php中在全局中自己定义的变量都是静态的,所以加不加static都一样)
  • 类 (可以看这个,不过通过实例对象也可以调用类的静态方法)
  • 函数

为什么说在全局中自己定义的变量都是静态的?函数作用域中静态变量的值被修改后,下次再访问就会得到修改后的值,而全局变量的值在某一处更改后,再在其他地方访问就是修改后的值,基于这一点,所以说全局中自己定义的全局变量都是静态的。

下面主要说说函数中的静态变量。

当函数执行完毕后,函数中的静态变量并不会丢失,当再次执行函数时,该函数中的静态变量会以上次函数执行完毕后的值为初始值。

function ttt() {
    static $a = 1;
    echo $a."\n";
    $a++;
}
ttt(); // 1
ttt(); // 2

静态声明是在编译时解析的。所以静态变量声明赋值时不能动态赋值(比如函数返回值,引入文件返回值等),例子:

class CC {
    static $name = sqrt(121);
}

function ttt() {
    static $a = 1 ? 1 : 0;
    echo $a;
}

ttt();
$c = new CC();

上面的代码会报语法错误,不会有结果输出。

函数静态变量的原理

函数的静态变量也是使用引用实现的。

静态变量没有引用赋值的例子(官方手册上的例子):

// 声明函数时在前面加上&,那么这个函数返回的是一个引用
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;
}

$obj2 = get_instance_noref();
var_dump($obj2);
$still_obj2 = get_instance_noref();
var_dump($still_obj2);

上面代码输出:

// 第一次调用函数时$obj的值
Static object: 
NULL
// $obj2的值
class stdClass#1 (1) {
  public $property =>
  int(1)
}
// 第二次调用函数时$obj的值
Static object: 
class stdClass#1 (1) {
  public $property =>
  int(1)
}
// $still_obj2的值
class stdClass#1 (1) {
  public $property =>
  int(2)
}
// 注意$obj,$obj2,$still_obj2输出的是同一个对象,从`stdClass#1`可以看出来

再看给静态变量引用赋值时的例子:

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;
}



$obj1 = get_instance_ref();
var_dump($obj1);
$still_obj1 = get_instance_ref();
var_dump($still_obj1);

上面代码的输出结果:

// 第一次调用函数时$obj的值
Static object: 
NULL
// $obj1的值
class stdClass#1 (1) {
  public $property =>
  int(1)
}
// 第二次调用函数时$obj的值
Static object: 
NULL
// $still_obj1的值
class stdClass#2 (1) {
  public $property =>
  int(1)
}
// $obj1与$still_obj1的值是两个不同的对象

从上面输出结果可已看出,给静态变量引用赋值之后,函数执行完毕后静态变量的值丢失了。每次函数执行,静态变量$obj都重新初始化,函数返回的也不是对同一个对象的引用。

结合这篇文章猜测:
语句static $obj;不仅仅只声明了一个变量,还建立了一个对静态内存区域的一个引用,这个内存区域是专门为函数的静态变量开辟的。
$obj = &$new;语句解除了这个引用关系,导致变量$obj表现的像一般的函数变量一样。

require和include

require和include的作用是一样的:包含和运行指定文件。

两者的区别是引入文件错误时,对错误的处理不一样:

  • require 在出错时产生 E_COMPILE_ERROR 级别的错误,导致脚本中止。
  • include 只产生警告(E_WARNING),脚本会继续运行。

当一个文件被包含时,其中所包含的代码继承了 include 所在行的变量范围。
从该处开始,调用文件在该行处可用的任何变量在被调用的文件中也都可用。
不过所有在包含文件中定义的函数和类都具有全局作用域。

PHP 中的所有函数和类都具有全局作用域,可以定义在一个函数之内而在之外调用,反之亦然。

如果 include 出现于调用文件中的一个函数里,则被调用的文件中所包含的所有代码将表现得如同它们是在该函数内部定义的一样。
所以它将遵循该函数的变量范围。此规则的一个例外是魔术常量,它们是在发生包含之前就已被解析器处理的。

test.php:

static $name = 'jack';
function ttt() {
    global $a;
    $a = $a + 10;
    echo 'in test.php!'."\n";
}
return 233;

index.php:

function ffs() {
    if(1) {
        include '/test.php';
        echo $name."\n";
    }
}

ffs();
ttt();

上面代码输出:

jack
in test.php!

require和include使用注意事项:

  • 大佬说不推荐使用require_onceinclude_once
  • 要清楚requireinclude是语句不是函数。
  • require常用在文件头部,用于引入一开始就要引入的文件;include常用于在程序执行过程中需要引入的文件。
  • 语法解析器在被引入文件的开头脱离 PHP 模式并进入 HTML 模式,到文件结尾处恢复。由于此原因,被引入文件中需要作为 PHP 代码执行的任何代码都必须被包括在有效的 PHP 起始和结束标记之中。
  • 可以在被包含文件中使用return语句指定返回值;不指定的情况下,成功引入文件时返回1。return语句会阻止被包含文件中代码的执行,但是return语句后面的函数声明不受影响。
posted @ 2021-08-30 17:18  Fogwind  阅读(102)  评论(0编辑  收藏  举报