一:引用变量

概念:
用不同的名字访问同一个变量内容

定义方式:&

工作原理:
PHP有一个机制,叫做cow (Copy On Write),写时拷贝
$b = $a;此时还是指向的一个内存空间,当我们对$a和$b其中一个变量进行修改操作的时候,才会发生copy操作,即给$b也复制一块内存空间 。

$b=&$a;就是公用一个内存空间。如果这个内存存储的数据变了,那么两个变量的值都会发生变化。

PHP内置内存函数:

memory_get_usage($real_usage) 返回当前分配给PHP脚本的内存量,单位是字节(byte)
$real_usage参数,值为布尔值。如果设置为 TRUE,获取系统分配的真实内存尺寸。如果未设置或者设置为 FALSE,将是 emalloc() 报告使用的内存量。

在实际WEB开发中,可以用 memory_get_usage()比较各个方法占用内存的高低
下面这个自定义函数将字节数转换成MB
代码如下:

  1. <?php
  2. function memory_usage() {
  3. $memory = ( ! function_exists('memory_get_usage')) ? '0' : round(memory_get_usage()/1024/1024, 2).'MB';
  4. return $memory;
  5. }


验证写时拷贝:
实例:1

<?php
// 定义一个变量
$a = range(0, 1000);
var_dump(memory_get_usage());
// 定义变量b,将a变量的值赋值给b
// COW Copy On Write
$b = $a;
var_dump(memory_get_usage());
// 对a进行修改
$a = range(0, 1000);
var_dump(memory_get_usage());

结论

/Users/zhaochun/www/imooc/demo1.php:5:int 399880
/Users/zhaochun/www/imooc/demo1.php:10:int 399880
/Users/zhaochun/www/imooc/demo1.php:14:int 436800

可以看出$b = $a;之后并没有马上给$b也申请一块内存空间,还是共用一个内存空间,所以值赋值前后内存使用量大致相同(会有一点偏差),当修改$a的值的时候,才会重新给$b开辟新的内存空间,所以此时内存使用量增大。

实例:2
将实例1的$b = $a;改为$b = &$a,打印结果为:

/Users/zhaochun/www/imooc/demo2.php:5:int 399880
/Users/zhaochun/www/imooc/demo2.php:9:int 399904
/Users/zhaochun/www/imooc/demo2.php:13:int 399904

结论:
可见引用的方式,从始至终都是共享一个内存空间。

释放内存

要想减少内存的占用,可以使用unset() 函数把不再需要使用的变量删除。虽然不会删除变量指向的内存空间,但是如果这个内存空间的引用计数为0的时候。php会执行回收机制,释放该变量(zval)所占的内存空间。否则会在php程序运行完之后才会启动回收机制。

对于不需要的大的变量,如果没有及时清理,这些变量实际上会一直存在内存中,直到请求结束(参考SAPI的生命周期)。在此之前,这些变量占据的内存不能被使用,便白白浪费了,换句话说,无法释放的内存导致了内存泄露。

如果这种内存泄露仅仅发生了一次或者少数几次,倒也还好,但如果是成千上万次的内存泄露,便是很大的问题了。尤其在长时间运行的脚本中(例如守护程序,一直在后台执行不会中断),由于无法回收内存,最终会导致系统“再无内存可用”。

循环中,同名变量下次赋值的时候, 原来的内存会写入新的值,所以内存也不会一直累积。

类似还有mysql_free_result() 函数,当我们不再需要查询数据得到的结果集时,可以使用释放查询占用的内存。
实例:

echo '开始内存:'.memory_get_usage(), ''; 
$tmp = str_repeat('hello', 1000); 
echo '运行后内存:'.memory_get_usage(), ''; 
unset($tmp); 
echo '回到正常内存:'.memory_get_usage();
打印结果:
开始内存:147296 
运行后内存:152456 
回到正常内存:147296

调试检测PHP代码性能的方法有:

<?php
memory_get_usage():分析内存占用空间。
microtime():分析程序执行时间。
memory_get_peak_usage():返回内存使用峰值,
getrusage():返回CUP使用情况。

memory_get_peak_usage() — 返回分配给你的 PHP 脚本的内存峰值字节数。
memory_get_usage() —返回当前分配给你的 PHP 脚本的内存量,单位是字节(byte)

在使用linux命令 ps 或 top 命令查看进程时, 能看到内存消耗的百分比和大小, 此处的大小是与 memory_get_peak_usage() 相一致的

memory_get_peak_usage()是系统分配的内存, 在一个请求中,unset掉的数据仍然会被统计。
memory_get_usage() 是当前脚本正在使用的内存,unset掉的数据不记录在内

举例说明:
1, 数据库读出来千万条数据,假如说需要消耗100MB,那么系统会分配给进程 100MB
2, 当处理完数据后 unset 掉, 其实当前进程的消耗的内存并不会变小, 即不会释放100MB空间
3, 内存被划分为, “已使用” 和 “空闲”, unset 只会把 “已使用” 变为 “空闲”, 下次内存请求时会先去”空闲”里取
4, 程序结束, GC 才会释放全部内存

unset后 memory_get_peak_usage 仍然会不继续升高, 程序执行被分配的内存(进程里的内存)仍然要以 memory_get_peak_usage 为准

zval容器

1 概念:
每个php变量存在一个叫”zval”的变量容器中
一个zval变量容器,除了包含变量的类型和值,还包括两个字节的额外信息。
第一个是”is_ref”,是个bool值,用来标识这个变量是否是属于引用集合(reference set)。通过这个字节,php引擎才能把普通变量和引用变量区分开来,
第二个额外字节是引用次数”refcount”,用以表示指向这个zval变量容器的变量(也称符号即symbol)个数。所有的符号存在一个符号表中,其中每个符号都有作用域(scope)。refcount“变成0时就被销毁. 当任何关联到某个变量容器的变量离开它的作用域(比如:函数执行结束),或者对变量调用了函数 unset()时,”refcount“就会减1
注意:PHP7的zval结构已经改变,简单数据类型已经不使用引用计数,他们的生存周期存在于整个请求期间,request 完成后会统一销毁释放,自然也就无需通过引用计数进行内存管理。
PHP7 中全新的 zval 容器和引用计数机制

2 验证写时拷贝

<?php
// zval变量容器
//生成一个新的zval容器
$a = range(0, 3);
// 显示zval信息(此函数需要安装xdebug扩展)
xdebug_debug_zval('a');//指向a的zval容器的引用次数"refcount"为1
// 把一个变量赋值给另一变量将增加引用次数(refcount)
$b = $a;
xdebug_debug_zval('a');//指向a的zval容器的引用次数"refcount"为2,此时b也指向这个zval容器
// 修改a
$a = range(0, 3);
xdebug_debug_zval('a');//指向a的zval容器的引用次数"refcount"为1,
// 给a重新赋值的时候,发生了写时拷贝,会给a重新开辟一块内存空间,b还是原先的内存空间

打印结果:
1 上 PHP基础知识 - 图1
改为引用之后 $b = &$a;,当修改$a的值,refcount仍然为2,is_ref为1,因为是引用方式
1 上 PHP基础知识 - 图2

3 复合类型
当考虑像 arrayobject这样的复合类型时,事情就稍微有点复杂. 与 标量(scalar)类型的值不同,arrayobject类型的变量把它们的成员或属性存在自己的符号表中。这意味着下面的例子将生成三个zval变量容器。

<?php
$a = array( 'meaning' => 'life', 'number' => 42 );
xdebug_debug_zval( 'a' );

以上例程的输出类似于:

a: (refcount=1, is_ref=0)=array (
   'meaning' => (refcount=1, is_ref=0)='life',
   'number' => (refcount=1, is_ref=0)=42
)

这三个zval变量容器是: a,meaning和 number。

取消引用

unset 一个引用,只是断开了变量名和变量内容之间的绑定。这并不意味着变量内容被销毁了,只会删除这个变量

对象的引用

默认情况下对象是通过引用传递的。(引用的是对象的标识符(一个ID),这个标识符指向对象本身)
详见:PHP中的对象复制及clone() 函数.note
但有时你可能想建立一个对象的副本,并希望原来的对象的改变不影响到副本 . 为了这样的目的,PHP定义了一个特殊的方法,称为
clone()

