LJKのBlog

学无止境

参考:https://www.imooc.com/video/2510/0

什么是 SPL

Standard PHP Library 的缩写,它是用于解决典型(常见)问题的一组接口与类的集合。

典型(常见)问题

数学建模/数据结构

解决数据怎么存储的问题

元素遍历

数据怎么查看的问题

常用方法的同意调用

通用方法(数组、集合的大小)

自定义遍历

类定义的自动装载

让 php 程序适应大型项目的管理要求,把功能的实现分散到不同文件中。

排序

sort / rsort

按值排序,值与下标没有绑定,升/降。

asort / arsort

按元素值进行排序,值与下标绑定。

ksort / krsort

按键的值进行排序。

natsort

自然排序

1
2
3
4
5
6
7
8
9
$array1 = $array2 = array("img12.png", "img10.png", "img2.png", "img1.png");

asort($array1);
echo "Standard sorting\n";
print_r($array1);

natsort($array2);
echo "\nNatural order sorting\n";
print_r($array2);

以上例程会输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Standard sorting
Array
(
[3] => img1.png
[1] => img10.png
[0] => img12.png
[2] => img2.png
)

Natural order sorting
Array
(
[3] => img1.png
[2] => img2.png
[1] => img10.png
[0] => img12.png
)

natcasesort

自然排序, 不区分大小写. natsort 是区分大小的.

操作指针

current

返回当前被内部指针指向的数组单元的值,并不移动指针。如果内部指针指向超出了单元列表的末端,current() 返回 FALSE。 pos()就是 current()的别名.

each

返回数组中当前的 键/值 对, 并将数组指针向前移动一步.

end

将数组的内部指针指向最后一个单元.

key

返回数组中内部指针指向的当前单元的键名。

next

将数组内部的指针向前移动一位.

prev

将数组内部的指针倒回一位. 和 next 刚好相反

reset

将数组的内部指针指向第一个单元.

堆栈操作

array_push

将一个或多个单元压入数组的末尾 (入栈)

array_pop

弹出数组最后一个单元 (出栈)

array_shift

将数组开头的单元移出数组.

array_unshift

在数组开头插入一个或多个单元.

统计

其他

array_change_key_case

将数组中的所有键名修改为全大写或者全小写

*array_chunk

将一个数组分割分成多个

array_column

返回数组中指定的一列

array_combine

创建一个数组, 用一个数组的值作为键名, 另一个数组的值作为值.

*array_count_values

统计数组中的每个值出现的次数

*array_diff_key

计算数组的差集, 比较$arr, $brr, $crr, $drr…多个数组, 返回$arr 中有而其他数组中没有的 对应的值

array_diff

计算数组的差集, 和 array_diff_assoc 的区别在于不检查键.

*array_fill_keys

array array_fill_keys ( array $keys , [mixed] $value ) 使用 value 参数的值作为值,使用 keys 数组的值作为键来填充一个数组。

array_fill

给定的值填充数组

array_filter

用回调函数过滤数组中的单元

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function odd($var)
{
// returns whether the input integer is odd
return($var & 1);
}

function even($var)
{
// returns whether the input integer is even
return(!($var & 1));
}

$array1 = array("a"=>1, "b"=>2, "c"=>3, "d"=>4, "e"=>5);
$array2 = array(6, 7, 8, 9, 10, 11, 12);

echo "Odd :\n";
print_r(array_filter($array1, "odd"));
echo "Even:\n";
print_r(array_filter($array2, "even"));

ps: & 位与运算, 当都是 1 时则为 1, 否则为 0

*array_flip

交换数组中的键和值.

array_intersect_key

计算数组的交集, 比较$arr, $brr, $crr, $drr…多个数组, 返回$arr 中有而其他数组中也有的 对应的值

array_intersect

计算数组的交集. 比较$arr, $brr, $crr, $drr…多个数组, 返回$arr 中有而其他数组中也有的值

array_key_exists

检查数组中是否有指定的键

key_exists

array_key_exists的别名.

array_keys

返回数组中部分的或所有的键

array_map

为一个或多个数组的每个元素应用回调函数.

array_merge_recursive

递归的合并多个数组, 相同键的值会合并到一个数组中.

array_merge

合并多个数组, 相同键的值会覆盖.

array_multisort

可以用来一次对多个数组进行排序,或者根据某一维或多维对多维数组进行排序。

*array_pad

返回 array 的一个拷贝,并用 value 将其填补到 size 指定的长度。如果 size 为正,则填补到数组的右侧,如果为负则从左侧开始填补。如果 size 的绝对值小于或等于 array 数组的长度则没有任何填补。有可能一次最多填补 1048576 个单元。

array_product

