红茶的个人站点

  • 首页
  • 专栏
  • 开发工具
  • 其它
  • 隐私政策
Awalon
Talk is cheap,show me the code.
  1. 首页
  2. 编程语言
  3. PHP
  4. 正文

PHP学习笔记8:函数

2021年12月5日 1104点热度 0人点赞 0条评论

image-20211129162010327

图源:php.net

定义

php的函数可以定义在任何地方,甚至是条件语句中:

function create_func(int $num){
    if ($num<10){
        function test(){
            echo "test function is called".PHP_EOL;
            echo "the \$num < 10";
        }
    }
    else{
        function test(){
            echo "test function is called".PHP_EOL;
            echo "the \$num >= 10";
        }
    }
}
create_func(10);
test();
// test function is called
// the $num >= 10

这个示例代码中create_func函数会根据传入参数的不同定义两个不同的函数,虽然这两个函数名称相同。

需要注意的是,php中函数一旦定义,其在全局作用域中都会生效,即可以在任何地方调用这个函数。

此外,在php中,是不能重复定义同名函数的,所以再次调用create_func函数会产生一个错误:

create_func(10);
// Fatal error: Cannot redeclare test() (previously declared in ...

参数

php8.0.0之后,函数的参数列表中,最后一个参数之后可以使用一个额外的,,这样做有助于编写长参数列表的函数:

function multi_params(
    $param1,
    $param2,
    $param3,
) {; //do something
}

引用传递

默认情况下,函数传参都是值传递:

<?php
function double_num(int $num)
{
    $num = $num * 2;
}
$a = 2;
double_num($a);
echo $a . PHP_EOL;
// 2

要修改值需要使用引用传递参数:

<?php
function double_num(int &$num)
{
    $num = $num * 2;
}
$a = 2;
double_num($a);
echo $a . PHP_EOL;
// 4

当然,传递对象时是引用传递,这点依然不会改变:

<?php
class Student
{
    public $name = "";
    public $age = 0;
}
$std = new Student;
function change_student_name(Student $std, string $name)
{
    $std->name = $name;
}
change_student_name($std, "Jack Chen");
echo "{$std->name}" . PHP_EOL;
// Jack Chen

默认值

可以给参数指定默认值,默认值可以是常量、基础类型、数组、null等,但不能是变量或者函数调用:

function default_params($param1 = 1, $param2 = 2)
{; //do something
}
default_params();

引用类型的参数也可以指定默认值:

<?php
class Student{
​
}
function default_params(?Student $student = null){
    ;//do something
}
default_params();

使用参数默认值的时候需要注意,要将有默认值的参数放在参数列表的尾部:

function add_two_str(string $str1, string $str2 = ',second str is empty.'): string
{
    return $str1 . $str2;
}
echo add_two_str("hello", "world") . PHP_EOL;
echo add_two_str("hello") . PHP_EOL;
// helloworld
// hello,second str is empty.

否则的话会报错:

<?php
function add_two_str(string $str1 = 'first param is empty,', string $str2): string
{
    return $str1 . $str2;
}
echo add_two_str("hello", "world") . PHP_EOL;
echo add_two_str("hello") . PHP_EOL;
// helloworld
// Fatal error: Uncaught ArgumentCountError: Too few arguments to function add_two_str(), 1 passed in ...

变长参数

参数列表中可以定义变长参数:

function print_all(...$params)
{
    foreach ($params as $val) {
        echo "{$val} ";
    }
    echo PHP_EOL;
}
print_all(1, "2.5", 666);
// 1 2.5 666 

函数中的变长参数$params可以当做一个数组来进行遍历和使用。

变长参数可以和位置参数同时使用,不过变长参数只能放在参数列表的最后:

function print_all($first, ...$params)
{
    echo "first param:{$first}" . PHP_EOL;
    echo "other params:";
    foreach ($params as $val) {
        echo "{$val} ";
    }
    echo PHP_EOL;
}
print_all(1, "2.5", 666);
// first param:1
// other params:2.5 666 

可以将一个数组的元素当做多个参数传递给函数:

<?php
function print_all(...$params)
{
    foreach ($params as $val) {
        echo "{$val} ";
    }
    echo PHP_EOL;
}
$arr = range(1, 20, 2);
print_all(...$arr);
// 1 3 5 7 9 11 13 15 17 19 

可以通过类型声明指定变长参数中参数的类型:

