函数

之前的章节我们了解了 PHP 的基础语法、变量与常量、数据类型、运算符和流程控制等基础知识,仅仅是这些我们并不算入门,本章我们将讲述 PHP 入门的“最后一脚”——函数。

不论是哪门编程语言,函数都占据着非常重要的地位。在一些编程语言(例如 JavaScript )中,函数甚至与其他数据类型地位一致。虽然 PHP 并非如此,但从其内置的大量函数来看,函数的重要性不言而喻。

我们先来了解函数的组成部分!

组成部分

在 PHP 中,除了内置函数,用户自定义的函数以关键字function标明,后跟函数名和用圆括号括起来的可选参数列表,最后是花括号括起来的函数体,形如:

function funcName($arg1, $arg2, $arg3)
{
    // 任何有效的PHP代码
}

解析如下:

  • function是函数声明关键字
  • funcName为函数名,函数名必须以下划线或者字母开头,后跟字母或者数字或者下划线
    • 请始终不要用两个下划线开头,这通常是 PHP 保留的魔术方法命名
    • 避免函数名与 PHP 结构名(如echoprintreturn等)重名,会导致解析错误
  • ($arg1, $arg2, $arg3)是参数列表,使用小括号括起来,参数列表可选

类型声明

虽然 PHP 是一门弱类型语言,但是函数的参数可以指定部分数据类型,这也被称为类型提示

可指定的合法类型声明有:类名/接口名、self(表示函数本身所在的类)、arraycallableboolfloatintstring

如果传入的参数数据类型不符合,则会造成类型异常。例如:

<?php

function add(int $left, int $right)
{
    return $left + $right;
}
echo add(1, 2);
echo PHP_EOL;
echo add('1', 2);// 输出:3
echo PHP_EOL;
echo add('jisu', 'api');// 输出错误信息:Fatal error: Uncaught TypeError: Argument 1 passed to add() must be of the type int, string given

如果你像笔者一样使用带代码提示的编辑器(VS Code),输入上方代码你可能会看到下面的提示:

类型提示

由结果我们可以看出,如果在函数中指定了传入参数的数据类型,这个约束是宽松的,就像上例中,字符串'1'可以被自动转换为整型1,但其他无法转换的将造成类型异常。建议读者可以多多尝试各种类型参数传入,运行测试结果。

参数默认值

函数的参数可以设置默认值,但设置了默认值的参数必须放在未设置默认值的参数后面,同时默认值必须只能是四种标量类型、复合类型array和特殊类型null。例如:

<?php

// 函数:参数

$user_list = [];
function register($name, $gender = '女')
{
    global $user_list;// 使用了全局变量$user_list
    $user_list[] = [
        'name'=>$name,
        'gender'=>$gender
    ];
    return $user_list;
}
// 传入两个参数
register('极速数据', '男');
var_dump($user_list);
/* 输出:
array(1) {
  [0]=>
  array(2) {
    ["name"]=>
    string(12) "极速数据"
    ["gender"]=>
    string(3) "男"
  }
}
*/

// 传入一个参数,此时默认该参数为$name,而$gender默认为'女'
register('PHP教程');
var_dump($user_list);
/* 输出:
array(2) {
  [0]=>
  array(2) {
    ["name"]=>
    string(12) "极速数据"
    ["gender"]=>
    string(3) "男"
  }
  [1]=>
  array(2) {
    ["name"]=>
    string(9) "PHP教程"
    ["gender"]=>
    string(3) "女"
  }
}
*/

// 试图传入三个参数
register('被当做第一个参数','被当做第二个参数覆盖了默认值','第三个参数由于没有被用到所以被忽略了');
var_dump($user_list);
/* 输出:
array(3) {
  [0]=>
  array(2) {
    ["name"]=>
    string(12) "极速数据"
    ["gender"]=>
    string(3) "男"
  }
  [1]=>
  array(2) {
    ["name"]=>
    string(9) "PHP教程"
    ["gender"]=>
    string(3) "女"
  }
  [2]=>
  array(2) {
    ["name"]=>
    string(24) "被当做第一个参数"
    ["gender"]=>
    string(42) "被当做第二个参数覆盖了默认值"
  }
}
*/

使用参数默认值可以减少输入,在一定程度上也避免了出错。

参数的引用传递

在 PHP 中,函数参数默认是通过值传递的,也就是说,在函数内部做的改变并不会影响函数外部的值,如下:

<?php

// 函数参数的值传递:$a与$b都是值传递
function add($a, $b)
{
    $a++;// $a递增
    $b++;// $b递增
    return $a + $b;
}
$a = 7;
$b = 8;
add($a, $b);
echo $a;// 输出:7。此时 $a 不变
echo PHP_EOL;
echo $b;// 输出:8。此时 $b 不变

