1. 程式人生 > >搞定PHP面試

搞定PHP面試

一、函式的定義

1. 函式的命名規則

函式名可以包含字母、數字、下劃線,不能以數字開頭。

function Func_1(){ } //合法
function func1(){ } //合法
function _func1(){ } //合法
function Func-1(){ } // 非法,不能包含 '-'
function 1_func(){ }// 非法,不能以數字開頭
在此所說的字母是 a-z,A-Z,以及 ASCII 字元從 127 到 255(0x7f-0xff)。因此實際上使用中文變數名也是合法的。甚至使用中文的標點符號作為變數名都是合法的。只是一般都不推薦這樣用。
// 使用中文函式名和變數名
function 面積($長, $寬){ 
    return $長 * $寬;
}

echo 面積(2, 3); // 合法,輸出 '6'

// 中文符號函式名
function ?。……(){ 
    return '中文符號';
}

echo ?。……(); // 合法,輸出 '中文符號'

函式名不區分大小寫

function Func(){ 
    return 'Func';
}

echo func(); // 輸出 'Func'

函式名不區分大小寫,不過在呼叫函式的時候,使用其在定義時相同的形式是個好習慣。

2. 函式的特性

任何有效的 PHP 程式碼都有可能出現在函式內部,甚至包括其它函式和類定義。

函式中包含其他函式

function foo()
{
  function bar()
  {
    echo "I don't exist until foo() is called.";
  }
}

/* 現在還不能呼叫bar()函式,因為它還不存在 */
foo();

/* 現在可以呼叫bar()函數了,因為foo()函式的執行使得bar()函式變為已定義的函式 */
bar(); // 輸出 'I don't exist until foo() is called.'

函式中包含類

function foo()
{
  class Bar{
      public $a = 1;
  }
}

/* 現在還不能例項化 Bar 類,因為它還不存在 */
foo();

$bar = new Bar();

echo $bar->a; // 輸出 '1'

PHP 中的所有函式和類都具有全域性作用域,可以定義在一個函式之內而在之外呼叫,反之亦然。

示例見上面兩個例子

函式無需在呼叫之前被定義,但是必須保證函式定義的程式碼能夠被執行到

foo(); // 輸出 'foo'

function foo()
{
  echo 'foo';
}