<?php
// 对象本身就是引用传递
class Person
{
    public $name = "zhangsan";
}
$p1 = new Person;
xdebug_debug_zval('p1');//指向p1的zval容器的引用次数"refcount"为1
$p2 = $p1;
xdebug_debug_zval('p1');//指向p1的zval容器的引用次数"refcount"为2,此时p2也指向这个zval容器
$p2->name = "lisi";
xdebug_debug_zval('p1');//指向p1的zval容器的引用次数"refcount"为2,普通方式赋值的时候,发生了写时拷贝,会给$p2重新开辟一块内存空间,此时指向p1的zval容器的引用次数"refcount"变为1,
//但是对象的调用以及赋值,默认是通过引用的方式,所以此时只是修改了原先的内存空间的值,p1和p2两个变量指向一个内存空间。

引用题

<?php
$data = ['a', 'b', 'c'];
foreach ($data as $key=>$val)
{
    $val = &$data[$key];
    var_dump($data);
}
var_dump($data);
  • 程序运行时,每一次循环结束后变量$data的值是什么?请解释
    程序执行完成后,变量$data的值是什么?请解释
    解题:
    *第一次循环:
    $key =  0;
    $val =  'a';
    $val = &$data[$key];  即$val=&$data[0];  
    此时$data = ['a', 'b', 'c'];
    
    第二次循环
    $key =  1;
    $val =  b;因为$val是引用的值,所以第二部循环$val是有值的,此时循环体为:
    foreach ($data as $key=>$val=&$data[0]),也就是foreach ($data as $key=>b=&$data[0]),
    b=&$data[0]引用的关系,所以会改变$data[0]的值为b,(非引用的关系如:$val = $data[$key];在foreach中也是$key=>$val=$data[0],$val刚开始也是等于上一次的值,只不过foreach体中会对$val重新赋值)
    接下来才是括号中的:$val = &$data[$key];b=&$data[1],这一步是给$data[1]引用赋值,值为b,
    此时$data = ['b', 'b', 'c'];
    
    第三次循环
    $key =  2;
    $val =  c;
    foreach ($data as $key=>$val=&$data[1]),也就是foreach ($data as $key=>c=&$data[1]),
    所以$data[1]的值为c
    此时$data = ['b', 'c', 'c'];
    
    循环结束
    PHP有函数作用域,但是没有块级作用域,条件语句和循环中的变量对外部可见,而且内外部可以相互改变。PHP没有C/C++、java等语言的块级作用域概念
    所以最后的结果就是最后一次循环的结果:$data = [‘b’, ‘c’, ‘c’];

    二:常量和数据类型

    变量名的命名规则

    1、由字母、下划线“_” 、数字 组成,不能以数字开头,不允许包含空格

    八大数据类型:

    四种标量类型:

    boolean (布尔型)

    一个是TRUE,另一个FALSE,可以理解为是或否。它不区分大小写,用”echo”输出“true”是“1”,“false”则什么也不输出。
    占用字节:通过查看PHP源目录Zend / zend_types.h找到bool的大小:
    typedef unsigned char zend_bool;
    PHP的bool类型底层是用c的char类型表示的,char只需要一个字节就能表示1或者0;(一个字节是8比特,8个0和1,有256个组合)
<?php
// 数组中的bool型,输出“true”是“1”,“false”则什么也不输出;
// 使用implode分割数组的时候,会将bool的true转为1,bool的false转换为空。使用for循环将数组拼接为字符串的时候,也是会进行转换
// 目的:使用逗号拼接字符串,true,false原样显示:
$ar = ['aaa',true,'bbb',false];

$a = '';
foreach ($ar as $key => $value) {
    if($value===true){
        $value = 'true';
    }else if($value===false){
        $value = 'false';
    }
    $a.=$value.',';
}
echo '<pre>';
    print_r($a);
echo '</pre>';
die;


integer (整型)

集合 ℤ = {…, -2, -1, 0, 1, 2, …} 中的一个数。有长度限制,32位平台和64位不同,超出了integer范围,会返回 float。
PHP 不支持无符号的int,PHP整型是有符号的,有负数。
PHP_INT_SIZE:表示整数integer值的字长(字节)
PHP_INT_MAX:表示整数integer值的最大值
注:
输出32位系统中PHP_INT_SIZE:4字节,PHP_INT_MAX:2147483647 (10位)
输出64位系统中PHP_INT_SIZE:8字节,PHP_INT_MAX:9223372036854775807 (19位)

$a = 1234567890123456789;//19位  
$b = 12345678901234567890; //20位,超出64位平台int范围,PHP会使用浮点型表示
echo '<pre>';
    var_dump($a); //int(1234567890123456789)
echo '</pre>';

echo '<pre>';
    var_dump($b); //float(1.2345678901235E+19)
echo '</pre>';
die;

为什么在不同的平台上int类型所占字节数有差异呢
不管什么类型,编译型语言都会把源码编译成机器码,由于不同平台寄存器位宽不一样, 所以有了自己对int的规定, 导致int 这个数据类型比较特殊,具体分配的字节数与机器字长和编译器有关。但一般等于机器寄存器位宽(64位平台除外,默认为4字节),如在32位平台上(所谓32位平台是指通用寄存器的数据宽度是32)编写代码,int 类型分配4个字节,而在16位平台是则分配2个字节。编译器是把代码转换为机器码的软件,如果他愿意,可以把int转换为256位的,只不过会增加机器的复杂度,降低可移植性。

float 浮点型

也就是通常说的小数,可以用小数点或者科学计数法表示。科学计数法可以使用小写的e,也可以使用大写的E。

$num_float1 = 1.234;    //1.234
$num_float2 = 1.2e3;    //1200
$num_float3 = 7.0E-3;   //0.007

float: 4个字节
double: 双精度 占用8个字节
PHP没有double的写法,应该是自动判断是float还是double,占用4或者8字节

PHP中浮点数的字长和平台相关,通常最大值是 1.8e308 并具有 14 位十进制数字的精度(64 位 IEEE 格式)。

浮点数的精度
浮点数的精度有限。尽管取决于系统,PHP 通常使用 IEEE 754 双精度格式,则由于取整而导致的最大相对误差为 1.11e-16。非基本数学运算可能会给出更大误差,并且要考虑到进行复合运算时的误差传递。
此外,以十进制能够精确表示的有理数如 0.1 或 0.7,无论有多少尾数都不能被内部所使用的二进制精确表示,因此不能在不丢失一点点精度的情况下转换为二进制的格式。这就会造成混乱的结果:例如,floor((0.1+0.7)*10) 通常会返回 7 而不是预期中的 8,因为该结果内部的表示其实是类似 7.9999999999999991118…。
所以永远不要相信浮点数结果精确到了最后一位,也永远不要比较两个浮点数是否相等。如果确实需要更高的精度,应该使用任意精度数学函数或者 gmp 函数

BCMath 数学函数
对于任意精度的数学, 如果有足够多的内存,PHP 提供的 BCMath 支持用字符串的形式表示任意大小和精度的数字,最大尺寸为 2147483647(即 0x7FFFFFFF)。

比较浮点数
要测试浮点数是否相等,要使用一个仅比该数值大一丁点的最小误差值。该值也被称为机器极小值(epsilon)或最小单元取整数,是计算中所能接受的最小的差别值。
$a 和 $b 在小数点后五位精度内都是相等的。

<?php
$a = 1.23456789;
$b = 1.23456780;
$epsilon = 0.00001;

if(abs($a-$b) < $epsilon) {
    echo "true";
}

NaN
某些数学运算会产生一个由常量 NAN 所代表的结果。此结果代表着一个在浮点数运算中未定义或不可表述的值。任何拿此值与其它任何值(除了 true)进行的松散或严格比较的结果都是 false
由于 NAN 代表着任何不同值,不应拿 NAN 去和其它值进行比较,包括其自身,应该用 is_nan() 来检查。

为什么浮点型不精确
因为十进制小数转换为二进制的过程中,使用的方法是乘 2 取整,小数继续乘2取整,直到积中的小数部分为零,最后顺序排列;有的数会出现无限循环的问题,所以浮点数是不精确的。
二进制 字符编码

string (字符串)

数字字母占用一个字节,汉字大部分占用三个字节

两种复合类型:

array (数组)

PHP 中的数组实际上是一个有序映射。映射是一种把 values 关联到 keys 的类型
索引数组赋值:

$arr[0]='苹果';
$array = array('苹果');
$array = array("foo", "bar", "hallo", "world");

关联数组赋值:

$arr['apple']='苹果';
$array = array('one'=>'苹果','two'=>'red');
$array = ['one'=>'apple','two'=>'red'];

object (对象)

要创建一个新的对象 object,使用 new 语句实例化一个类

两种特殊类型:

resource (资源)

一种特殊变量,保存了到外部资源的一个引用。资源是通过专门的函数来建立和使用的,资源类型变量为:打开文件、数据库连接、图形画布区域等的特殊句柄,因此将其它类型的值转换为资源没有意义。

NULL

表示一个变量没有值。NULL 唯一可能的值就是 不区分大小写的null

PHP数据类型底层

在c语言中,32位int都是占用4个字节,long占用4个字节;64位系统下,int占用4个字节,long占用8个字节
而PHP中只有int类型,int类型也要代替long的范围,所以在64位系统下,要占用8字节。
PHP语法简单,并没有规定很多细粒度的数据类型,比如用一个int就代替了short int,int,long 等;所以就会浪费空间,用short就能做的事,实际需要使用long来代替。

1、16位编译器

char :1个字节

char*(即指针变量): 2个字节

short int : 2个字节

int: 2个字节

unsigned int : 2个字节

float: 4个字节

double: 8个字节

long: 4个字节

long long: 8个字节

unsigned long: 4个字节

2、32位编译器

char :1个字节

char*(即指针变量): 4个字节(32位的寻址空间是2^32, 即32个bit,也就是4个字节。同理64位编译器)

short int : 2个字节

int: 4个字节

unsigned int : 4个字节

float: 4个字节

double: 8个字节

long: 4个字节

long long: 8个字节

unsigned long: 4个字节

3、64位编译器

char :1个字节

char*(即指针变量): 8个字节

short int : 2个字节

int: 4个字节

unsigned int : 4个字节

float: 4个字节

double: 8个字节

long: 8个字节

long long: 8个字节

unsigned long: 8个字节

伪类型:

混合型(mixed) 说明一个参数可以接受多种不同的类型。<br />    数字型(number) 说明一个参数可以是 [int](http://php.net/manual/zh/language.types.integer.php) 或者 [float](http://php.net/manual/zh/language.types.float.php)。<br />    回调(callback) [callback](http://php.net/manual/zh/language.pseudo-types.php#language.types.callback)回调类型。<br />    void(),无效的, 返回值无意义、作为入参表示不接受任何参数

注意:

整型表示方法:

它可以用十进制、八进制、十六进制指定。十进制就是日常使用的数字;八进制,数字前必须加上“0”;十六进制,数字前必须加“0x”

$data_int = 1234;    // 十进制数
$data_int = -567;    // 一个负数
$data_int = 0123;    // 八进制数(等于十进制的 83)
$data_int = 0x1a;    // 十六进制数(等于十进制的 26)

浮点类型不能用到比较运算当中:

$a = 0.1;
$b = 0.7;
$c = $a + $b;
if ($c == 0.8) {
    echo 1;
}else{
    echo 2;
}
echo $c;//0.8
此时输出2,说明$c不等于0.8,虽然直接输出$c的值为0.8,是PHP程序进行四舍五入了,
0.7+0.8进行运算的时候,交给cpu进行运算,cpu在运算的时候会转成二进制,就会有一定的损耗,最后的值是0.79999....
解决办法:将浮点型都扩大倍数,在进行运算,最后将结果缩小倍数。如$c =  (($a*100)+($b*100))/100

布尔等于false的七种情况:

整型0,包括000
浮点0.0,包括0.000
0字符串  '0'  ,注意:字符串 "00"  "0.0"为true,
空字符串,单引号双引号,heredoc和newdoc
布尔false,
NULL
空数组array(),

为NULL值的情况:

被赋值为 NULL
尚未被赋值(未定义的变量)
被 unset()。unset($var)将会删除$var变量,但变量指向的内存空间的值不会删除)
使用 $b = (unset)$var ,会将$var转换为null,但不会删除$var变量和$var的值,仅是返回 NULL 值而已。没有实际意义,直接给$b=NULL即可。

字符串的定义方式以及区别:

转义字符:

转义字符 意义 ASCII码值(十进制)
\a 响铃(BEL) 007
\b 退格(BS) ,将当前位置移到前一列 008
\f 换页(FF),将当前位置移到下页开头 012
\n 换行(LF) ,将当前位置移到下一行开头 010
\r 回车(CR) ,将当前位置移到本行开头 013
\t 水平制表(HT) (跳到下一个TAB位置) 009
\v 垂直制表(VT) 011
\\ 代表一个反斜线字符’’\‘ 092
\‘ 代表一个单引号(撇号)字符 039
\“ 代表一个双引号字符 034
\? 代表一个问号 063
\0 空字符(NULL) 000
\ooo 1到3位八进制数所代表的任意字符 三位八进制
\xhh 1到2位十六进制所代表的任意字符 二位十六进制

不能解析 就会原样输出;
能解析就是会 将其代表的实际意思输出;
\转义符号,可以转义某些场景下具有特殊含义的字符,使其失去特殊含义,如 ‘ \’ ‘ 会输出’ ,将单引号转义
或者和其他字符组合成为特定含义的符号:如\n表示换行

单引号:
单引号不能解析变量和转义字符,但是可以转义\和单引号本身,单引号中出现\和’会报错,因为转义了,需要前面加个\再次进行转义,使其失去特殊含义
‘$a’;会输出$a;’{$a}’会输出{$a}
‘\n’输出\n ‘ \’ ‘ 输出’ ‘\‘ 输出\ ‘ \” ‘输出 \” (单引号中的双引号没有特殊含义,所以\”没有特殊含义,原样输出)

双引号:
双引号可以解析变量

$a=1;
"$a"会输出对应的值1;"{$a}"也会输出1    
"abc$ab" 会输出abc,因为程序找不到$ab变量,为了清楚的判断变量名是$a还是$ab,变量可以使用特殊字符和{}包含
"abc$a-b" 会输出:abc1-b,因为变量由数字字母下划线组成,不能由数字开头,所以-之前的是变量名,如果这里是$a_b就会输出abc,提示找不到变量$a_b

双引号可以解析所有转义字符
“\” “ “ “,双引号中出现一个\或者”,也不会原样输出这两个字符,会进行解析,所以会报错,可以使用转义符号\将\或者”进行转义,使其没有特殊含义,作为普通字符输出
“ \n “会被解析成换行 “ \” “输出” ‘\“输出\ “\’”输出\’(双引号中的单引号没有特殊含义,所以\”没有特殊含义,原样输出)

heredoc和newdoc(用来处理大文本)
heredoc类似于双引号
$str = <<<EoT
可以解析变量,变量不需要用连接符.或,来拼接,可以直接写html代码,里面带变量。
EoT;

newdoc类似于单引号
$str = <<<’EoT’
不可以解析变量
EoT;

9大超全局变量

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

<?php
echo '<pre>';
    print_r($GLOBALS);
echo '</pre>';

