• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
小怪物
猥琐但不邪恶
博客园    首页    新随笔    联系   管理    订阅  订阅
{php 变量}

#一、PHP的变量定义:变量用于存储值,比如数字、文本字符串或数组、五中:string /integer /double /array /object 

命名规则:

1、PHP的变量名是区分大小写的。

2、变量名必须以$开头

3、变量名开头可以是下划线

4、变量名不能以数字字符开头
5.$this 是一个特殊的变量,它不能被赋值。
<?php
  $var = 'Bob';
  $Var = 'Joe';
  echo "$var, $Var";      // 输出 "Bob, Joe"

  $_4site = 'not yet';     // 合法变量名;以下划线开头
  $i站点is = 'mansikka';   // 合法变量名;可以用中文
  $long='123';
  $$long="PHP";          // 合法变量; 用存放在变量$long里的字符串作为新变量的变量名,等同于$big_long_variable_name="PHP"; */  
 
  vars='no';                 // xxx非法变量名;没以$开头
  $4site = 'not yet';      // xxx非法变量名;以数字开头
?>

#二、php变量的赋值

<?php
         $shit='123';  	//一般“=”来赋值
         $$shit=="456";
         $shit2=& $shit;//引用赋值
         echo $shit2;	//123
         
         $shit='fuck';	//值被修改
         echo $shit;	//fuck
?>

*只有有名字的变量才可以引用赋值:

<?php
	$foo = 25;
	$bar = &$foo;      // 合法的赋值
	$bar = &(24 * 7);  // 非法; 引用没有名字的表达式

	function test()
	{
	   return 25;
	}

	$bar = &test();    // 非法
?>

#变量默认值

虽然在 PHP 中并不需要初始化变量,但对变量进行初始化是个好习惯。未初始化的变量具有其类型的默认值

- 布尔类型的变量默认值是 FALSE,

- 整形和浮点型变量默认值是零,

- 字符串型变量默认值是空字符串null或者数组变量的默认值是空数组。

*依赖未初始化变量的默认值在某些情况下会有问题,例如把一个文件包含到另一个之中时碰上相同的变量名。

 另外把php.in register_globals 打开是一个主要的安全隐患。使用未初始化的变量会发出E_NOTICE错误,

 但是在向一个未初始化的数组附加单元时不会。

 isset() 语言结构可以用来检测一个变量是否已被初始化。

<?php
// Unset AND unreferenced (no use context) variable; outputs NULL
var_dump($unset_var);
//isset() 语言结构可以用来检测一个变量是否已被初始化
echo(isset($unset_var)
?"true\n":"false\n");

// Boolean usage; outputs 'false' (See ternary operators for more on this syntax)
echo($unset_bool ? "true\n" : "false\n");

// String usage; outputs 'string(3) "abc"'
$unset_str .= 'abc';
var_dump($unset_str);

// Integer usage; outputs 'int(25)'
$unset_int += 25; // 0 + 25 => 25
var_dump($unset_int);

// Float/double usage; outputs 'float(1.25)'
$unset_float += 1.25;
var_dump($unset_float);

// Array usage; outputs array(1) { [3]=> string(3) "def" }
$unset_arr[3] = "def"; // array() + array(3 => "def") => array(3 => "def")
var_dump($unset_arr);

// Object usage; creates new stdClass object
// Outputs: object(stdClass)#1 (1) { ["foo"]=> string(3) "bar" }
$unset_obj->foo = 'bar';
$unset_obj
->shit = '234';
var_dump($unset_obj);
var_export($unset_obj);
?>

#预定义变量

Warning

PHP 4.2.0 以及后续版本中,PHP 指令 register_globals 的默认值为 off。这是 PHP 的一个主要变化。让 register_globals 的值为 off 将影响到预定义变量集在全局范围内的有效性。例如,为了得到 DOCUMENT_ROOT 的值,将必须使用 $_SERVER['DOCUMENT_ROOT'] 代替$DOCUMENT_ROOT,又如,使用 $_GET['id'] 来代替 $id 从 URL http://www.example.com/test.php?id=3 中获取 id 值,亦或使用$_ENV['HOME'] 来代替 $HOME 获取环境变量 HOME 的值。