function print_all(int|float ...$params)
{
    ...
}
print_all(1, "2.5", 666, "hello");
// Fatal error: Uncaught TypeError: print_all(): Argument #4 must be of type int|float, string given ...

变长参数也可以使用引用传参:

<?php
function double_all(int &...$params)
{
    foreach ($params as &$val) {
        $val = $val * 2;
    }
}
$a = 1;
$b = 3;
$c = 5;
double_all($a, $b, $c);
echo "{$a} {$b} {$c}";
// 2 6 10

指名参数

php8.0.0新增了指名参数,通过指名参数可以无视参数顺序进行传参:

<?php
function my_map(callable $func, iterable &$iter)
{
    foreach ($iter as &$val) {
        $func($val);
    }
    unset($val);
}
function my_double(int &$num)
{
    $num = $num * 2;
}
$arr = range(1, 10);
my_map(iter: $arr, func: "my_double");
require_once "../util/array.php";
print_arr($arr);
// [0:2, 1:4, 2:6, 3:8, 4:10, 5:12, 6:14, 7:16, 8:18, 9:20]

事实上这种传参方式在Python中使用的相当广泛。

可以同时使用“位置传参”和“指名传参”,此时指名传参应当放在位置传参之后:

function named_params($param1, $param2, $param3)
{
    echo "param1:{$param1}" . PHP_EOL;
    echo "param2:{$param2}" . PHP_EOL;
    echo "param3:{$param3}" . PHP_EOL;
}
named_params(1, param3: 3, param2: 2);
// param1:1
// param2:2
// param3:3

返回值

在php中,缺省return语句时,实际上函数会返回null:

<?php
function no_return()
{
    $a = 1;
}
var_dump(no_return());

这和C++之类的语言是截然不同的,后者会严格区分void和null返回值的情况。

php和大多数语言一样,函数只能有一个返回值,不过可以通过返回一个数组或对象的方式返回多个数据:

<?php
function multi_return()
{
    return [1, 2, 3];
}
[$first, $second, $third] = multi_return();
echo "{$first},{$second},{$third}" . PHP_EOL;
// 1,2,3

这里的[$first, $second, $third] = multi_return();是一种短数组语法,在php 7.1.0之后可用,等价于list($first, $second, $third) = multi_return();。

php的函数可以返回一个引用:

<?php
require_once "../util/array.php";
function &return_reference(array &$arr): array
{
    foreach ($arr as &$val) {
        $val = 2 * $val;
    }
    unset($val);
    return $arr;
}
$arr = range(1, 10);
$arr2 = &return_reference($arr);
$arr3 = &return_reference($arr2);
print_arr($arr);
// [0:4, 1:8, 2:12, 3:16, 4:20, 5:24, 6:28, 7:32, 8:36, 9:40]

需要注意的是,如果要接收返回值的引用,需要$arr2 = &return_reference($arr);,而不是$arr2 = return_reference($arr);。

这种写法看起来的确颇为怪异,不过需要返回引用的情况应该很少。

变量函数

php官方手册将“Variable Functions”翻译为“可变函数”,但是我觉得不太合适。可变函数很容易让人认为是通过反射等底层编程来修改函数本身,但实际上“Variable Functions”仅是一种使用变量名来调用函数的方式,所以我把它叫做“变量函数”。

在PHP学习笔记3:其它类型和类型声明中介绍了Callable伪类型,我们可以将函数参数指定为Callable类型,并直接执行,此外还展示了如何将各种函数传递。

实际上Callable类型就是变量函数,变量函数可以代表一个Callable,并使用()执行相应的函数:

function my_func(){
    echo "my_func is called";
}
$func_name = "my_func";
$func_name();
// my_func is called

还可以调用对象方法:

<?php
class MyClass{
    public function print(){
        echo "MyClass->print() is called".PHP_EOL;
    } 
}
$myclass = new MyClass;
$myclass_print = array($myclass,"print");
$myclass_print();
// MyClass->print() is called

还可以调用类方法:

class MyClass{
    public static function print(){
        echo "MyClass::print() is called".PHP_EOL;
    }
}
$myclass_print = array("MyClass", "print");
$myclass_print();
// MyClass::print() is called

官方手册并没有说变量函数的运行机制,我猜测php 解释器会根据变量函数中的字符串或数组结合当前加载的函数和类来确定对应的函数或方法,然后执行。

如果你使用过Python或Go就会觉得这种方式相当的繁琐和“笨拙”,我猜测这是因为php的原始语法和函数式编程不兼容导致的,毕竟在其它语言中函数名可以作为一个callable对象来传递和调用,但php会将其解析为一个未定义常量,所以最后只能用这种别扭的方式来“曲线救国”。