Array
(
    [_GET] => Array()  
    [_POST] => Array()
    [_COOKIE] => Array
        (
            [remember_82e5d2c56bdd0811318f0cf078b78bfc] => eyJpdiI6ImtmVTJxUWpZdGpXakxtYzd0XC9TRU5BPT0iLCJ2YWx1ZSI6InRiQnMzb1wvcE44U0pUTUhHbnkyaXNNMVk2NG9JN0Nhb0dpTlhHY1ZWd1lmWFFwUzFkcEdTeDE5NVBBRGpZR1lncHlyNVZXcHVEKzJBekRzK25uQU8xRVo3ZHdRaWhaMFEyclU1UGdzQ0xJYz0iLCJtYWMiOiJkMmE1Y2NhNTM2NDAzYTU0MjMzNTU4ZWE1M2FhZjc2N2IwODVkMzRmYjk1YmFmOWMzNjA1MmJkNWEzNTBiNWU2In0=
        )
    [_FILES] => Array()
    [_ENV] => Array()
    [_REQUEST] => Array()
    [_SERVER] => Array
        (
            [HTTP_HOST] => localhost
            [HTTP_CONNECTION] => keep-alive
            [HTTP_CACHE_CONTROL] => max-age=0
            [HTTP_UPGRADE_INSECURE_REQUESTS] => 1
            [HTTP_USER_AGENT] => Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
            [HTTP_SEC_FETCH_USER] => ?1
            [HTTP_ACCEPT] => text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
            [HTTP_SEC_FETCH_SITE] => same-origin
            [HTTP_SEC_FETCH_MODE] => navigate
            [HTTP_REFERER] => http://localhost/demo/
            [HTTP_ACCEPT_ENCODING] => gzip, deflate, br
            [HTTP_ACCEPT_LANGUAGE] => zh-CN,zh;q=0.9
            [HTTP_COOKIE] => remember_82e5d2c56bdd0811318f0cf078b78bfc=eyJpdiI6ImtmVTJxUWpZdGpXakxtYzd0XC9TRU5BPT0iLCJ2YWx1ZSI6InRiQnMzb1wvcE44U0pUTUhHbnkyaXNNMVk2NG9JN0Nhb0dpTlhHY1ZWd1lmWFFwUzFkcEdTeDE5NVBBRGpZR1lncHlyNVZXcHVEKzJBekRzK25uQU8xRVo3ZHdRaWhaMFEyclU1UGdzQ0xJYz0iLCJtYWMiOiJkMmE1Y2NhNTM2NDAzYTU0MjMzNTU4ZWE1M2FhZjc2N2IwODVkMzRmYjk1YmFmOWMzNjA1MmJkNWEzNTBiNWU2In0%3D
            [PATH] => /usr/bin:/bin:/usr/sbin:/sbin
            [SERVER_SIGNATURE] => 
            [SERVER_SOFTWARE] => Apache/2.4.41 (Unix) PHP/7.2.25
            [SERVER_NAME] => localhost
            [SERVER_ADDR] => ::1
            [SERVER_PORT] => 80
            [REMOTE_ADDR] => ::1
            [DOCUMENT_ROOT] => /Users/zhaochun/www
            [REQUEST_SCHEME] => http
            [CONTEXT_PREFIX] => 
            [CONTEXT_DOCUMENT_ROOT] => /Users/zhaochun/www
            [SERVER_ADMIN] => webmaster@dummy-host2.example.com
            [SCRIPT_FILENAME] => /Users/zhaochun/www/demo/a.php
            [REMOTE_PORT] => 64211
            [GATEWAY_INTERFACE] => CGI/1.1
            [SERVER_PROTOCOL] => HTTP/1.1
            [REQUEST_METHOD] => GET
            [QUERY_STRING] => 
            [REQUEST_URI] => /demo/a.php
            [SCRIPT_NAME] => /demo/a.php
            [PHP_SELF] => /demo/a.php
            [REQUEST_TIME_FLOAT] => 1581412374.152
            [REQUEST_TIME] => 1581412374
        )
    [GLOBALS] => Array
 *RECURSION*
)


$_SERVER:服务器和执行环境信息。包含了诸如头信息(header)、路径(path)、以及脚本位置(script locations)等等信息的数组。这个数组中的项目由 Web 服务器创建。

http://localhost/imooc/ceshi.php/user/login?id=123
[SERVER_ADDR] => 192.168.0.109,服务器ip地址
[REMOTE_ADDR] => 192.168.0.103,客户端的ip地址
[SERVER_NAME] => localhost,域名
[REQUEST_URI] => /imooc/ceshi.php/user/login?id=123,完整路径
        [SCRIPT_NAME] => /imooc/ceshi.php,脚本路径
[PATH_INFO] => /user/login,路由 
[QUERY_STRING] => id=123,url参数
[HTTP_REFERER] => http://localhost/imooc/demo.html,上级跳转过来的URL


$_GET:通过 URL 参数传递给当前脚本的变量的数组。(form表单get方式也是通过URL传递参数的)
$_POST:用POST方法传递的参数的有关信息。当 HTTP POST 请求的 Content-Type 是 application/x-www-form-urlencodedmultipart/form-data 时,会将变量以关联数组形式传入当前脚本。
$_FILES:包含所有上传的文件信息。
$_COOKIE:通过HTTP cookie传递到脚本的信息
$_SESSION:所有会话有关的信息
$_REQUEST:默认情况下包含了 $_GET$_POST$_COOKIE 的数组。不建议使用,比较慢
$_ENV:是一个包含服务器端环境变量的数组,为空的话可以在php.ini中开启,生产服务器建议关闭。

常量

常量一经定义,不能被修改,不能被删除。
通常常量都全用大写
常量类似变量,不同之处在于:
在设定以后,常量的值无法更改
常量名不需要开头的美元符号 ($)
PHP7数组常量可以使用 define() 函数定义。 在PHP5.6,它们只能使用 const 关键字定义。

define('language', ['php', 'java', 'jsp', 'asp']);
const language = ['php', 'java', 'jsp', 'asp'];
echo language[1];//java


define()函数申明常量:
define(name,value,case_insensitive)
第三个参数如果设置为true,对大小写敏感

const也可以声明常量:
const FOO= “abcdef”;
define(‘FOO’, ‘BAR’);
echo FOO;

const和define()区别:类中使用const,条件语句使用define()
const本身就是一个语言结构。而define是一个函数。所以使用const速度要快的多。

const是在编译时定义,因此必须处于最顶端的作用区域,可用于全局变量,可用于类成员的定义。不能在函数,循环及if条件中使用;
define是函数,也就是能调用函数的地方都可以使用

<?php
class ClassName1 
{
    const A= "a"; //常量可定义类成员
    function __construct()
    {
        define('FOO', 'b');//可在函数中使用
        // const  FOO= "abcdef";//报错,不可在函数中使用
        echo FOO;//b
        echo self::A; //a
    }
}

echo ClassName1::A; //a,类似静态变量,可以直接访问

new ClassName1();
 <br />      <br />**获取常量的值:**
constant(string $name),当常量名储存在一个变量里,或者由函数返回常量名。当常量为字符串类型时候可以使用constant获取常量的值,
echo constant('PAI');  效果等于:echo PAI;


判定常量是否被定义
defined(string constants_name)

系统常量

PHP_VERSION:当前解析器的版本号。它可以告诉我们当前PHP解析器的版本号
PHP_OS:执行当前PHP版本的操作系统名称
__FILE__ :php程序完整文件名。当前文件在服务器的物理位置。
__DIR__:PHP程序位置
__LINE__ :PHP程序文件行数。它可以告诉我们,当前代码在第几行。
__CLASS__:    类名
__FUNCTION__:只是返回方法的名字:doitAgain
__METHOD__:返回类的名字和方法的名字:Test::doitAgain
__NAMESPACE__:命名空间名称:
__TRAIT__:Trait 是为类似 PHP 的单继承语言而准备的一种代码复用机制,通过trait申明的代码,可以直接使用use在类中使用。但是trait中的代码有外部依赖的话,需要通过引入或者传递参数的形式写入trait中。
<?php
trait Dog{
    public $name="dog";
    public function drive(){
        echo "This is dog drive";
    }
    public function eat(){
        echo "This is dog eat";
    }
}

class Animal{
    public function drive(){
        echo "This is animal drive";
    }
    public function eat(){
        echo "This is animal eat";
    }
}

class Cat extends Animal{
    use Dog; //使用trait类,必须在函数外引入
    // 可以重写trait的方法
    public function drive(){
        echo "This is cat drive";
    }
}
$cat = new Cat();
$cat->drive(); // This is cat drive
echo "<br/>";
$cat->eat(); // This is dog eat

同名方法优先级:本类,trait类中,继承的基类(extends继承的类就是基类);
优先顺序是来自当前类的成员覆盖了 trait 的方法,而 trait 则覆盖了被继承的方法

一个类可以组合多个Trait,通过逗号隔开,如下
use trait1,trait2
当不同的trait中,有着同名的方法或属性,会产生冲突,可以使用insteadof或 as进行解决,insteadof 是进行替代,而as是给它取别名,as 还可以修改方法的访问控制

class dog{
    use trait1,trait2{
        trait1::eat insteadof trait2;
        trait2::drive as driven;
        //trait2::drive as private driven;
    }
}


三:运算符

算术运算符:
+ - / %
% 模数 $x % $y $x 除 $y 的余数

