ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

PHP学习笔记7:控制流

2021-12-05 14:31:18  阅读:224  来源: 互联网

标签:echo name val 控制流 笔记 EOL PHP match


PHP学习笔记7:控制流

image-20211129162010327

图源:php.net

if

php中常用的if语法与C++或Java中的没有区别:

<?php
$a = 1;
if ($a < 5) {
    echo "a < 5" . PHP_EOL;
} else if ($a == 5) {
    echo "a == 5" . PHP_EOL;
} else {
    echo "a > 5" . PHP_EOL;
}
// a < 5

其中else if也可以写作elseif,两者几乎没有区别。

php还有一种不常见的替代语法:

<?php
$a = 1;
if ($a < 5):
    echo "a < 5" . PHP_EOL;
elseif ($a == 5):
    echo "a == 5" . PHP_EOL;
else:
    echo "a > 5" . PHP_EOL;
endif;
// a < 5

这种替代写法在风格上更像shell(比如bash),如果将其运用在html模版中时会比使用{}顺眼一点:

<?php if (1<2): ?>
    <h1>1<2</h1>
<?php endif; ?>

但老实说,我多年工作中从来没见人这么写过,事实上现在各种模版引擎都很成熟了,并不需要将php用于模版语言,就算是小型项目,使用传统语法也不会对有经验的php程序员造成影响,反而如果是混合着使用这种替代语法会让人困扰。

while

while循环也没有太多可说的,其行为也与传统语言一致:

<?php
$arr = range(1, 20, 2);
$index = 0;
$len = count($arr);
while ($index < $len) {
    echo $arr[$index] . ' ';
    $index++;
}
echo PHP_EOL;
// 1 3 5 7 9 11 13 15 17 19 

遍历数组的最优方式依然是foreach,这里仅为举例,下面介绍其他循环语句的示例同样如此,不再重复说明。

do while

do...whilewhile类似,区别在于do...while中至少会执行一次循环块:

$arr = range(1, 20, 2);
$index = 0;
do {
    if (count($arr) == 0) {
        break;
    }
    echo $arr[$index] . ' ';
    $index++;
} while ($index < count($arr));
// 1 3 5 7 9 11 13 15 17 19 

for

for语句是除了foreach以外最常用的循环语句:

<?php
$arr = range(1, 10);
for ($i = 0; $i < count($arr); $i++) {
    echo $arr[$i] . ' ';
}
echo PHP_EOL;
// 1 2 3 4 5 6 7 8 9 10 

上边这个循环语句存在一个效率问题,即每次循环都会执行count($arr)来计算数组长度,虽然数组长度在底层应该是保存为一个具体的值,count($arr)理论上应该是一个常数级别的时间复杂度,但尽可能优化代码总是好的:

...
$len = count($arr);
for ($i = 0; $i < $len; $i++) {
    ...
}

我经常这么写,不过还可以:

...
for ($i = 0, $len = count($arr); $i < $len; $i++) {
	...
}

这么做的优点是结构更清晰,因为理论上讲,$len只会在循环语句中使用,是一个归属于for语句的局部变量。但实际上这在php中并非如此,在循环体后依然可以访问到$len,无论你将它定义在for的初始化语句中还是for之外。关于这点我已经在PHP学习笔记4:变量中变量作用域的部分讨论过了。

foreach

foreach可以遍历iterable类型的变量,这点在PHP学习笔记3:其它类型和类型声明中有过介绍:

$a = range(1, 10);
foreach ($a as $val) {
    echo "{$val} ";
}
echo PHP_EOL;
// 1 2 3 4 5 6 7 8 9 10 
foreach ($a as $key => $val){
    echo "{$key}:{$val}, ";
}
echo PHP_EOL;
// 0:1, 1:2, 2:3, 3:4, 4:5, 5:6, 6:7, 7:8, 8:9, 9:10, 

这里再展示一个遍历生成器函数的示例:

function get_fibnaci(): iterable
{
    yield 1;
    yield 1;
    yield 2;
    yield 3;
    yield 5;
}
foreach (get_fibnaci() as $num) {
    echo "{$num} ";
}
echo PHP_EOL;

遍历的时候可以利用索引结合[]修改数组的值:

require_once "../util/array.php";
$a = range(1, 10);
foreach ($a as $key => $val) {
    $a[$key] = 2 * $val;
}
print_arr($a);
// [0:2, 1:4, 2:6, 3:8, 4:10, 5:12, 6:14, 7:16, 8:18, 9:20]