更多相关信息,请阅读 register_globals 的配置项条目,安全一章中的使用 Register Globals,以及 PHP » 4.1.0 和 » 4.2.0 的发布公告。

如果有可用的 PHP 预定义变量那最好用,如超全局数组。

从 PHP 4.1.0 开始,PHP 提供了一套附加的预定数组,这些数组变量包含了来自 web 服务器(如果可用),运行环境,和用户输入的数据。这些数组非常特别,它们在全局范围内自动生效,例如,在任何范围内自动生效。因此通常被称为自动全局变量(autoglobals)或者超全局变量(superglobals)。(PHP 中没有用户自定义超全局变量的机制。)超全局变量罗列于下文中;但是为了得到它们的内容和关于 PHP 预定义变量的进一步的讨论以及它们的本质,请参阅预定义变量。而且,你也将注意到旧的预定义数组($HTTP_*_VARS)仍旧存在。自 PHP 5.0.0 起, 用 register_long_arrays 设置选项可禁用 长类型的 PHP 预定义变量数组。

Note: 可变变量

超级全局变量不能被用作可变变量。

Note:

尽管超全局变量和 HTTP_*_VARS 同时存在。但是他们并不是同一个变量,所以改变一个的值并不会对另一个产生影响。

如果某些 variables_order 中的变量没有设定,它们的对应的 PHP 预定义数组也是空的。

预定义变量

对于全部脚本而言,PHP 提供了大量的预定义变量。这些变量将所有的外部变量表示成内建环境变量,并且将错误信息表示成返回头。
参见 FAQ “register_globals 对我有什么影响?”
Table of Contents

超全局变量 — 超全局变量是在全部作用域中始终可用的内置变量
$GLOBALS — 引用全局作用域中可用的全部变量
$_SERVER — 服务器和执行环境信息
$_GET — HTTP GET 变量
$_POST — HTTP POST 变量
$_FILES — HTTP 文件上传变量
$_REQUEST — HTTP Request 变量
$_SESSION — Session 变量
$_ENV — 环境变量
$_COOKIE — HTTP Cookies
$php_errormsg — 前一个错误信息
$HTTP_RAW_POST_DATA — 原生POST数据
$http_response_header — HTTP 响应头
$argc — 传递给脚本的参数数目
$argv — 传递给脚本的参数数组

#可变变量

有时候使用可变变量名是很方便的。就是说,一个变量的变量名可以动态的设置和使用。

<?php 
          $$a="456";
          echo "{$$a}@$a@ ${$a}";//456@@ 456 
?>

要将可变变量用于数组,必须解决一个模棱两可的问题。这就是当写下 $$a[1] 时,解析器需要知道是想要 $a[1] 作为一个变量呢,

还是想要 $$a 作为一个变量并取出该变量中索引为 [1] 的值。解决此问题的语法是,对第一种情况用 ${$a[1]},对第二种情况用 ${$a}[1]。

#变量函数

Variable handling 函数array_key_exists