计算数组中所有值得乘积

array_rand

从数组中随机取出一个或多个单元

array_reduce

将回调函数 callback 迭代地作用到 array 数组中的每一个单元中,从而将数组简化为单一的值。

array_replace_recursive

使用后面数组元素的值替换数组 array1 的值。 如果一个键存在于第一个数组同时也存在于第二个数组,它的值将被第二个数组中的值替换。 如果一个键存在于第二个数组,但是不存在于第一个数组,则会在第一个数组中创建这个元素。 如果一个键仅存在于第一个数组,它将保持不变。 如果传递了多个替换数组,它们将被按顺序依次处理,后面的数组将覆盖之前的值。 是递归的:它将遍历数组并将相同的处理应用到数组的内部值。

array_replace

与 array_replace_recursive 的区别就是他是非递归的.

array_reverse

返回单元顺序相反的数组

在数组中搜索给定的值,如果成功则返回首个相应的键名,如果失败则返回 false。

in_array

在数组中搜索给定的值,如果成功则返回 true,如果失败则返回 false。

array_slice

从数组中取出一段.

1
2
$foo = [1,2,3,4,5,6,7,8,9];
var_dump(array_slice(array_reverse($foo), 0, 2));

array_splice

去掉数组中的某一部分, 并用其他值代替.

array_sum

对数组中所有值求和

array_unique

移除数组中重复的值, 返回过滤后的数组.

array_values

返回数组的值并建立数字索引.

array_walk_recursive

对数组中的每个成员递归地应用自定义函数, 函数默认传入两个参数, 值 和 键.

array_walk

同 array_walk_recursive, 但是不递归.

array

新建一个数组.

compact

新建一个数组, 包括变量名和他们的值.

extract

它的作用和 compact 刚好相反.

count

计算数组中的单元数目, 或对象中的属性个数. sizeof 是 count 的别名.

list

把数组中的值赋给一组变量. 注意: PHP5 中, list()从最右边的参数开始赋值; PHP7 中, list()从最左边的参数开始赋值. 通常而言, 不建议依赖于操作的顺序, 在未来可能会再次发生改变.

range

创建一个包含指定范围单元的数组.

shuffle

打乱数组

示例:

1
2
3
4
public function onRequest(?string $action): ?bool
{
return parent::onRequest($action); // TODO: Change the autogenerated stub
}

已上示例表示传入的参数$action 可以是 null 或者字符串,不能是其他类型。同理:返回值只能是 bool 或者 null。

CLI 模式

CLI 模式也就是命令行模式,

zend_types.h 中定义了所有变量的数据类型。

zend_hash.h 中定义了数组相关的 api。

zend_alloc_sizes.h 和 zend_alloc.c 和 zend_alloc.h 这三个文件是和内存相关。

zend_alloc_sizes.h 中重点掌握 ZEND_MM_BINS_INFO 这个宏定义。

zend_alloc.h 中重点掌握以下函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ZEND_API char*  ZEND_FASTCALL zend_strndup(const char *s, size_t length) ZEND_ATTRIBUTE_MALLOC;