赋值运算符
1,“=”:把右边表达式的值赋给左边的运算数。它将右边表达式值复制一份,交给左边的运算数。换而言之,首先给左边的运算数申请了一块内存,然后把复制的值放到这个内存中(写时拷贝:其中一个值发生改变的时候,才会重新申请内存空间)。
2,“&”:引用赋值,意味着两个变量都指向同一个数据。它将使两个变量共享一块内存,如果这个内存存储的数据变了,那么两个变量的值都会发生变化。
3,+= -=
= /= %=

$a = 5;
$a+1;
echo $a;//5
echo '<hr>';
$a+=1;//相当于 $a=$a+1;
echo $a;//6


位运算
位运算

比较运算符
== 等于
=== 全等
!= 不等
<> 不等
!== 非全等
<
>
<=
>=
$a??$b??$c NULL合并运算符:从左往右第一个存在且不为 NULL,将返回它的第一个操作数;否则返回第二个操作数,以此类推。代替三元运算并与 isset()函数功能结合一起使用,php7提供

三元运算符

<?php
对于表达式(expr1)?(expr2):(expr3),如果expr1的值为true,则此表达式的值为expr2,否则为expr3。
$a >= 60 ? "及格": "不及格";
// 算考生在第几排第几个位置,
$maxLine = 4; //每排人数
$no = 18;//学生编号
$line = ceil($no/$maxLine);//计算在第几排。除不尽就进一,下一排
//计算第几排第几个位置。$no%$maxLine余数为0说明能除进,那么位置就是每排人数。除不尽就取余数,余数是几就在第几个座位    
$row = $no%$maxLine ? $no%$maxLine : $maxLine;
echo "编号<b>".$no."</b>的座位在第<b>".$line."</b>排第<b>".$row."</b>个位置";


执行运算符
反引号``表示,PHP 将尝试将反引号中的内容作为 shell 命令来执行

$output = `ls -al`;
echo '<pre>';
    print_r($output);
echo '</pre>';

会在浏览器中输出和终端中运行ls -al相同的效果

逻辑运算符
and 与 $x and $y 如果 $x 和 $y 都为 true,则返回 true。
or 或 $x or $y 如果 $x 和 $y 至少有一个为 true,则返回 true。前一个成立则不执行后一个
xor 异或 $x xor $y 如果 $x 和 $y 有且仅有一个为 true,则返回 true。
&& 与 $x && $y 如果 $x 和 $y 都为 true,则返回 true。
|| 或 $x || $y 如果 $x 和 $y 至少有一个为 true,则返回 true。前一个成立则不执行后一个
! 非 !$x 如果 $x 不为 true,则返回 true。

字符串运算符
. 连接运算符 :它返回其左右参数连接后的字符串
$txt1 = “Hello” $txt2 = $txt1 . “ world!” 现在 $txt2 包含 “Hello world!”
.= 连接赋值运算符 :它将右边参数附加到左边的参数之后
$txt1 = “Hello” $txt1 .= “ world!” 现在 $txt1 包含 “Hello world!”

递增/递减运算符
运算符 名称 描述
++$x 前递增 $x 加一递增,然后返回 $x
$x++ 后递增 返回 $x,然后 $x 加一递增
—$x 前递减 $x 减一递减,然后返回 $x
$x— 后递减 返回 $x,然后 $x 减一递减

++ — 不影响布尔值
递减NULL没效果,仍为NULL
递增NULL值为1

数组运算符
运算符 名称 例子 结果
+ 联合 $x + $y $x 和 $y 的联合(但不覆盖重复的键)
== 相等 $x == $y 如果 $x 和 $y 拥有相同的键/值对,则返回 true。
=== 全等 $x === $y 如果 $x 和 $y 拥有相同的键/值对,且顺序相同类型相同,则返回 true。
!= 不相等 $x != $y 如果 $x 不等于 $y,则返回 true。
<> 不相等 $x <> $y 如果 $x 不等于 $y,则返回 true。
!== 不全等 $x !== $y 如果 $x 与 $y 完全不同,则返回 true。

类型运算符
instanceof 用于确定一个 PHP 变量是否属于某一类 class 的实例:也可用来确定一个变量是不是继承自某一父类的子类的实例:
返回bool型

<?php
class MyClass{}
$a = new MyClass;
var_dump($a instanceof MyClass);//bool(true)


错误控制运算符 “@”
将@放置在一个PHP表达式之前,该表达式可能产生的任何错误信息都被忽略掉;
如果激活了track_error(在php.ini中设置)特性,表达式所产生的任何错误信息都被存放在变量$php_errormsg中,此变量在每次出错时都会被覆盖,所以如果想用它的话必须尽早检查。
$conn = @mysql_connect(“localhost”,”username”,”password”);
echo “出错了,错误原因是:”.$php_errormsg;
需要注意的是:错误控制前缀“@”不会屏蔽解析错误的信息,不能把它放在函数或类的定义之前,也不能用于条件结构例如if和foreach等。

运算符优先级

经常用到优先级顺序:算数运算 比较运算 符号逻辑运算&& || 赋值运算 字母逻辑运算and or :

优先级从上到下:(开发当中应该使用括号标明运算顺序增加代码可读性)
递增++,递减—,右边的优先级高
! 逻辑运算符 取反
/ % + - . 算数运算符 乘除取余加减
< <= > >= == != === !== <> <=> 比较运算符
& 位运算和引用
&& || 符合逻辑运算符,&&比 || 优先级高,
_= += -=
= /= 赋值运算符,右边优先级高:$a = $b = $c 等同$a = ($b = $c)
_and xor or 字母逻辑运算符

<?php
/**
>优先级高于||高于=
先执行比较运算符:前一个3>0成立,为true,然后执行逻辑或运算1 || ,逻辑或成立,立即返回true,且不再执行逻辑或后面的。
以此时将true赋值给$a,$b为默认设置的0
递增不影响布尔值,true++仍为true。
int 0++为1.
 */
$a = 0;
$b = 0;
if ($a = 3 > 0 || $b = 3>0) 
{
    var_dump($a);//boolean true
    var_dump($b);//int 0
  $a++;
  $b++;
  var_dump($a);//boolean true
    var_dump($b);//int 1
}
// 把上面的if改为:
// if ($a = 3 < 0 || $b = 3>0) ,$a $b 前后四个值都为 :boolean true
//先执行比较运算符: 前一个3<0不成立,然后执行后一个3>0,成立, 
//然后执行逻辑运算符:$a =  false  ||  $b  =  true,逻辑或有一个为true,返回true
//最后执行赋值运算符:逻辑或前一个不成立,会执行后面的。赋值运算符右边优先级高,先给$b赋值,3>0成立返回true ,所以$b = true。然后 将false||true的结果返回给$a,为true
// 所以$a=true;$b=true;递增不影响布尔型,所以结果都是true;


短路效果:如果 || 前面的成立,立即返回true,|| 后面的优先级高也不会执行,实例:

if( true || $a==w){
    echo 1;
}else{
    echo 2;
}

此时会输出1,说明$a == w没有执行。如果执行的话会提示错误:使用未定义的常量w
改为:if( false || $a==w),给出错误:使用未定义常量,然后输出2

四 流程控制

数组指针操作相关函数:

reset() 函数将内部指针指向数组中的第一个元素,并输出。
list () 把数组中的值赋给一组变量,这不是真正的函数,而是语言结构。仅能用于数字索引的数组,并假定数字索引从 0 开始。

<?php
$my_array = array("Dog","Cat","Horse");
list($a, $b, $c) = $my_array;

current() - 返回数组中的当前元素的值
end() - 将内部指针指向数组中的最后一个元素,并输出
next() - 将内部指针指向数组中的下一个元素,并输出
prev() - 将内部指针指向数组中的上一个元素,并输出
each() - 返回当前元素的键名和键值,并将内部指针向前移动。键/值对被返回带有4个元素的关联和索引混合的数组,键名分别为0、1、key和value。其中键名0和key对应的值是一样的,是数组元素的键名,1和value则包含有数组元素的值。正因为each返回的当前元素的键名中有数字索引,且是0,所以可以将键值赋值给list,结合while循环,可以遍历数组

遍历数组的方式

for循环:只能遍历索引数组,且数组下标需要连续。(for循环遍历数组需要计算数组的长度,来决定循环的次数)

<?php
$arr = array('http://www.jb51.net','脚本之家','PHP教程'); 
$num = count($arr); //先计算出数组$arr中元素的个数
for($i=0;$i<$num;++$i){ 
    echo $arr[$i].'<br />'; 
}