Table of Contents

  • isset — 检测变量是否设置
  • unset — 释放给定的变量
  • empty — 检查一个变量是否为空
  • var_dump — 打印变量的相关信息
  • var_export — 输出或返回一个变量的字符串表示
  • print_r — 打印关于变量的易于理解的信息。
  • serialize — 产生一个可存储的值的表示
  • settype — 设置变量的类型
  • strval — 获取变量的字符串值
  • unserialize — 从已存储的表示中创建 PHP 的值
  • is_null — 检测变量是否为 NULL
  • is_array — 检测变量是否是数组
  • is_bool — 检测变量是否是布尔型
  • is_callable — 检测参数是否为合法的可调用结构
  • is_double — is_float 的别名
  • is_float — 检测变量是否是浮点型
  • is_int — 检测变量是否是整数
  • is_integer — is_int 的别名
  • is_long — is_int 的别名
  • is_numeric — 检测变量是否为数字或数字字符串
  • is_object — 检测变量是否是一个对象
  • is_real — is_float 的别名
  • is_resource — 检测变量是否为资源类型
  • is_scalar — 检测变量是否是一个标量
  • is_string — 检测变量是否是字符串
  • array_key_exists(mixed key, array search) 检查给定的键名或索引是否存在于数组中
  • debug_zval_dump — Dumps a string representation of an internal zend value to output
  • doubleval — floatval 的别名
  • floatval — 获取变量的浮点值
  • get_defined_vars — 返回由所有已定义变量所组成的数组
  • get_resource_type — 返回资源(resource)类型
  • gettype — 获取变量的类型
  • import_request_variables — 将 GET/POST/Cookie 变量导入到全局作用域中
  • intval — 获取变量的整数值

*各种数据类型  函数输出比较

表P.1.用 PHP 函数对 $x 的比较

表达式 gettype() empty() is_null() isset() boolean :
if($x)

$x
= ""; string TRUE FALSE TRUE FALSE
$x
= NULL NULL TRUE TRUE FALSE FALSE
var $x; NULL TRUE TRUE FALSE FALSE
$x 尚未定义 NULL TRUE TRUE FALSE FALSE
$x
= array(); array TRUE FALSE TRUE FALSE
$x
= false; boolean TRUE FALSE TRUE FALSE
$x
= true; boolean FALSE FALSE TRUE TRUE
$x
= 1; integer FALSE FALSE TRUE TRUE
$x
= 42; integer FALSE FALSE TRUE TRUE
$x
= 0; integer TRUE FALSE TRUE FALSE
$x
= -1; integer FALSE FALSE TRUE TRUE
$x
= "1"; string FALSE FALSE TRUE TRUE
$x
= "0"; string TRUE FALSE TRUE FALSE
$x
= "-1"; string FALSE FALSE TRUE TRUE
$x
= "php"; string FALSE FALSE TRUE TRUE
$x
= "true"; string FALSE FALSE TRUE TRUE
$x
= "false"; string FALSE FALSE TRUE TRUE

#变量范围

变量范围

 

变量的范围即它定义的上下文背景(也就是它的生效范围)。大部分的 PHP 变量只有一个单独的范围。
这个单独的范围跨度同样包含了 include 和 require 引入的文件。例如:

<?php
  $a
= 1;
  include
'b.inc';
?>
这里变量 $a 将会在包含文件 b.inc 中生效:

<?php
$a
= 1; /* global scope */

function Test()
{
echo $a;
/* reference to local scope variable */
}

Test();
?>
这个脚本不会有任何输出,因为 echo 语句引用了一个局部版本的变量 $a,而且在这个范围内,它并没有被赋值。

 

PHP 中全局变量在函数中使用时必须申明为global。

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

function Sum()
{
    global $a, $b;
    $b = $a + $b;
}

Sum();
echo $b;
?>
以上脚本的输出将是“3”。在函数中申明了全局变量 $a 和 $b,任何变量的所有引用变量都会指向到全局变量。

 

 

在全局范围内访问变量的第二个办法,是用特殊的 PHP 自定义 $GLOBALS 数组。前面的例子可以写成:

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

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

Sum();
echo $b;
?>

 

$GLOBALS 是一个关联数组,每一个变量为一个元素,键名对应变量名,值对应变量的内容。

$GLOBALS 之所以在全局范围内存在,是因为 $GLOBALS 是一个超全局变量。以下范例显示了超全局变量的用处:

Example #3 演示超全局变量和作用域的例子

<?php
function test_global()
{
    // 大多数的预定义变量并不 "super",它们需要用 'global' 关键字来使它们在函数的本地区域中有效。
    global $HTTP_POST_VARS;

    echo $HTTP_POST_VARS['name'];

    // Superglobals 在任何范围内都有效,它们并不需要 'global' 声明。Superglobals 是在 PHP 4.1.0 引入的。
    echo $_POST['name'];
}
?>