不过有更简单的方式:

require_once "../util/array.php";
$a = range(1, 10);
foreach ($a as &$val) {
    $val = 2 * $val;
}
unset($val);
print_arr($a);
// [0:2, 1:4, 2:6, 3:8, 4:10, 5:12, 6:14, 7:16, 8:18, 9:20]

需要注意的是,使用foreach遍历数组时使用的引用变量&$val应当在循环结束后立即使用unset()消除。否则可能会在之后使用同样名称的循环变量时产生一些问题:

require_once "../util/array.php";
$a = range(1, 10);
foreach ($a as &$val) {
    $val = 2 * $val;
}
print_arr($a);
// [0:2, 1:4, 2:6, 3:8, 4:10, 5:12, 6:14, 7:16, 8:18, 9:20]
$b = [1, 1, 1];
foreach ($b as $val) {;
}
print_arr($a);
// [0:2, 1:4, 2:6, 3:8, 4:10, 5:12, 6:14, 7:16, 8:18, 9:1]

第二个foreach循环中,$val并非一个新建的变量,而是由之前循环建立的,而且这是一个引用变量,当前指向的是$a[9]的引用,所以foreach ($b as $val)的一个副作用是,每次循环都会将引用变量指向的变量值用$b当前元素改写。所以在第二次循环结束后,$a[9]的值当变成$b[2]的值,也就是1

有时候可以使用一些简单的结构来表示一些隐藏信息,而不是使用完整的索引:

<?php
$students = array(
    array("Li lei", 20),
    array("Xiao Ming", 15),
    array("Jack Chen", 10),
);
foreach ($students as $std) {
    $name = $std[0];
    $age = $std[1];
    echo "Student(name:{$name}, age:{$age})" . PHP_EOL;
}

但就像上面代码展示的那样,此时需要使用下标来明确指定对应的数据,稍显麻烦。在Python和Go中,有一种更简便的方式:

students = [("Li Lei", 20), ("Xiao Ming", 20), ("Jack Chen", 10)]
for name, age in students:
    print("student(name:{:s}, age:{:d})".format(name, age))
# student(name:Li Lei, age:20)
# student(name:Xiao Ming, age:20)
# student(name:Jack Chen, age:10)

在Python中,for name,age in students这种方式叫做“解包”,就是将每一个迭代的students元素从元组分解到对应的单独变量。

php也提供类似的方式:

<?php
$students = array(
    array("Li lei", 20),
    array("Xiao Ming", 15),
    array("Jack Chen", 10),
);
foreach ($students as list($name, $age)) {
    echo "Student(name:{$name}, age:{$age})" . PHP_EOL;
}
// Student(name:Li lei, age:20)
// Student(name:Xiao Ming, age:15)
// Student(name:Jack Chen, age:10)

这里的list()虽然看起来很像是一个函数,但本质上并不是,它是一个语法结构。可以将list理解为一个特殊的,需要和()结合使用的关键字。

和Python的解包语法类似,使用list时也可以进行缺省:

...
foreach ($students as list($name,)) {
    echo "Student(name:{$name}" . PHP_EOL;
}
// Student(name:Li lei
// Student(name:Xiao Ming
// Student(name:Jack Chen

需要注意的是,php的list和Python的解包语法相比,更死板:

$students = array(
    array("Li lei", 20, "3年级", "2班", "swim"),
    array("Xiao Ming", 15, "5年级", "6班", "draw"),
    array("Jack Chen", 10, "7年级", "2班", "music"),
);
foreach ($students as list($name,, $favorite)) {
    echo "Student(name:{$name}, favorite:{$favorite}" . PHP_EOL;
}
// Student(name:Li lei, favorite:3年级
// Student(name:Xiao Ming, favorite:5年级
// Student(name:Jack Chen, favorite:7年级

可以看到,list产生的结果是和数组中的元素位置完全对应的,并不能像Python那样通过一些特殊语法获取头部和尾部的信息:

students = [("Li Lei", 20, "四年级", "3班", "swim"),
            ("Xiao Ming", 20, "六年级", "8班", "music"),
            ("Jack Chen", 10, "三年级", "2班", "draw")]
for name, *_, favorite in students:
    print("student(name:{:s}, favorite:{:s})".format(name, favorite))