foreach遍历数组:可遍历索引和关联数组,foreach 仅能够应用于数组和对象;
无需计算数组长度;foreach首先会复制一份数组进行遍历,对数组进行改写的时候才会发生拷贝,foreach对原数组的改写并不会影响原数组;可以通过在 $value 之前加上 & 来修改数组的元素。此方法将以引用赋值而不是拷贝一个值:

<?php
$arr = array(1, 2, 3, 4);
foreach ($arr as &$value) {
    $value = $value * 2;
}
// 现在 $arr 是 array(2, 4, 6, 8)
// 数组最后一个元素的 $value 引用在 foreach 循环之后仍会保留。建议使用 unset() 来将其销毁
unset($value); // 最后取消掉引用

// 未使用 unset($value) 时,$value 仍然引用到最后一项,也就是上面的$arr[3]的值;$arr[3]的值会被之后的$value变量更新掉;导致后续的$value变量的值改变了原arr数组。
foreach ($arr as $key => $value) {
    // $arr[3] 会被每一个$value更新掉
    echo "{$key} => {$value} ";
    print_r($arr);
}
// 直到最终倒数第二个值被复制到最后一个值

// output:
// 0 => 2 Array ( [0] => 2, [1] => 4, [2] => 6, [3] => 2 ) // 第1次遍历的value是2,2替换到arr[3]
// 1 => 4 Array ( [0] => 2, [1] => 4, [2] => 6, [3] => 4 ) // 第2次遍历的value是4,4替换到arr[3]
// 2 => 6 Array ( [0] => 2, [1] => 4, [2] => 6, [3] => 6 ) // 第3次遍历的value是6,6替换到arr[3]
// 第四次遍历的是arr[3],此时arr[3]的值已经是6了
// 3 => 6 Array ( [0] => 2, [1] => 4, [2] => 6, [3] => 6 ) // 第4次遍历的value是6,6替换到arr[3]

foreach会对数组进行重置数组指针操作,即:reset()操作,如果一个数组之前进行了数组指针的操作,如next()将数组指针进行了移动,使用foreach遍历该数组会重置数组,即从数组的第一个元素遍历

<?php
$arr = array('http://www.jb51.net','脚本之家','PHP教程'); 
foreach($arr as $value){ 
    echo $value.'<br />'; 
}
foreach($arr as $k=>$v){ 
    echo $k."=>".$v."<br />"; 
}

foreach 这种迭代的方法,是如何移动指针,如何防止迭代越界?
foreach 的时候, 是先将指针初始化到0的位置, 然后进行 valid 越界判定, 如果没有越界,那么打印当前的 值 和 key,这算是完成了第一次迭代, 然后调用 next 的方法,挪动指针到下一个位置, 判断越界,然后打印键值对, 一直到数据打印完, 依然让指针 +1,这时候发现 指针指向了一个空节点,然后 valid 方法判断返回 false,才结束了打印

while循环,只能遍历索引数组;和for循环类似,需要计算数组长度。while可以方便的设置多条件过滤,比for灵活

<?php
$arr = array('http://www.jb51.net','脚本之家','PHP教程'); 
$num = count($arr); //先计算出数组$arr中元素的个数
$i=0;
while ( $i<$num ) {
    echo $arr[$i].'<br />'; 
    $i++;
}


do{} while 循环,先执行一次,然后进行while条件判断,为true再进行循环。也只能遍历索引数组

<?php
$arr = array('http://www.jb51.net','脚本之家','PHP教程'); 
$num = count($arr); //先计算出数组$arr中元素的个数
$i=0;
do{
    echo $arr[$i].'<br />'; 
    $i++;
}while($i<$num);

while list() each() 组合循环:可遍历索引和关联数组,该组合循环不会重置数组指针,如果之前对改数组进行了指针操作,使用组合循环的时候,接着上次的数组指针继续循环。
PHP7.2已经废弃,不建议使用:因为each()函数在几乎所有可以想象到的方面都不如foreach,包括超过10倍慢

<?php
$arr = array('http://www.jb51.net','脚本之家','PHP教程'); 
while(list($k,$v) = each($arr)){ 
    echo $k.'=>'.$v.'<br />'; 
}


continue; // 跳出本次循环,可选数字参数来决定跳过几重循环到循环结尾。默认值是 1,即跳到当前循环末尾。如if里面包含if,在里层if里写continue 2,就会直接跳出外层循环中的下一轮循环
break; //终止循环,如果要跳出多重循环的话,可以用n来表示层数,如果不带参数默认是终止本次循环。

各种循环应用场景:
for循环一般用于计数循环
while循环一般用于多条件循环,也可以用于计数循环;比for灵活
foreach用于遍历关联和索引数组

遍历对象

PHP 提供了一种定义对象的方法使其可以通过单元列表来遍历,例如用 foreach 语句。默认情况下,所有可见属性都将被遍历。

<?php
class MyClass
{
    public $var1 = 'value 1';
    public $var2 = 'value 2';
    public $var3 = 'value 3';

    protected $protected = 'protected var';
    private   $private   = 'private var';

    function iterateVisible() {
       echo "MyClass::iterateVisible:\n";
       foreach ($this as $key => $value) {
           print "$key => $value\n";
       }
    }
}

$class = new MyClass();

foreach($class as $key => $value) {
    print "$key => $value\n";
}
echo "\n";


$class->iterateVisible();


//输出
// 首选会将public的属性遍历出来
var1 => value 1
var2 => value 2
var3 => value 3
//然后遍历可见方法
MyClass::iterateVisible: 
// iterateVisible方法内进行遍历,私有遍历和受保护的变量对本类都是可见的,所以都可以遍历出来。
var1 => value 1
var2 => value 2
var3 => value 3
protected => protected var
private => private var

分支

if elseif else
在if elseif中只能有一个语句块被执行
使用if基本原则:把可能性大的条件放在前面处理,减少流程判断,因为if会从前往后依次进行判断

switch case

<?php
switch (variable) {//参数只能是整形,浮点型,字符串。
    case 'value':
        # code...
        break;
    default:
        # code...
        break;
}


continue 语句作用到 switch 上的作用类似于 break
switch 语句一行接一行地执行当一个 case 语句中的值和 switch 表达式的值匹配时 PHP 才开始执行语句,直到 switch 的程序段结束或者遇到第一个 break 语句为止。如果不在 case 的语句段最后写上 break 的话,PHP 将继续执行下一个 case 中的语句段。例如:

<?php
switch ($i) {
    case 0:
        echo "i equals 0";
    case 1:
        echo "i equals 1";
    case 2:
        echo "i equals 2";
}

如果 $i 等于 0,PHP 将执行所有的 echo 语句!如果 $i 等于 1,PHP 将执行后面两条 echo 语句。只有当$i 等于 2 时,才会得到“预期”的结果——只显示“i equals 2”。

if和switch比较:
switch 语句中条件只求值一次并用来和每个 case 语句比较(而且switch会生成跳转表,直接跳到对应的case)。
else if 语句的条件会再次求值。
如判断参数$a的值在一到五之间,switch会先求值一次,如得到$a等于2,就会找case为2的,求值只运算了一次,而if条件中,if会进行一次求值,判断$a是否等于1,然后else if判断是否等于2…会求值多次。

if else 支持复杂的条件判断,而switch只适用于明确值的条件判断:如字典值,数字比较等;

优化多个if elseif?
1,把可能性大的条件放在前面处理,减少流程判断,因为if会从前往后依次进行判断
2,如果参数是整形,浮点型,字符串,使用switch代替if
3,混合开发中,前端模版中的判断一般用if else

替代写法

if,while,for,foreach,switch有替代写法:使用:冒号、endif、endwhile、endfor
替代语法的基本形式:
把左花括号 { ,换成冒号 : ,把右花括号 } 分别换成 endif;,endwhile;,endfor;,endforeach; 以及 endswitch;
例子:
正常写法:

while ( $i <= 10) {
    # code...
}

替代写法:更加简洁,一般用于PHP和HTML混编中,更加清晰

while ($i <= 10):
    # code...
endwhile;

五 自定义函数和内部函数

PHP的变量作用域和全局变量

可变函数

通过变量的值来调用函数,因为变量的值是可变的,所以可以通过改变一个变量的值来实现调用不同的函数。
经常用在 回调函数、函数列表,或者根据动态参数来调用不同的函数。可变函数的调用方法为变量名加括号。