使用静态变量static  variable

静态变量仅在局部函数域中存在,但当程序执行离开此作用域时,其值并不丢失。看看下面的例子:

Example #4 演示需要静态变量的例子

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

 $a 的值设为 0 并输出 "0"。

将变量加一的 $a++ 没有作用,因为一旦退出本函数则变量 $a 就不存在了。

要写一个不会丢失本次计数值的计数函数,要将变量 $a 定义为静态的:

Example #5 使用静态变量的例子

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

现在,变量$a在第一调用test()时被初始化,每次调用 test() 函数都会输出 $a 的值并加一。

静态变量也提供了一种处理递归函数的方法。递归函数是一种调用自己的函数。写递归函数时要小心,因为可能会无穷递归下去。必须确保有充分的方法来中止递归。一下这个简单的函数递归计数到 10,使用静态变量 $count 来判断何时停止:

 

Example #6 静态变量与递归函数

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

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

Note:

静态变量可以按照上面的例子声明。如果在声明中用表达式的结果对其赋值会导致解析错误。

 

Example #7 声明静态变量

<?php
function foo(){
    static $int = 0;          // correct
    static $int = 1+2;        // wrong  (as it is an expression)
    static $int = sqrt(121);  // wrong  (as it is an expression too)

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

全局和静态变量的引用

在 Zend 引擎 1 代,它驱动了 PHP4,对于变量的 static 和 global 定义是以 references 的方式实现的。例如,在一个函数域内部用 global 语句导入的一个真正的全局变量实际上是建立了一个到全局变量的引用。这有可能导致预料之外的行为,如以下例子所演示的:

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

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

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

执行以上例子会导致如下输出:


NULL
object(stdClass)(0) {
}

类似的行为也适用于 static 语句。引用并不是静态地存储的:

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

    echo 'Static object: ';
    var_dump($obj);
    if (!isset($obj)) {
        // 将一个引用赋值给静态变量
        $obj = &new stdclass;
    }
    $obj->property++;
    return $obj;
}

function &get_instance_noref() {
    static $obj;

    echo 'Static object: ';
    var_dump($obj);
    if (!isset($obj)) {
        // 将一个对象赋值给静态变量
        $obj = new stdclass;
    }
    $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)(1) {
["property"]=>
int(1)
}

上例演示了当把一个引用赋值给一个静态变量时,第二次调用 &get_instance_ref() 函数时其值并没有被记住。

#扩展:[部分摘自网络] 

 
一、定义(c描述): 

//----------------------------------------------- 
typedef struct _zval_struct zval; 
typedef union _zvalue_value { 
long lval;     
double dval;     
struct { 
  char *val; 
  int len; 
} str; 
HashTable *ht;     
zend_object_value obj; 
} zvalue_value; 
struct _zval_struct { 
zvalue_value value;             //php变量值,联合类型。可接受各种数据类型的值。   
zend_uchar type;               //php变量类型 
zend_uchar is_ref;             //是否为引用 
zend_uint refcount;            //引用个数 
}; 
//-----------------------------------------------(zend.h) 
结构体zval即为所描述的php变量。 

二、操作 

1。定义一个变量,并赋值。如$a=1; 
内部所执行的操作大概为: 
①定义一个zval变量a。②为变量a分配内存,并初始化。③为变量a赋值。④将变量a添加到符号表中 
//----------------------------------------------- 
zval *a;                                                                                             //① 
MAKE_STD_ZVAL(a);                                                                       //② 
ZVAL_LONG(a, 1);                                                                            //③ 
ZEND_SET_SYMBOL(EG(active_symbol_table), “local_variable”, a); //④ 
//----------------------------------------------- 
注: 
其中②的具体操作为: 
#define MAKE_STD_ZVAL(zv) \ 
ALLOC_ZVAL(zv); \                                                    //分配内存 
INIT_PZVAL(zv);                                                         //初始化 