# student(name:Li Lei, favorite:swim)
# student(name:Xiao Ming, favorite:music)
# student(name:Jack Chen, favorite:draw)

不管怎么说,妥善地使用list语法会让一些遍历代码简洁很多。

更多list的使用说明见官方手册list

break

break可以让代码从循环或者switch结构中跳出。

这里以一个产生圣诞树字符图形的代码进行说明:

<?php
$a = range(1, 10);
foreach ($a as $val) {
    for ($i = 0; $i < $val; $i++) {
        echo '*';
    }
    echo PHP_EOL;
}
// *
// **
// ***
// ****
// *****
// ******
// *******
// ********
// *********
// **********

如果需要限定产生的*形字符的最大长度,可以:

$a = range(1, 10);
foreach ($a as $val) {
    for ($i = 0; $i < $val; $i++) {
        if ($i >= 6) {
            break;
        }
        echo '*';
    }
    echo PHP_EOL;
}
// *
// **
// ***
// ****
// *****
// ******
// ******
// ******
// ******
// ******

这样做只会跳出里层的循环,所以最后依然会以最大长度输出几行*。如果要让程序在超过最大长度后直接结束输出,可以:

<?php
$a = range(1, 10);
foreach ($a as $val) {
    for ($i = 0; $i < $val; $i++) {
        if ($i >= 6) {
            break 2;
        }
        echo '*';
    }
    echo PHP_EOL;
}
// *
// **
// ***
// ****
// *****
// ******
// ******

当然这并非唯一的做法,仅用于演示。

就像示例代码中展示的,break语句可以追加数字,以表明要跳出的循环层数,默认情况下仅会跳出一层循环,相当于break 1

continue

php中continue的基本用法与其它语言相同,这里不做过多介绍。比较特别的是,continuebreak一样,也可以接受一个正整数,可以指定跳出若干层循环体后再进入下一次循环:

$a = range(1, 10);
foreach ($a as $val) {
    echo PHP_EOL;
    for ($i = 0; $i < $val; $i++) {
        if ($i >= 3 && $val % 2 == 0) {
            continue 2;
        }
        echo '*';
    }
}
// 
// *
// **
// ***
// ***
// *****
// ***
// *******
// ***
// *********
// ***

现在的图形更像圣诞树了:)

switch

switch的用法完全和C++/Java保持一致:

<?php
function get_response(string $request): string
{
    $response = "";
    switch ($request) {
        case "hello":
        case "你好":
            $response = "hello";
            break;
        case "how are you":
            $response = "how are you";
            break;
        case "bye":
            $response = "bye";
            break;
        default:
            $response = "I don't known";
    }
    return $response;
}
echo get_response("hello") . PHP_EOL;
echo get_response("你好") . PHP_EOL;
echo get_response("bye") . PHP_EOL;
// hello
// hello
// bye

match

match是php8.0.0新加入的语法结构,其基本的语法规范是:

<?php
$return_value = match (subject_expression) {
    single_conditional_expression => return_expression,
    conditional_expression1, conditional_expression2 => return_expression,
};

switch类似,match也是罗列多个匹配条件进行匹配,并且每个匹配条件对应一个匹配后会被执行的表达式。不同的是,match中匹配条件可以是复杂的表达式,而不仅仅局限于简单的基础类型数据。

此外,match结构会返回一个结果,该结果是匹配到的条件对应的表达式执行后产生的。

switch一样,match的匹配条件也是顺序执行,不过需要注意的是,如果某个条件被匹配,之后的匹配条件中的表达式都不会被执行:

<?php
function cond_func1()
{
    echo "cond_func1 is called" . PHP_EOL;
    return 1;
}
function cond_func2()
{
    echo "cond_func2 is called" . PHP_EOL;
    return 2;
}
function cond_func3()
{
    echo "cond_func3 is called" . PHP_EOL;
    return 3;
}
function match_func(int $num): string
{
    return match ($num) {
        cond_func1() => 'func1',
        cond_func2() => 'func2',
        cond_func3() => 'func3',
    };
}
match_func(1);
// cond_func1 is called
match_func(2);
// cond_func1 is called
// cond_func2 is called
match_func(3);
// cond_func1 is called
// cond_func2 is called
// cond_func3 is called

match中应当至少有一个条件被匹配到,否则会产生一个UnhandledMatchError类型的异常:

match_func(4);
// Fatal error: Uncaught UnhandledMatchError: Unhandled match case 4 in ...

