函数
之前的章节我们了解了 PHP 的基础语法、变量与常量、数据类型、运算符和流程控制等基础知识,仅仅是这些我们并不算入门,本章我们将讲述 PHP 入门的“最后一脚”——函数。
不论是哪门编程语言,函数都占据着非常重要的地位。在一些编程语言(例如 JavaScript )中,函数甚至与其他数据类型地位一致。虽然 PHP 并非如此,但从其内置的大量函数来看,函数的重要性不言而喻。
我们先来了解函数的组成部分!
组成部分
在 PHP 中,除了内置函数,用户自定义的函数以关键字function
标明,后跟函数名和用圆括号括起来的可选参数列表,最后是花括号括起来的函数体,形如:
function funcName($arg1, $arg2, $arg3)
{
// 任何有效的PHP代码
}
解析如下:
function
是函数声明关键字funcName
为函数名,函数名必须以下划线或者字母开头,后跟字母或者数字或者下划线- 请始终不要用两个下划线开头,这通常是 PHP 保留的魔术方法命名
- 避免函数名与 PHP 结构名(如
echo
、print
、return
等)重名,会导致解析错误
($arg1, $arg2, $arg3)
是参数列表,使用小括号括起来,参数列表可选
类型声明
虽然 PHP 是一门弱类型语言,但是函数的参数可以指定部分数据类型,这也被称为类型提示。
可指定的合法类型声明有:类名/接口名、self
(表示函数本身所在的类)、array
、callable
、bool
、float
、int
和string
。
如果传入的参数数据类型不符合,则会造成类型异常。例如:
<?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
为指定的返回类型,返回值的可声明合法类型与函数的参数类型声明一致(可指定的合法类型声明有:类名/接口名、self
、array
、callable
、bool
、float
、int
和string
)。具体示例如下:
<?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,限于篇幅,后续章节将可能不会完整地注释出代码的运行结果。