但如果函数想要改变参数值呢?那就需要使用引用传递。参数通过引用传递,在函数内部做的任何改变都会影响到参数值。要想对某个参数使用引用传递,只需要在参数前加上符号&,如下所示:

<?php

// 函数参数的引用传递:其中$a进行引用传递,$b仍是值传递
function add2(&$a, $b)
{
    $a++;
    $b++;
    return $a + $b;
}
$a = 7;
$b = 8;
add2($a, $b);
echo $a;// 输出:8。此时 $a 改变了
echo PHP_EOL;
echo $b;// 输出:8。此时 $b 保持不变

可变参数列表

参数的默认值部分,我们写了一个传入两个参数的函数,但在使用该函数时传入三个参数也并没有出错。这是因为 PHP 的函数接受参数列表是可变的。

在许多严格类型的编程语言里,例如 Java ,如果向函数传入多余的参数将会导致错误。你也许听过“重载”,很多语言也支持重载,它意味着同名函数接受不同类型或者数量的参数。但PHP没有重载

PHP 函数参数列表的可变性加上弱类型,无法支持重载操作。这对于一部分从强类型语言转来的朋友可能会很不适应,但通过使用可变参数列表,可以达到“重载”的效果。

例如一个可能有多个值的求和运算,在强类型编程语言中,可能需要写多个同名但参数列表不同的函数,但在 PHP 中,可以使用参数列表进行处理,代码如下:

<?php

// 函数的可变参数列表
function sum(...$num)
{
    $sum = 0;
    foreach($num as $val) {
        $sum += $val;
    }
    return $sum;
}
$sum = sum(1, 2, 3, 4, 5);
echo $sum;// 输出:15

也许有细心的朋友发现,在函数参数前有...,这就是 PHP 5.6 版本以后函数的可变数量的参数列表的标识。它将用户传入的参数组成一个数组,而函数的形参名就是这个数组的名称(如上方代码的$num),无论用户传入多少参数,函数都可以接收。这也体现了PHP弱类型语言的灵活性

可变数量参数列表像带默认值的参数一样,需要放在必须会使用到的参数后面,下列代码很好的展示了三种参数(不带默认值的参数、带默认值的参数和可变数量参数列表)的传入优先级:

<?php

// 传入参数优先级
function priority($first, $second = '极速教程', string ...$params)
{
    echo '第一个参数:',$first,"\n";
    echo '第二个参数:',$second,"\n";
    foreach ($params as $value) {
        echo $value,' ';
    }
    echo PHP_EOL;
}
priority('我们是极速教程','拥有','HTML', 'PHP','JavaScript','Python');
/* 输出:
第一个参数:我们是极速教程
第二个参数:拥有
HTML PHP JavaScript Python
*/

priority('这次只传入一个参数');
/* 输出:
第一个参数:这次只传入一个参数
第二个参数:极速教程
*/

返回值

在前面的几个示例中,我们有用到return关键字,在上一章流程控制中的中止语句一节中,我们也详细讲述了return的用法。在这里特别说明在函数中return的用法。

return在函数中表明返回一个值,它可以返回任意类型,包括数组和对象。像上一节所说的,它会立即结束函数的运行。例如下面的代码:

<?php

// 函数的返回值

function demo()
{
    echo '这是一个示例代码';
    return '返回一个值并结束了这个函数的运行,下面的代码不会被执行';
    echo '不会被执行的被无视的我';
}
$val = demo();// 输出:这是一个示例代码

var_dump($val);// 输出:string(81) "返回一个值并结束了这个函数的运行,下面的代码不会被执行"

类型声明类似,函数的返回值也可以指定返回类型啦!这一功能由 PHP 7 开始支持,故还是建议始终使用最新稳定版本的PHP进行学习与开发。

其语法如下:

function funcName(): dataType
{
    // 代码块
    return $returnValue;
}

其中dataType为指定的返回类型,返回值的可声明合法类型与函数的参数类型声明一致(可指定的合法类型声明有:类名/接口名、selfarraycallableboolfloatintstring)。具体示例如下:

<?php

function demo2($arg): string
{
    return $arg;
}
var_dump(demo2('返回了一个字符串类型'));// 输出:返回了一个字符串类型
var_dump(demo2(666));// 输出:666
// echo demo2(['一个数组']);// 错误:Fatal error:  Uncaught TypeError: Return value of demo2() must be of the type string
var_dump(demo2(NULL));// 错误:Fatal error:  Uncaught TypeError: Return value of demo2() must be of the type string

PHP 7.1 提供了一个新的功能,可以使return不仅可以返回指定类型,还可以返回NULL,只需要在返回类型声明前加上一个问号?,修改上面的代码,再看看输出结果。

可变函数

可变函数不同于“可变参数列表”,它的意思是:一个值为字符串类型的变量在加上一对圆括号后,PHP 将自动查找与其值相同的函数,并试图执行该函数。例如:

<?php

// 可变函数

function demo()
{
    echo '我是一个名为 demo 的函数',"\n";
}

$val = 'demo';
$val();// 输出:我是一个名为 demo 的函数

学习过Javascript的朋友可能会对这个感到一丝熟悉,它经常用于回调函数、函数表等用途。这个我们暂作了解,不做深入探讨。

匿名函数

关于匿名函数,了解JavaScript的朋友应该很了解它。匿名函数允许临时创建一个没有名称的函数,也叫闭包函数。

在 PHP 中稍有不同,匿名函数是通过类Closure来实现的,每个匿名函数都会被自动转换为内置类Closure的实例。Closure是一个什么样的类我们暂时不做了解,先来看看匿名函数的使用:

<?php

// 匿名函数

$anonymous = function() {
    echo '我是一个不配拥有姓名的函数';
};
$anonymous();// 输出:我是一个不配拥有姓名的函数

由示例代码可以看出,匿名函数可以作为一个值传递给变量,其声明与函数声明类似,只是不需要指定函数名。

use变量继承

和普通函数一样,匿名函数内的变量都属于局部变量,当在匿名函数体内访问函数之外的变量将会出错。例如:

<?php

$msg = '你的宝宝突然出现';
$tip = function () {
    echo $msg;
};
$tip();// 错误:Notice: Undefined variable: msg

在匿名函数内,想要访问函数外部的变量(父级作用域的变量),应该使用use关键字进行继承。上面的代码正确的做法应是:

<?php

// 使用 use 传递父作用域中的变量
$msg = '你的宝宝突然出现';
$tip = function() use ($msg) {
    echo $msg;
};
$tip();// 输出:你的宝宝突然出现

从 PHP 7.1 开始,匿名函数不允许传入超全局变量$this或者与参数同名的变量。

变量与常量章节中,我们知道关键字global可以访问函数之外的变量,那么它和use有什么区别呢?请看下面的代码:

<?php

$attr = '在类外部的一个变量';
class anonymous
{
    public $attr = '在函数外部的一个变量';

    public function save1()
    {
        $crypt1 = function() use ($attr) {
            return $attr;// 出错:Notice: Undefined variable: attr
        };
        $crypt2 = function() {
            global $attr;
            return $attr;
        };

        // file_put_contents()是一个文件写入函数
        file_put_contents(__DIR__ . '/msg1-1.txt', $crypt1());
        file_put_contents(__DIR__ . '/msg1-2.txt', $crypt2());

        // file_get_contents()是一个文件读取函数
        $res1 = file_get_contents(__DIR__ . '/msg1-1.txt');
        $res2 = file_get_contents(__DIR__ . '/msg1-2.txt');

        echo $res1 , '_', $res2;
    }

    public function save2()
    {
        $attr = '在函数内部的一个变量';
        $crypt1 = function() use ($attr) {
            return $attr;
        };
        $crypt2 = function() {
            global $attr;
            return $attr;
        };

        file_put_contents(__DIR__ . '/msg2-1.txt', $crypt1());
        file_put_contents(__DIR__ . '/msg2-2.txt', $crypt2());

        $res1 = file_get_contents(__DIR__ . '/msg2-1.txt');// 输出:在函数内部的一个变量
        $res2 = file_get_contents(__DIR__ . '/msg2-2.txt');// 输出:在类外部的一个变量

        echo $res1 , '-', $res2;
    }
}
$anonymous = new anonymous();
$anonymous->save1();// 输出:_在类外部的一个变量
echo PHP_EOL;
$anonymous->save2();// 输出:在函数内部的一个变量-在类外部的一个变量

// 运行时请注释掉出错的部分

结果表明: global关键字无法访问类属性,只能访问类和函数外部的变量use关键字只能访问父级作用域(即最靠近匿名函数定义的那一层作用域)的变量。

内置函数

函数是 PHP 的重要组成部分,在 PHP 中有许多标准的函数和结构供开发者使用,例如前面的示例代码涉及的file_put_contents()函数,根据名称也可知道它是一个从文件读取内容的函数,类似的函数还有很多,我们不用每个都学习,在之后的教程中也会对常用的几类函数进行实例说明。

在之前的介绍部分我们知道 PHP 拥有许多扩展库,部分扩展库也拥有自己的函数。如果想使用这部分函数,必须保证该扩展是开启的,扩展可通过修改配置文件开启或者关闭。


学习完函数的部分,也意味着我们已经了解了大部分关于 PHP 的知识,但还缺少一个能够将前面所学的内容系统地归纳起来的部分,这部分就是下一章的内容:类和对象。从类和对象开始,将涉及大段代码,请准备好代码编辑器或 IDE,限于篇幅,后续章节将可能不会完整地注释出代码的运行结果。