<?php
function name() {
    echo 'jobs';
}
$func = 'name';
$func(); //调用可变函数


可变函数也可以用在对象的方法调用上。

<?php
class book {
     function getName() {
           return 'bookname';
     }
}
$func = 'getName';//方法名
$book = new book();
$book->$func();

静态变量:static关键字

1 仅初始化一次
2 初始化时需要赋值
3 每次执行函数,该值会保留
4 static变量是局部的,仅在函数内部有效,当程序执行离开此作用域时,其值并不会消失
5 可以记录函数的调用次数,从而可以在某些条件下终止递归
1 上 PHP基础知识 - 图3

1 上 PHP基础知识 - 图4
第一次调用函数,函数内部定义了static关键字,但是没有赋值,所以此时$count=NULL,NULl递增为1,递减没效果,由于是后递增,先返回NULL,然后递增为1,第一次结果为NULL
第二次调用函数,由于静态变量在一次程序运行中只初始化一次,所以static $count,这一步初始化操作不执行,在内存中找到$count的值为1,return $count++ , 所以此时值为1
最终的值是5 NULL 1 ,但是 echo NULL不会输出,所以最终的值是5 1

require incluce

require,需要必须的意思。在一开始就加载,放在脚本程序的最前面,文件出错时,脚本终止,致命错误
incluce,包含的意思,在用到时加载,文件中出错了,主程序继续往下执行,只有一个警告错误
在流程控制的处理区段中,require是无条件包含也就是如果一个流程里加入require,无论条件成立与否都会先执行require所在的这个区段中
_once后缀表示已加载的不加载,先检查要导入的档案是不是已经在该程序中的其它地方被导入过了,如果有的话就不会再次重复导入

内置函数

另外一些函数是通过其他扩展来支持的,比如mysql数据库处理函数,GD图像处理函数,邮件处理函数等,PHP默认加载了一些常用的扩展库,我们可以安装或者加载其他扩展库来增加PHP的处理函数。

时间日期函数

date_default_timezone_set(‘PRC’);//设置东8区时区

time()返回当前的unix时间戳
自 Unix 纪元(0:00:00 January 1, 1970 GMT)起到现在的秒数,值与micrtime()的sec值一致。

date()格式化一个本地时间/日期,第二个参数默认为time(),
echo date(“Y-m-d H:i:s”).”
“;//2017-02-28 06:56:42

strtotime()将英文文本日期时间解析为 Unix 时间戳:
指定日期的时间戳:

<?php
$t ="1989-03-01 01:00:00";
echo strtotime($t);//604688400
strtotime('now')和time();一样,都是当前时间戳
echo date('Y-m-d H:i:s',strtotime('now'));//当前时间戳 2017-01-09 21:04:11
echo date('Y-m-d H:i:s',strtotime('+1second'));//当前时间戳+1秒 2017-01-09 21:04:12
echo date('Y-m-d H:i:s',strtotime('+1minute'));//当前时间戳+1分 2017-01-09 21:05:11
echo date('Y-m-d H:i:s',strtotime('+1hour'));//当前时间戳+1小时 2017-01-09 22:04:11
echo date('Y-m-d H:i:s',strtotime('+1day'));//当前时间戳+1天 2017-01-10 21:04:11
echo date('Y-m-d H:i:s',strtotime('+1week'));//当前时间戳+1周 2017-01-16 21:04:11
echo date('Y-m-d H:i:s',strtotime('+1month'));//当前时间戳+1月 2017-02-09 21:04:11
echo date('Y-m-d H:i:s',strtotime('+1year'));//当前时间戳+1年 2018-01-09 21:04:11
echo date('Y-m-d H:i:s',strtotime('+12year 12month 12day 12hour 12minute 12second'));//当前时间戳+12年,12月,12天,12小时,12分,12秒 2030-01-22 09:16:23


mktime — 取得一个日期的 Unix 时间戳

mktime(hour,minute,second,month,day,year)

参数可以从右向左省略,任何省略的参数会被设置成本地日期和时间的当前值

获取今日、昨日、上周、本月的起始时间戳和结束时间戳

<?php
//获取今日开始时间戳和结束时间戳
 $start = mktime(0,0,0,date('m'),date('d'),date('Y'));
 $end = mktime( 0,0,0,date('m'),date('d')+1,date('Y') ) -1;//明天开始的时间戳减一就是今天结束的时间戳
 //获取昨日起始时间戳和结束时间戳
 $beginYesterday = mktime(0,0,0,date('m'),date('d')-1,date('Y'));
 $endYesterday = mktime(0,0,0,date('m'),date('d'),date('Y')) -1;
 //获取上周起始时间戳和结束时间戳
 $beginLastweek = mktime(0,0,0,date('m'),date('d')-date('w')+1-7,date('Y'));
 $endLastweek = mktime(23,59,59,date('m'),date('d')-date('w')+7-7,date('Y'));
 //获取本月起始时间戳和结束时间戳
 $beginThismonth=mktime(0,0,0,date('m'),1,date('Y'));
 $endThismonth=mktime(23,59,59,date('m'),date('t'),date('Y'));


microtime()返回当前unix时间戳和微秒数:
如果调用时不带可选参数,本函数以 “msec sec” 的格式返回一个字符串,其中 msec 是微秒部分, sec 是自 Unix 纪元(0:00:00 January 1, 1970 GMT)起到现在的秒数。字符串的两部分都是以秒为单位返回的。

<?php
echo microtime();//0.64255200 1524652129    返回值类型是string(21),前面是微妙,后面是秒。
//微妙部分,最后两位都是0,一秒等于1百万微妙,所以999999,99万9千9百99就是微妙能表示的最大值,所以微妙部分有效值只有6位。
echo microtime(true); //  1524652129.6426,返回值类型是float,是sec+msec的和,超过四位只保留四位小数
1秒(s)=1000毫秒(ms)
1 毫秒=1000 微秒
1秒(s)=1百万微秒(μs)


microtime()函数常用于性能分析

<?php
date_default_timezone_set("PRC");
$start =  microtime(true);
for($i=0; $i<100000; $i++);//执行一段循环操作,模拟耗时
$end = microtime(true);
echo $end-$start;//0.0067892074584961


获取毫秒
microtime() 函数返回当前 Unix 时间戳的微秒数。
因为1秒(s)=1000毫秒(ms)= 1,000,000 微秒(μs),所以将秒乘以1000就是毫秒,但是microtime(true)保留了四位小数,需要扩大一万倍才会没有小数部分。可以扩大一千倍就是毫妙,然后使用使用四舍五入
$msectime= round(microtime(true)1000);
如果要将毫秒级存入数据库,需要将类型改为BIGINT类型

*获取微妙:

list($usec, $sec) = explode(" ", microtime());

$usec就是微妙部分,单位是秒,乘以100万倍后单位就是微秒

getdate — 取得日期/时间信息

date_default_timezone_set('PRC');

获取当前日期的详细信息

echo '<pre>';
    print_r(getdate());
echo '</pre>';

// 格式如下:
/Array
(
[seconds] => 41 //秒
[minutes] => 33 //分
[hours] => 15 //小时
[mday] => 11 //号
[wday] => 4 //星期几
[mon] => 1 //月
[year] => 2018 //年
[yday] => 10 //一年中的某天
[weekday] => Thursday
[month] => January
[0] => 1515656021 //自 Unix 纪元以来经过的秒数
)
/

// 获取特定日期的详细信息

echo '<pre>';
    print_r(getdate(strtotime('2000-1-1 0:0:0')));
echo '</pre>';
die;

ip处理函数

使用编程语言的函数,将IP转换为整型,ip4 使用int类型就可以
ip2long 将 IPV4 的字符串互联网协议转换成长整型数字
long2ip 将长整型转化为字符串形式的互联网标准格式地址(IPV4)
用处:php中向数据库存储IP地址时可以使用 ip2long 函数将ip地址从字符串转换成整型,从而节省了在数据库的存储空间,取出时再用long2ip 即可还原IP地址:

因为PHP的 integer 类型是有符号,所有会导致许多的IP地址在32位系统下为负数, 你需要使用 “%u” 进行转换,通过 sprintf()printf() 得到的字符串来表示无符号的IP地址。