这个问题可以使用default来避免:

...
function match_func(int $num): string
{
    return match ($num) {
        cond_func1() => 'func1',
        cond_func2() => 'func2',
        cond_func3() => 'func3',
        default => 'no matched func',
    };
}
...
match_func(4);
// cond_func1 is called
// cond_func2 is called
// cond_func3 is called

可以将match的“主题”设置为true,此时match的作用将超脱“匹配”这个功能,而是变为纯粹地依次检查条件表达式的值是否为true。利用这个特点,我们可以用match实现一些本来可能要使用if...elif...构建的复杂语句:

<?php
function get_response(string $request): string
{
    return match (true) {
        str_contains($request, 'hello') || str_contains($request, '你好') => 'hello',
        str_contains($request, 'bye') || str_contains($request, '再见') => 'bye',
        default => "I don't known",
    };
}
echo get_response("hello, how are you.").PHP_EOL;
// hello
echo get_response("你好,吃饭了吗?").PHP_EOL;
// hello
echo get_response("bye.").PHP_EOL;
// bye

再看一个根据给定成绩返回分数等级的例子:

<?php
function get_grade(int $mark): string
{
    return match (true) {
        $mark < 60 => 'D',
        $mark >= 60 && $mark < 80 => 'C',
        $mark >= 80 && $mark < 90 => 'B',
        $mark >= 90 && $mark <= 100 => 'A',
        default => 'Error',
    };
}
echo get_grade(55).PHP_EOL;
// D
echo get_grade(70).PHP_EOL;
// C
echo get_grade(90).PHP_EOL;
// A

return

return语句的作用同样无需过多赘述,唯一需要阐述的是,php作为一个脚本语言,return同样可以在最外层的脚本全局作用域内使用:

<?php
// a.php
$num = 1;
if ($num < 5){
    return "\$num < 5";
}
else{
    return "\$num >= 5";
}
echo "this will not be executed".PHP_EOL;

另一个脚本b.php引用a.php,并输出结果:

<?php
// b.php
$result = include("./a.php");
echo $result.PHP_EOL;

不过这样的用法并不常见,至少我从来没见过。

require

php使用inluderequire来加载别的代码。includerequire的功能几乎完全一样,唯一的区别是如果加载目标代码失败,require会产生E_ERROR,并直接终止程序,后续代码将不会执行:

<?php
// a.php
echo "a.php is loaded." . PHP_EOL;
<?php
// b.php
require "./s.php";
require "./a.php";
// Warning: require(./s.php): Failed to open stream: No such file or directory in ... on line 2

// Fatal error: Uncaught Error: Failed opening required './s.php' (include_path='.;C:\php\pear') in ...
// Stack trace:
// #0 {main}
//   thrown in ...b.php on line 2

include则只会产生E_WARMING,后续的代码依然会被执行:

<?php
// b.php
include "./s.php";
include "./a.php";

// Warning: include(./s.php): Failed to open stream: No such file or directory in ...b.php on line 3

// Warning: include(): Failed opening './s.php' for inclusion (include_path='.;C:\php\pear') in ...b.php on line 3
// a.php is loaded.

从更早发现可能的bug的角度看,更推荐使用require

在实际开发中,更多的是使用require_onceinclude_once,这样可以避免重复加载代码,并且还可以循环引用代码:

<?php
// a.php
require_once "./b.php";
class Student{

}
$teacher = new Teacher();
<?php
// b.php
require_once "./a.php";
class Teacher{

}
$std = new Student();

以前我以为循环引用是一件很普通的事,前一段时间学习了Python和Go才知道,很多语言都不支持循环引用,遇到类似的问题都要对代码进行拆分,以避免产生循环引用的问题。

关于加载代码的其它细节问题,这里不过多阐述,想了解的可以阅读官方手册include

goto

php是支持goto语法的,如果说C的指针是一个相当被诟病的设计,很多后来的借鉴于C的新语言都摒弃或者改善了对指针的支持,那么goto就是一个彻彻底底的“恶魔”,你几乎找不出哪一门别的支持goto的语言,当然,php可以。

虽然php对goto仅是有限支持,但我个人对此的建议是不要使用。

如果你依然感兴趣,可以前往官方文档goto

谢谢阅读。

往期内容

标签:echo,name,val,控制流,笔记,EOL,PHP,match
来源: https://blog.csdn.net/hy6533/article/details/121729094

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有