定義 foo()` 函式的程式碼不會被執行

foo(); // PHP Fatal error:  Uncaught Error: Call to undefined function foo()

if (false) {
    function foo()
    {
      echo 'foo';
    }
}

PHP 不支援函式過載,也不可能取消定義或者重定義已宣告的函式。

function foo(){
    echo 0;
}

function foo() {
    echo 1;
} 
// PHP Fatal error:  Cannot redeclare foo() (previously declared

PHP 中可以呼叫遞迴函式

function recursion($a)
{
    if ($a <= 5) {
        echo "$a\n";
        recursion($a + 1);
    }
}
echo recursion(0);

輸出

0
1
2
3
4
5
要避免遞迴函式/方法呼叫超過 100-200 層,因為可能會使堆疊崩潰從而使當前指令碼終止。 無限遞迴可視為程式設計錯誤。

遞迴次數過多

function recursion($a)
{
    if ($a <= 300) {
        echo "$a\n";
        recursion($a + 1);
    }
}
echo recursion(0);
// PHP Fatal error:  Uncaught Error: Maximum function nesting level of '256' reached, aborting! 

PHP 的函式支援引數預設值

function square($num = 0)
{
    echo $num * $num;
}
echo square(3), "\n";
echo square(), "\n";

輸出

9
0

PHP 的函式支援可變數量的引數(PHP 5.6+)

function sum(...$numbers) {
    $acc = 0;
    foreach ($numbers as $n) {
        $acc += $n;
    }
    return $acc;
}

echo sum(1);    // 輸出 1
echo sum(1, 2);    // 輸出 3
echo sum(1, 2, 3); // 輸出6

二、函式的引數

通過引數列表可以傳遞資訊到函式,多個引數之間以逗號作為分隔符。引數是從左向右求值的。

PHP 支援通過值傳遞引數(預設),通過引用傳遞引數以及引數預設值。也支援可變長度的引數列表。

1. 通過值傳遞引數(預設)

預設情況下,函式引數通過值傳遞,即使在函式內部改變引數的值,它並不會改變函式外部的值。

function addition($num)
{
    $num++;
}

$num = 1;
addition($num); 

echo $num; // 輸出 1

2. 通過引用傳遞引數

如果希望函式可以修改傳入的引數值,必須通過引用傳遞引數。

如果希望函式的一個引數通過引用傳遞,可以在定義函式時該引數的前面加上符號 &

function addition(& $num)
{
    $num++;
}

$num = 1;
addition($num); 

echo $num; // 輸出 2
使用引用傳遞引數,呼叫函式是隻能傳變數,不能直接傳值。
function addition(& $num)
{
    $num++;
}

addition(1); // PHP Fatal error:  Only variables can be passed by reference 

3. 引數預設值

函式可以定義 C++ 風格的標量引數預設值

function makecoffee($type = "cappuccino")
{
    return "Making a cup of $type.\n";
}
echo makecoffee(); // Making a cup of cappuccino.
echo makecoffee(null); // Making a cup of .
echo makecoffee("espresso"); // Making a cup of espresso.

PHP 還允許使用陣列 array 和特殊型別 NULL 作為預設引數

function makecoffee($types = ["cappuccino"], $coffeeMaker = NULL)
{
    $device = is_null($coffeeMaker) ? "hands" : $coffeeMaker;
    return "Making a cup of ".join(", ", $types)." with $device.\n";
}
echo makecoffee(); // Making a cup of cappuccino with hands.
echo makecoffee(["cappuccino", "lavazza"], "teapot"); // Making a cup of cappuccino, lavazza with teapot.

預設值必須是常量表達式,不能是諸如變數,類成員,或者函式呼叫等。

預設引數必須放在任何非預設引數的右側;否則,函式將不會按照預期的情況工作。

function makecoffee($type = "cappuccino", $coffeeMaker)
{
    return "Making a cup of {$type} with {$coffeeMaker}.";
}

echo makecoffee(null, 'Jack'); // Making a cup of  with Jack.

echo makecoffee('Jack'); // PHP Fatal error:  Uncaught ArgumentCountError: Too few arguments to function makecoffee(), 1 passed in XX and exactly 2 expected in XX

4. 型別宣告

型別宣告允許函式在呼叫時要求引數為特定型別。 如果給出的值型別不對,那麼將會產生一個錯誤: 在PHP 5中,這將是一個可恢復的致命錯誤,而在PHP 7中將會丟擲一個TypeError異常。

為了指定一個型別宣告,型別應該加到引數名前。這個宣告可以通過將引數的預設值設為NULL來實現允許傳遞NULL。

有效的型別宣告

型別 描述 最小PHP版本
Class/interface name 該引數必須是給定類或介面的例項(instanceof)。 PHP 5.0.0
self 引數必須是當前方法所在類的例項(instanceof)。只能在類和例項方法上使用。 PHP 5.0.0
array 引數必須是陣列(array) PHP 5.1.0
callable PHP 5.4.0
bool 引數必須是布林值 PHP 7.0.0
float 引數必須是浮點數 PHP 7.0.0
int 引數必須是整數 PHP 7.0.0
string 引數必須是字串 PHP 7.0.0

5. 嚴格型別

預設情況下,如果能做到的話,PHP將會強迫錯誤型別的值轉為函式期望的標量型別。例如,一個函式的一個引數期望是string,但傳入的是integer,最終函式得到的將會是一個string型別的值。

function toString(string $var)
{
    return $var;
}

var_dump(toString(1)); // string(1) "1"

可以基於每一個檔案開啟嚴格模式。在嚴格模式中,只有一個與型別宣告完全相符的變數才會被接受,否則將會丟擲一個TypeError。 唯一的一個例外是可以將integer傳給一個期望float的函式。

可以使用 declare 語句和strict_types 宣告來啟用嚴格模式:啟用嚴格模式同時也會影響返回值型別宣告。

declare(strict_types=1);

function toString(string $var)
{
    return $var;
}

toString(1); // PHP Fatal error:  Uncaught TypeError: Argument 1 passed to toString() must be of the type string, integer given

將integer傳給一個期望float的函式

declare(strict_types=1);

function toFloat(float $var)
{
    return $var;
}

$int = 1;
var_dump($int); // int(1)
var_dump(toFloat($int)); // double(1)
嚴格型別適用於在啟用嚴格模式的檔案內的函式呼叫,而不是在那個檔案內宣告的函式。一個沒有啟用嚴格模式的檔案內呼叫了一個在啟用嚴格模式的檔案中定義的函式,那麼將會遵循呼叫者的偏好(弱型別),而這個值將會被轉換。

嚴格型別僅用於標量型別宣告,也正是因為如此,才需要PHP 7.0.0 或更新版本,因為標量型別宣告也是在這個版本中新增的。

6. 可變數量的引數列表(PHP 5.5+)

PHP 在使用者自定義函式中支援可變數量的引數列表。在 PHP 5.6 及以上的版本中,由 ... 語法實現;在 PHP 5.5 及更早版本中,使用函式 func_num_args()func_get_arg(),和 func_get_args() 實現。

... (in PHP 5.6+)

function sum(...$numbers) {
    $acc = 0;
    foreach ($numbers as $n) {
        $acc += $n;
    }
    return $acc;
}

echo sum(1);    // 輸出 1
echo sum(1, 2);    // 輸出 3
echo sum(1, 2, 3); // 輸出6

func_num_args()func_get_arg()func_get_args()(PHP 5.5)

function sum() {
    $acc = 0;
    foreach (func_get_args() as $n) {
        $acc += $n;
    }
    return $acc;
}

echo sum(1);    // 輸出 1
echo sum(1, 2);    // 輸出 3
echo sum(1, 2, 3); // 輸出6

三、函式返回值

1. 函式返回值的特性

值通過使用可選的返回語句(return)返回

可以返回包括陣列和物件的任意型別。

返回語句會立即中止函式的執行,並且將控制權交回呼叫該函式的程式碼行。

函式不能返回多個值,但可以通過返回一個數組來得到類似的效果。

如果省略了 return,則返回值為 NULL。

function sum($a, $b) {
    $a + $b;
}

var_dump(sum(1, 2)); // NULL

2. return語句

如果在一個函式中呼叫 return 語句,將立即結束此函式的執行並將它的引數作為函式的值返回。return 也會終止 eval() 語句或者指令碼檔案的執行。

$expression = 'echo "我在return之前"; return; echo "我在return之後";';

eval($expression); // 輸出“我在return之前”

如果在全域性範圍中呼叫 return,則當前指令碼檔案中止執行。如果在主指令碼檔案中呼叫 return,則指令碼中止執行。如果當前指令碼檔案是被 include 或者 require 的,則控制交回呼叫檔案。此外,return 的值會被當作 include 或者 require 呼叫的返回值。

a.php

<?php
$b = require 'b.php';
$c = include 'c.php';

echo $b;
echo $c;

return;
echo '我在a.php return之後';

b.php

<?php
return "我是b.php\n";
echo '我在b.php return之後';

c.php

<?php
return "我是c.php\n";
echo '我在c.php return之後';

執行 a.php,輸出結果為:

我是b.php
我是c.php
Note: 注意既然 return 是語言結構而不是函式,因此其引數沒有必要用括號將其括起來。通常都不用括號,實際上也應該不用,這樣可以降低 PHP 的負擔。

Note: 如果沒有提供引數,則一定不能用括號,此時返回 NULL。如果呼叫 return 時加上了括號卻又沒有引數會導致解析錯誤。

Note: 當用引用返回值時永遠不要使用括號,這樣行不通。只能通過引用返回變數,而不是語句的結果。如果使用 return ($a); 時其實不是返回一個變數,而是表示式 ($a) 的值(當然,此時該值也正是 $a 的值)。

3. 函式的引用返回

從函式返回一個引用,必須在函式宣告和指派返回值給一個變數時,都使用引用運算子 &

function &myFunc()
{
    static $b = 10;
    return $b;
}

$a = myFunc();
$a = 100;
echo myFunc(); // 10;

$a = &myFunc();
$a = 100;
echo myFunc(); // 100;

四、可變函式

PHP 支援可變函式的概念。這意味著如果一個變數名後有圓括號,PHP 將尋找與變數的值同名的函式,並且嘗試執行它。可變函式可以用來實現包括回撥函式,函式表在內的一些用途。

可變函式不能用於例如 echo,print,unset(),isset(),empty(),include,require 以及類似的語言結構。需要使用自己的包裝函式來將這些結構用作可變函式。

function foo() {
    return "I'm foo()\n";
}

// 使用 echo 的包裝函式
function echoit($string)
{
    echo $string;
}

$func = 'foo';
echo $func();   // This calls foo(),輸出“I'm foo()”

$func = 'echoit';
$func($func);  // This calls echoit(),輸出“echoit”


$func = 'echo';

echo($func); // 輸出“echo”

$func($func); // PHP Fatal error:  Uncaught Error: Call to undefined function echo()

可以用可變函式的語法來呼叫一個物件的方法

class Foo
{
    function variable()
    {
        $name = 'bar';
        $this->$name(); // This calls the Bar() method
    }

    function bar()
    {
        echo "This is Bar";
    }
}

$foo = new Foo();
$funcName = "variable";
$foo->$funcName();   // This calls $foo->variable(),輸出“This is Bar”

當呼叫靜態方法時,函式呼叫要比靜態屬性優先

class Foo
{
    static $variable = 'static property';
    static function variable()
    {
        echo 'Method Variable called';
    }
}

echo Foo::$variable; // 輸出 'static property'.
$variable = "variable";
Foo::$variable();  // 呼叫 $foo->variable(),輸出“Method Variable called”

可以呼叫儲存在標量內的 callable(PHP 5.4+)

class Foo
{
    static function bar()
    {
        echo "bar\n";
    }

    function baz()
    {
        echo "baz\n";
    }
}

$func = ["Foo", "bar"];
$func(); // 輸出 "bar"

$func = [new Foo, "baz"];
$func(); // 輸出 "baz"

$func = "Foo::bar";
$func(); // 自 PHP 7.0.0 版本起會輸出 "bar"; 在此之前的版本會引發致命錯誤

五、匿名函式

1. 匿名函式的特性

匿名函式(Anonymous functions),也叫閉包函式(closures),允許 臨時建立一個沒有指定名稱的函式。最經常用作回撥函式(callback)引數的值。

匿名函式目前是通過 Closure 類來實現的。

閉包函式也可以作為變數的值來使用。

PHP 會自動把此種表示式轉換成內建類 Closure 的物件例項。把一個 closure 物件賦值給一個變數的方式與普通變數賦值的語法是一樣的,最後也要加上分號。

$greet = function($name)
{
    printf("Hello %s \n", $name);
};

$greet('World'); // Hello World
$greet('PHP'); // Hello PHP

閉包可以從父作用域中繼承變數。

任何此類變數都應該用 use 語言結構傳遞進去。 PHP 7.1 起,不能傳入此類變數: superglobals、 $this 或者和引數重名。

$message = 'hello';

// 沒有 "use"
$example = function () {
    var_dump($message);
};
echo $example(); // PHP Notice:  Undefined variable: message

// 繼承 $message
$example = function () use ($message) {
    var_dump($message);
};
echo $example(); // string(5) "hello"

// 繼承的變數的值來自於函式定義時,而不是呼叫時
$message = 'world';
echo $example(); // string(5) "hello"

通過引用繼承父作用域中的變數,可以將父作用域更改的值反映在函式呼叫中

$message = 'hello';

// 通過引用繼承
$example = function () use (&$message) {
    var_dump($message);
};
echo $example(); // string(5) "hello"

// 父作用域更改的值反映在函式呼叫中
$message = 'world';
echo $example(); // string(5) "world"

閉包函式也可以接受常規引數

$message = 'world';

$example = function ($arg) use ($message) {
    var_dump($arg . ' ' . $message);
};
$example("hello"); // string(11) "hello world"

六、例項分析

例1

//宣告函式swap,作為下面匿名函式的回撥函式
function swap(&$x, &$y)
{
    $temp = $x;
    $x = $y;
    $y = $temp;

    return;
}

//call_user_func呼叫的回撥函式
function add($a, $b)
{
    return $a + $b;
}

//匿名函式,即不宣告函式名稱而使用一個變數來代替函式宣告
$fuc = function ($fucName) {
    $x = 1;
    $y = 2;
    //呼叫回撥函式
    $fucName($x, $y);
    echo 'x=' . $x . ',y=' . $y, "\n";

    //與$fucName($x, $y)相同效果
    //這裡無法呼叫swap方法,因為swap方法是對引數引用傳值
    //call_user_func與call_user_func_array都無法呼叫引用傳參形式的函式
    echo call_user_func('add', $x ,$y);
};

//呼叫 $fuc
$fuc('swap');

輸出:

x=2,y=1
3

例2

$var1 = 5;
$var2 = 10;

function foo(&$my_var)
{
    global $var1;
    $var1 += 2;
    $var2 = 4;
    $my_var += 3;
    return $var2;
}

$my_var = 5;
echo foo($my_var). "\n";
echo $my_var. "\n";
echo $var1;
echo $var2;
$bar = 'foo';
$my_var = 10;
echo $bar($my_var). "\n";

第14行呼叫 foo() 方法

function foo(&$my_var)
{
    global $var1; // 5
    $var1 += 2; // 7
    $var2 = 4; // 4
    $my_var += 3; // 8
    return $var2; // 4
}

$my_var = 5;
echo foo($my_var). "\n"; // 4

第14行到第17行輸出的值分別為:

echo foo($my_var). "\n"; // 4
echo $my_var. "\n"; // 8
echo $var1; // 7
echo $var2; // 10

第20行再次呼叫foo() 方法

function foo(&$my_var)
{
    global $var1; // 7
    $var1 += 2; // 9
    $var2 = 4; // 4
    $my_var += 3; // 13
    return $var2; // 4
}

$bar = 'foo';
$my_var = 10;
echo $bar($my_var). "\n"; // foo($my_var)  => 4