<?php
$ip = '255.255.255.255';
$ip_long = ip2long($ip);
echo $ip_long.PHP_EOL;  // 4294967295
echo long2ip($ip_long); // 255.255.255.255

255十进制转换为二进制是11111111,所以四个255就是 4组 8个1,共32个1,也就是32位系统下int无符号的最大值
但是PHPint是有符号的,所以可以把超出有符号int范围的用负数的int表示,PHP提供了转换函数:其实就是将每一项也就是255转换为2进制11111111,然后共32个1,将32个1转换为十进制就可以,最多是由10位组成。如果只是将每组的11111111转换成十进制,就是255255255255,是12位了。所以将每项转换为二进制,合并起来一起转换为整型是最节省空间的。

打印处理

echo 输出内容,不是一个函数,是语言结构,可以不加括号,echo可输出多个内容
echo 1,2,’hi’; // 1 2 hi
echo (1); //使用括号的话只能输出一个内容。
print — 输出字符串: 不是一个函数,是语言结构,可以不加括号,和 echo 最主要的区别: print 仅支持一个参数,并总是返回 1。
print_r — 打印关于变量的易于理解的信息。false不显示
var_dump — 打印变量的相关信息,var_dump还会输出类型和长度,false打印出false

var_export — 输出或返回一个变量的字符串表示,它和 var_dump() 类似,不同的是其返回的表示是合法的 PHP 代码。如var_dump返回的数组格式是便于理解数组,但是不是PHP的数组格式,而var_export返回的是PHP认识的数组格式,可以直接被PHP执行,所以是很为危险的函数。可用来生成PHP配置文件。

上面这三个函数的第二个参数设置为true的话,不会输出结果,会返回给一个变量。如将结果写入laravel日志中。
Log::info(var_export($data,true));
Log::info(print_r($data,true));
三个打印函数图解:
1 上 PHP基础知识 - 图5

序列化和反序列化函数

serialize() — 产生一个可存储的值的表示,这有利于存储或传递 PHP 的值,同时不丢失其类型和结构。serialize() 可处理除了 resource 之外的任何类型。如可以将对象序列化之后存入session中
unserialize — 反序列化,恢复对象
当序列化对象时,PHP 将试图在序列动作之前调用该对象的成员函数 sleep()。这样就允许对象在被序列化之前做任何清除操作。类似的,当使用 unserialize() 恢复对象时, 将调用 wakeup() 成员函数。

字符串处理函数

字符串函数:http://php.net/manual/zh/ref.strings.php
获取字符串的长度
strlen()函数对于计算英文字符是非常的擅长,一个空格算一个长度,多个空格算多个长度,一个UTF8的中文字符是3个长度

<?php
$str = 'hello我';
$len = strlen($str);//8
// mb_strlen() 会将一个中文字符当作长度1来计算,英文任然为一个长度
echo mb_strlen($str);// 6


字符串的截取
1、英文字符串的截取函数substr()
substr(字符串变量,开始截取的位置从0开始,截取个数)

echo substr("Hello world",6);//world 多个连续空格只算一位
2、中文字符串的截取函数mb_substr()<br />    mb_substr(字符串变量,开始截取的位置,截取个数)<br /> <br />**查找字符串**<br />    查找字符串在另一字符串中第一次出现的位置(区分大小写)<br />    strpos(要处理的字符串,要查找的字符, 规定开始搜索的位置[可选])<br /> <br />**替换字符串**
<?php
str_replace(要查找的字符串, 要替换的字符串, 被搜索的字符串, 替换进行计数[可选])
$str = 'I want to learn js';
$replace = str_replace('js', 'php', $str);

格式化字符串
printf() 函数把格式化的字符串写入变量并输出。
vprintf() 中的参数位于数组中

<?php
$str = "北京";
$number = 9;
printf("在%s有 %u 百万辆自行车。",$str,$number);//在北京有 9 百万辆自行车。
vprintf("在%s有 %u 百万辆自行车。",array($str,$number));


sprintf () 函数把格式化的字符串写入变量中,但不输出。
vsprintf() 中的参数位于数组中,

<?php
$str = "北京";
$number = 9;
$a = sprintf("在%s有 %u 百万辆自行车。",$str,$number);//不会直接输出,需要打印出来
echo $a;//在北京有 9 百万辆自行车。

例如:

<?php
 $str = '99.9';
    $result = sprintf('%01.2f', $str);
    echo $result;//结果显示99.90

%01.2f 是什么意思呢?
1、% 符号是开始的意思,写在最前面表示指定格式开始了。 也就是 “起始字符”
2、跟在 % 符号后面的是 0, 是 “填空字元” ,表示如果位置空着就用0来填满。
3、在 0 后面的是1,这个 1 是规定整个所有的字符串占位要有1位以上(小数点也算一个占位)。
如果把 1 改成 6,则 $result的值将为 099.90
因为,在小数点后面必须是两位,99.90 一共5个占位,现在需要6个占位,所以用0来填满。
4、在 %01 后面的 .2 (点2) 就很好理解了,它的意思是,小数点后的数字必须占2位。 如果这时候,$str 的值为9.234,则 $result的值将为9.23.
为什么4 不见了呢? 因为在小数点后面,按照上面的规定,必须且仅能占2位。 可是 $str 的值中,小数点后面占了3位,所以,尾数4被去掉了,只剩下 23。
5、f 表示转换成浮点数,最后以 “转换字符” 结尾。

常见的转换字符有:
%s - 字符串
%f - 浮点数(本地设置)
%u - 不包含正负号的十进制数(大于等于 0)


字符串的合并与分割

explode() — 使用一个字符串分割另一个字符串,返回一维数组
$str = "www.runoob.com";
print_r (explode(".",$str));//Array([0] => www [1] => runoob [2] => com )

implode() — 将一维数组的值合并为字符串,别名 join() 函数;
$arr = array('Hello','World!','Beautiful','Day!');
echo implode(" ",$arr);//Hello World! Beautiful Day!


字符串的转义
php字符串转义函数 addslashes()
函数说明:用于对特殊字符加上转义字符,返回一个字符串
返回值:一个经过转义后的字符串

$str = "what's your name?";
echo addslashes($str);//输出:what\'s your name?


其余函数

strrev — 反转字符串(从右往左)
strstr — 查找字符串的首次出现,并返回字符串的剩余部分:
echo strstr("I love Shanghai!","love");//love Shanghai!

strtr() — 转换指定字符,适合替换单个或者多个不连贯的字符为指定的字符。在php7之前速度要比str_replace快4倍甚至十多倍。php7速度相近
echo strtr("Hello world!","el","-*");//H-**o wor*d!
echo strtr("Hello world!","Hello","wo");//wollo world!

preg_replace()  执行一个正则表达式的搜索和替换
substr_replace() 把字符串的一部分替换为另一个字符串。通过指定下标开始替换,下标从0开始

数组处理函数

数组函数:http://php.net/manual/zh/ref.array.php

<?Php
返回键或者值
array_keys() 返回包含数组中所有键名的一个新数组。
array_values() 返回一个包含给定数组中所有键值的数组,但不保留键名。

交集,差集
array_diff() 返回两个数组的差集数组。
array_intersect() 用于比较两个(或更多个)数组的键值,并返回交集。
合并
array_merge() 把一个或多个数组合并为一个数组。
增删,可以模拟:队列(通道)先进先出,栈(桶)先进后出
array_shift() 删除数组中第一个元素,并返回被删除后元素的值。
array_unshift() 数组头部添加值
array_pop() 删除数组中的最后一个元素。并返回被删除后元素的值。
array_push()在数组尾部添加一个或多个元素
排序
sort() 函对索引数组进行升序排序。

真题答疑

<?php
$var1 = 5;
$var2 = 10;
function foo(&$my_var)
{
    global $var1;
    $var1 += 2;
    $var2 = 4;
    $my_var += 3;
    return $var2;
}
$my_var = 5;
$bar = 'foo';//可变变量,
echo $bar($my_var). "\n";// (可变函数)4,$var2是函数内的局部变量,作为返回值,所以为4
echo $my_var. "\n";//8,my_var作为引用参数传递给函数,函数内部对其修改+3,影响外部的值,所以为8
echo $var1;//7,函数内使用global申明的变量是全局变量,所以函数内对其修改影响外部的值(除了引用赋值),所以为7
echo $var2;//10,var2是函数内的局部变量,函数内修改不影响函数外部的值,仍为10