ZEND_API void* ZEND_FASTCALL _emalloc(size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) ZEND_ATTRIBUTE_MALLOC ZEND_ATTRIBUTE_ALLOC_SIZE(1);
ZEND_API void* ZEND_FASTCALL _safe_emalloc(size_t nmemb, size_t size, size_t offset ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) ZEND_ATTRIBUTE_MALLOC;
ZEND_API void* ZEND_FASTCALL _safe_malloc(size_t nmemb, size_t size, size_t offset) ZEND_ATTRIBUTE_MALLOC;
ZEND_API void ZEND_FASTCALL _efree(void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
ZEND_API void* ZEND_FASTCALL _ecalloc(size_t nmemb, size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) ZEND_ATTRIBUTE_MALLOC ZEND_ATTRIBUTE_ALLOC_SIZE2(1,2);
ZEND_API void* ZEND_FASTCALL _erealloc(void *ptr, size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) ZEND_ATTRIBUTE_ALLOC_SIZE(2);
ZEND_API void* ZEND_FASTCALL _erealloc2(void *ptr, size_t size, size_t copy_size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) ZEND_ATTRIBUTE_ALLOC_SIZE(2);
ZEND_API void* ZEND_FASTCALL _safe_erealloc(void *ptr, size_t nmemb, size_t size, size_t offset ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
ZEND_API void* ZEND_FASTCALL _safe_realloc(void *ptr, size_t nmemb, size_t size, size_t offset);
ZEND_API char* ZEND_FASTCALL _estrdup(const char *s ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) ZEND_ATTRIBUTE_MALLOC;
ZEND_API char* ZEND_FASTCALL _estrndup(const char *s, size_t length ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) ZEND_ATTRIBUTE_MALLOC;
ZEND_API size_t ZEND_FASTCALL _zend_mem_block_size(void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);

zend_alloc.c 中重点掌握 AG 这个宏:

1
2
3
4
5
6
7
8
9
10
11
typedef struct _zend_alloc_globals {
zend_mm_heap *mm_heap;
} zend_alloc_globals;

#ifdef ZTS
static int alloc_globals_id;
# define AG(v) ZEND_TSRMG(alloc_globals_id, zend_alloc_globals *, v)
#else
# define AG(v) (alloc_globals.v)
static zend_alloc_globals alloc_globals;
#endif

ReflectionClass
ReflectionMethod

ReflectionClass 类报告了一个类的有关信息。

ReflectionMethod 类报告了一个方法的有关信息。

实例化 ReflectionMethod 的时候需要传入对象和方法两个参数。

通常 ReflectionMethod 可以作为参数传递到ReflectionClassgetMethod。来限制获取的方法类别。

  1. Traversable 遍历接口
  2. Iterator 迭代器接口
  3. IteratorAggregate 聚合式迭代器接口
  4. ArrayAccess 数组式访问接口
  5. Serializable 序列化接口
  6. Closure

JS 版本数组去重

1
2
3
let arr = [2, 22, 2, 2];
let x = new Set(arr);
console.log([...x]);

PHP 版本数组去重

1
2
3
4
5
6
7
8
$input = ["a" => "green", "red", "b" => "green", "blue", "red"];
//方法一 使用array_unique()函数
array_unique($input);
print_r($input);
//方法二 键值交换, 然后只取键
$res = array_flip($input);
$res = array_keys($input);
print_r($res);

补充 : 如何重建数组的索引 ?

1
//使用array_values 返回数组的值并建立数字索引.

进程数和线程数设置要考虑以下条件

  • CPU 核数
  • 内存大小
  • 业务偏向 IO 密集还是 CPU 密集 ( 注意非阻塞式IO 属于 CPU 密集型,而不属于 IO 密集型。)

比较一下三种方式, 哪种更好?

  1. 多进程单线程
  2. 单进程多线程
  3. 多进程多线程

个人感觉, 在 swoole 中, 因为是全协程异步. 所以适合单进程多线程, 然后线程数目设置等于 CPU 核数.

下载 php 的 tar.gz 包,解压后进入 Zend 目录,其中有 bench.php 和 micro_bench.php 两个文件。

使用不同版本的 php 分别执行这两个文件,看看执行时间差距。

每一个变量都有一个内存位置,每一个内存位置都定义了可使用连字号(&)运算符访问的地址,它表示了在内存中的一个地址。

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

int main ()
{
int var1;
char var2[10];

printf("var1 变量的地址: %p\n", &var1 );
printf("var2 变量的地址: %p\n", &var2 );

return 0;
}

结果:

1
2
var1 变量的地址: 0x7fff5cc109d4
var2 变量的地址: 0x7fff5cc109de

如何使用指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>

int main ()
{
int var = 20; /* 实际变量的声明 */
int *ip; /* 指针变量的声明 */

ip = &var; /* 在指针变量中存储 var 的地址 */

printf("Address of var variable: %p\n", &var );

/* 在指针变量中存储的地址 */
printf("Address stored in ip variable: %p\n", ip );

/* 使用指针访问值 */
printf("Value of *ip variable: %d\n", *ip );

return 0;
}

结果:

1
2
3
Address of var variable: bffd8b3c
Address stored in ip variable: bffd8b3c
Value of *ip variable: 20

结构体指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
struct house {
char location[20];
float area;
int price;
};
int main() {
struct house kangqiao = {'昌邑', 132.0, 5000}; # 位置、面积、价格
struct house * h; # 定义指针h
h = &kangqiao # 给指针h赋值
printf("location=%s\n", (*h).location); # 打印location
# (*h).location、h->location、kangqiao.location 这三种写法是等价的
return 0;
}

数组指针

1
2
3
4
5
6
7
8
#include <stdio.h>
int main() {
int arr[] = {1,2,3,4};
int * p = arr; # 现在p指向arr第一个元素也就是arr[1]的地址
p++; # 现在p向后移动一位,指向arr的下一个元素,也就是arr[2]
# int * p = arr; p++; 这种写法等价与 int * p = arr + 1
return 0;
}

int _ p = arr; p++; 这种写法等价与 int _ p = arr + 1

字符串数组

C 语言规定数组代表数组所在内存位置的首地址,也就是 str[0]的地址,即 str=&str[0];

而 printf(“%s\n”, str); 为什么用首地址就可以输出字符串。

因为还有一个关键,在 C 语言中字符串常量的本质其实是一个地址,这个是许多初学者比较难理解的问题。

新建一个 c 文件,hello.c

1
2
3
4
5
6
#include <stdio.h>
int main()
{
puts("hello word");
return 0;
}

然后

1
gcc ./hello.c

在当前目录下生成 a.out 可执行文件。

1
./a.out

输出 hello word 。

这个编译过程经过了四个步骤:

  1. .c 文件 -> .i 文件 预处理
  2. .i 文件 -> .s 文件 编译
  3. .s 文件 -> .o 文件 汇编
  4. .o 文件 -> .out 可执行文件 链接

预处理

1
2
gcc -o hello.i hello.c -E
# -o 参数是让生成 .i 文件, -E参数是只执行预处理,后面的步骤不执行

宏定义

1
#define R 20

define 中是没有 c 语法的,只是单纯的字符串替换

宏非常简单,就是“替换”

宏函数

typedef

typedef 和预处理没什么关系, 但是和宏比较容易混淆.

它的作用是给变量类型起别名

条件编译

#if

1
2
3
4
5
6
7
8
9
#if 整型常量表达式 1
程序段 1
#elif 整型常量表达式 2
程序段 2
#elif 整型常量表达式 3
程序段 3
#else
程序段 4
#endif

它的意思是:如常“表达式 1”的值为真(非 0),就对“程序段 1”进行编译,否则就计算“表达式 2”,结果为
真的话就对“程序段 2”进行编译,为假的话就继续往下匹配,直到遇到值为真的表达式,或者遇到 #else。这一
点和 if else 非常类似。
需要注意的是,#if 命令要求判断条件为“整型常量表达式”,也就是说,表达式中不能包含变量,而且结果必须
是整数;而 if 后面的表达式没有限制,只要符合语法就行。这是 #if 和 if 的一个重要区别。

#ifdef

1
2
3
4
5
#ifdef 宏名
程序段 1
#else
程序段 2
#endif

它的意思是,如果当前的宏已被定义过,则对“程序段 1”进行编译,否则对“程序段 2”进行编译。

#ifndef

1
2
3
4
5
#ifndef 宏名
程序段 1
#else
程序段 2
#endif

与 #ifdef 相比,仅仅是将 #ifdef 改为了 #ifndef。它的意思是,如果当前的宏未被定义,则对“程序段 1”进行编
译,否则对“程序段 2”进行编译,这与 #ifdef 的功能正好相反。

三者之间的区别

最后需要注意的是,#if 后面跟的是“整型常量表达式”,而 #ifdef 和 #ifndef 后面跟的只能是一个宏名,不能是
其他的。

结构体是不同类型的变量的集合, 与数组相反, 数组是相同类型变量的集合

定义

示例: 定义一个名为 house 的结构体,有三种方式:

定义结构体和定义变量分开

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
struct house {
char location[20];
int area;
int price;
};
int main() {
struct house kangqiao;
return 0;
}

定义结构体同时定义变量

1
2
3
4
5
6
7
8
9
#include <stdio.h>
struct house {
char location[20];
int area;
int price;
}kangqiao;
int main() {
return 0;
}

只为一个变量定义结构体

1
2
3
4
5
6
7
8
9
#include <stdio.h>
struct {
char location[20];
int area;
int price;
}kangqiao;
int main() {
return 0;
}

初始化和引用

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
struct house {
char location[20];
float area;
int price;
};
int main() {
struct house kangqiao = {'昌邑', 132.0, 5000}; # 位置、面积、价格
return 0;
}

结构体指针

结构体的内存对齐方式

对齐规则

  1. 第一个成员在结构体变量偏移量为 0 的地址处。
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。对齐数 = 编译器默认的一个对齐数与该成员大小中的较小值。vs 中默认值是 8 Linux 默认值为 4.
  3. 结构体总大小为最大对齐数的整数倍。(每个成员变量都有自己的对齐数)
  4. 如果嵌套结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(包含嵌套结构体的对齐数)的整数倍。

共用体也被称为联合体; 使用 union 关键字定义;

  1. union 中可以定义多个成员,union 的大小由最大的成员的大小决定。
  2. union 成员共享同一块大小的内存,一次只能使用其中的一个成员。
  3. 对某一个成员赋值,会覆盖其他成员的值(也不奇怪,因为他们共享一块内存。但前提是成员所占字节数相同,当成员所占字节数不同时只会覆盖相应字节上的值,比如对 char 成员赋值就不会把整个 int 成员覆盖掉,因为 char 只占一个字节,而 int 占四个字节
  4. 联合体 union 的存放顺序是所有成员都从低地址开始存放的。