#define ALLOC_ZVAL(z) \                                            //分配内存 
ZEND_FAST_ALLOC(z, zval, ZVAL_CACHE_LIST) 
#define ZEND_FAST_ALLOC(p, type, fc_type) \ 
(p) = (type *) emalloc(sizeof(type)) 
#define emalloc(size) _emalloc((size) ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC) 
ZEND_API void *_emalloc(size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) 
{ 
TSRMLS_FETCH(); 
if (UNEXPECTED(!AG(mm_heap)->use_zend_alloc)) { 
  return malloc(size);                                         //通过c函数操作内存 
} 
return _zend_mm_alloc_int(AG(mm_heap), size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); 
} 
#define INIT_PZVAL(z) \                                     //初始化   
(z)->refcount = 1; \                                            //引用个数为1 
(z)->is_ref = 0;                                                  //变量没有被引用 
其中④的具体操作为: 
先会检查一下要添加的变量是否已经存在在符号表中, 
若存在,则不存储,把对应的refcount加一。 
若不存在,则添加在符号表后面。 
可查看zend_API.h文件中ZEND_SET_SYMBOL_WITH_LENGTH的宏定义。 

2。把一个变量的值赋给另一个变量。如:$a=1;$b=$a; 
内部所执行的操作大概为: 
不申请新的内存空间,直接把变量b添加进符号表,然后指向变量a所指的内存空间。 
即把变量的refcount加一。#define ZVAL_ADDREF(pz) (++(pz)->refcount)。但各自的is_ref都为0。 

3。unset一个变量。如$a=1;$b=$a;unset($b); 
内部所执行的操作大概为: 
检查变量的refcount是否大于1,若大于1,说明还有别的变量也在关注此内存空间,则把refcount减一,即#define ZVAL_DELREF(pz) (--(pz)->refcount)。若等于1,则直接释放变量空间,即FREE_ZVAL(var)。 

4。非引用的两个变量共享同一内存空间,当其中一个被赋于新值时。如:$a=1;$b=$a;$b=2; 
内部所执行的操作大概为: 
①执行$a=1;后,变量a的is_ref为0,refcount为1。②执行$b=$a;后,is_ref变为0,refcount变为2。变量a、b指向了同一内存空间③执行$b=2;时,先判断is_ref,若为0,说明没有被引用,则调用分离变量宏,把refcount减一,为变量划分新的内存空间、初始化,并赋新值。若为1,说明有被引用,可直接赋值。 
#define SEPARATE_ZVAL_IF_NOT_REF(ppzv) \ 
if (!PZVAL_IS_REF(*ppzv)) { \ 
  SEPARATE_ZVAL(ppzv); \ 
} 
#define SEPARATE_ZVAL(ppzv)  \ 
{    \ 
  zval *orig_ptr = *(ppzv); \ 
      \ 
  if (orig_ptr->refcount>1) { \ 
   orig_ptr->refcount--; \ 
   ALLOC_ZVAL(*(ppzv)); \ 
   **(ppzv) = *orig_ptr; \ 
   zval_copy_ctor(*(ppzv));\ 
   (*(ppzv))->refcount=1; \ 
   (*(ppzv))->is_ref = 0; \ 
  }    \ 
} 

5。被引用的两个变量,其中一个被赋新值。如:$a=1;$b=&$a;$b=2; 
内部所执行的操作大概为: 
①执行$a=1;后,变量a的is_ref为0,refcount为1。②执行$b=&$a;后,is_ref变为1,refcount变为2。③执行$b=2;时,先判断is_ref,若为0,则调用分离变量宏。若为1,则直接赋值。 
#define REPLACE_ZVAL_VALUE(ppzv_dest, pzv_src, copy) { \ 
int is_ref, refcount;    \ 
       \ 