匿名函数

php匿名函数与其它函数式编程的语法很像:

$nonNamedFunc = function () {
    echo "111" . PHP_EOL;
};
$nonNamedFunc();
// 111

但其实有着本质的不同,因为上边已经说过了,php的原始语法和函数式编程是不相容的,所以实质上匿名函数也是一种看上去很像,但本质上完全不同的东西:

var_dump($nonNamedFunc);
// object(Closure)#1 (0) {
// }

匿名函数实际上是一个Closure(闭包)类的实例,我们实际上是在传递和使用Closure实例,而非真正的函数,这依然是一种“曲线救国”。

当然我们在使用的时候不用考虑太多,匿名函数在使用上还是很像其它语言中的写法的。

比如可以在调用函数时在传参列表中定义:

<?php
function my_call_func(callable $func)
{
    $func();
}
my_call_func(function () {
    echo "non named func is called.";
});
// non named func is called.

也可以直接返回一个匿名函数:

<?php
function get_increase_func($step)
{
    return function ($num) use ($step) {
        return $num + $step;
    };
}
$increase_func = get_increase_func(3);
echo $increase_func(6) . PHP_EOL;
// 9

不过也不是全然一样,比如不支持定义后调用:

<?php
function (){
    echo "non named func is called".PHP_EOL;
}();
// Parse error: syntax error, unexpected token "(" in ...

匿名函数作为闭包,最大的用处是可以直接使用外部作用域的变量:

<?php
$a = 100;
$non_name_func = function() use ($a){
    echo "\$a is {$a}".PHP_EOL;
};
$non_name_func();
// $a is 100

只不过需要将使用的外部变量写在use语句后的列表中。

如果要修改外部变量,可以:

<?php
$a = 100;
$non_name_func = function () use (&$a) {
    $a = $a * 2;
};
$non_name_func();
echo "\$a is {$a}" . PHP_EOL;
// $a is 200

比较特别的是,如果在类方法中定义匿名函数,将会自动绑定$this变量到闭包:

<?php
class Student
{
    public $name;
    public $age;
    public function get_printer(): callable
    {
        return function () {
            echo "Student(name:{$this->name}, age:{$this->age})" . PHP_EOL;
        };
    }
}
$std = new Student;
$std->name = "Jack Chen";
$std->age = 20;
$printer = $std->get_printer();
$printer();
// Student(name:Jack Chen, age:20)

静态匿名函数

可以使用static将匿名函数声明为静态:

class Student
{
    public $name;
    public $age;
    public function get_printer(): callable
    {
        return static function () {
            echo "Student(name:{$this->name}, age:{$this->age})" . PHP_EOL;
            // Fatal error: Uncaught Error: Using $this when not in object context in ... 
        };
    }
}

此时匿名函数就不能绑定到对象,自然也无法使用$this。

箭头函数

箭头韩式是php 7.4引进的新特性,可以看做是简洁版的匿名函数:

$add_func = fn (int $a, int $b): int => $a + $b;
echo $add_func(1, 2) . PHP_EOL;
// 3

他们本质上是相同的,都是闭包类的实例。比较特别的是,箭头函数会自动绑定外部作用域变量,所以不需要使用use语句:

<?php
$a = 100;
$increase_func = fn (int $sep): int => $a + $sep;
echo $increase_func(3) . PHP_EOL;
// 103

虽然这一点使用起来比匿名函数方便,但是有个缺陷——无法修改外部变量。如果需要在匿名函数中修改外部变量,只能使用传统的匿名函数。

谢谢阅读。

往期内容

  • PHP学习笔记7:控制流

  • PHP学习笔记6:表达式和运算符

  • PHP学习笔记5:常量

  • PHP学习笔记4:变量

  • PHP学习笔记3:其它类型和类型声明

  • PHP学习笔记2:数组

  • PHP学习笔记1:基础

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: 暂无
最后更新:2021年12月5日

魔芋红茶

加一点PHP,加一点Go,加一点Python......

点赞
< 上一篇
下一篇 >

文章评论

取消回复

*

code

COPYRIGHT © 2021 icexmoon.cn. ALL RIGHTS RESERVED.
本网站由提供CDN加速/云存储服务

Theme Kratos Made By Seaton Jiang

宁ICP备2021001508号

宁公网安备64040202000141号