SEPARATE_ZVAL_IF_NOT_REF(ppzv_dest);  \  //判断是否被引用,并执行相应操作。 
is_ref = (*ppzv_dest)->is_ref;   \ 
refcount = (*ppzv_dest)->refcount;  \ 
zval_dtor(*ppzv_dest);    \ 
**ppzv_dest = *pzv_src;    \ 
if (copy) {                                  \ 
  zval_copy_ctor(*ppzv_dest);  \ 
    }                                       \ 
(*ppzv_dest)->is_ref = is_ref;   \ 
(*ppzv_dest)->refcount = refcount;  \ 
} 

6。分离①:如$a=1;$b=$a;$c=&$a; 
内部所执行的操作大概为: 
①执行$a=1;后,is_ref为0,refcount为1。②执行$b=$a;后,is_ref为0,refcount变为2。③执行$c=&$a;时,变量b被分离出来,重新划分内存空间。变量a、c指向原本变量a的空间。变量b的is_ref为0,refcount为1。变量a、c的is_ref为1,refcount为2。 

7。分离②:如$a=1;$b=&$a;$c=$a; 
内部所执行的操作大概为: 
①执行$a=1;后,is_ref为0,refcount为1。②执行$b=&$a;后,is_ref为1,refcount变为2。③执行$c=$a;时,变量c被分离出来,划分新空间,is_ref为0,refcount为1。变量a、b保持原来的引用不变,is_ref为1,refcount为2。 

8。对全局变量和局部变量的存储。在内部,有很多个哈希表,有些是用来管理数组用的,有些管理对象用的,还有两个是用来管理php变量的,一个是全局变量哈希表,一个是局部变量哈希表。 
需要向系统登记的变量,只要调用ZEND_SET_SYMBOL(EG(active_symbol_table), "local_variable", new_var1);(登记为局部变量)或ZEND_SET_SYMBOL(&EG(symbol_table), "global_variable", new_var2);(登记为全局变量)。哈希表结构可查看zend_hash.h文件。 

附: 

1。对系统核心的操作,可尽量考虑提高其操作的执行效率。 
如可用“多行宏定义”替代“无返回值小函数”来提高执行速度。因为宏定义,是在程序编译时,把宏体直接替换到调用其对应宏名的地方,省去了c语言中对函数调用的一系列操作(如:保存当前变量状态,函数相关信息进栈、出栈等操作)。 

2。编译型语言中,变量须先定义,后使用。在编译时就为全部变量划分好了内存空间。 
而解释型语言中,运行时要用到变量的时候,才去申请内存空间。这样可省去定义变量的麻烦,不过有些隐藏的语法错误要在运行时才可被发现。 

3。操作内存时,可尽量考虑减少内存的使用。如$a=1;$b=$a;。内部操作其实只是将变量a、b指向同一内存区域。这样做的好处,一方面可节约内存,另一方面,减少操作步骤,提高系统的执行速度。因为省去了申请内存空间,初始化,赋值的一些操作,而只是简单得把变量b添加到符号表,并指向a所指的内存区域。等到必要的,才对变量a、b的内存区域进行分离。如遇到$b=2;。 

4。有些疑惑的地方。 
①按照以上定义的php变量结构体zval,当我们定义一个long型变量,按理,该变量内部的“type”成员的值应该为long,而且可以储存的数的范围为:(4个字节)-2^31+1~2^31。当实际定义一个php变量并赋值时,发现,当超过long能表示的范围时,会自动向double转化。 
②double类型为8个字节,能存储的范围为:-2^63+1~2^63。当实际定义一个php变量并赋值时,发现,其可表示的数超过1.0E+260。这已经超过double能表示的范围。 
我猜想,内部应该有一个专门用来处理变量类型转化,扩大变量的数据存储能力的程序段。这好象就是泛类型,不太懂。不过我没有找到相关的程序段。 
结论:使用php变量,可不用太担心变量的数据溢出问题。 

#

posted on 2011-06-02 14:02  小怪物  阅读(6580)  评论(2)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3