PHP中的過載

tlanyan發表於2019-03-03

轉載請註明文章出處:tlanyan.me/overload-in…

整理思路時想到一個問題:PHP為什麼不允許同名函式存在?即不允許常見於其他語言的過載機制?

過載和重寫

先區分一下過載(overload)和重寫(override):過載指多個名字相同,但引數不同的函式在同一作用域並存的現象;重寫出現在繼承中,指子類重定義父類功能的現象,也被稱為覆蓋。過載中說的引數不同有三種情況:引數個數不同,引數型別不同,引數順序不同。重寫一般指函式的覆蓋,即相同簽名的成員函式在子類中重新定義(實現抽象函式或介面不是重寫),是實現多型(polymorphism)的一種關鍵技術。成員變數也可以過載/覆蓋,但一般不會這麼做。

用簡單的C程式碼來說明過載:

int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
double add(int a, int b, double c) { return a + b + c; }
double add(double a, int b, int c) { return a + b + c; }
複製程式碼

第一個函式為參考基準,其他三個對應過載的三種情形。函式過載多見於強型別語言,編譯後函式在函式符號表的名稱一般是函式名加引數型別。上面的四個函式,g++編譯後,nm命令檢視符號表中的名字,輸出如下:

[tlanyan@server ~]# nm test | grep add
0000000000400730 t _GLOBAL__sub_I__Z3addii
0000000000400851 T _Z3adddd
00000000004008b1 T _Z3adddii
000000000040083d T _Z3addii
000000000040087d T _Z3addiid
複製程式碼

最後四行的第三列對應編譯後四個函式的符號資訊,_Z3為字首,add是函式名,剩下的字母d代表double,i代表int,與生命一一對應。

再回到PHP的過載。PHP的函式宣告中引數無需宣告型別,直接排除引數型別不同、引數順序不同兩種過載,只剩下引數個數不同一條路可走。定義一個引數個數不同名字相同的函式,這麼一個小小的過載要求,在PHP中也是不合法的!原因是PHP中不允許同名函式存在,想定義重名函式,死心吧!雖然大多數情況下以預設引數方式實現過載基本上夠用,但不時還會覺得憋屈,忍不住想問一句:PHP為什麼不允許(同名函式)過載啊?!

PHP的苦衷

PHP不支援同名函式的過載是有原因的。上面已經提到,PHP函式宣告時不需要指定引數型別,過載中的三種情況立馬廢掉兩種。倖存的引數個數不同這一條路也走不通,為什麼呢?因為PHP中呼叫函式時,少傳引數,不行;多傳引數,沒問題!來個簡單的例子:

function foo($arg1, $arg2) {
    echo "$arg1,  $arg2\n";
}

// 函式呼叫
// 引數過少,提示:
//PHP Warning:  Missing argument 2 for foo()
// PHP Notice:  Undefined variable: arg2 in php shell code on line 2
foo("tlanyan");

// 引數個數正好,執行正常
foo("hello", "tlanyan");

// 多傳引數,執行正常
foo("hello", "tlanyan", "nice day");

// 傳更多引數,也一切正常
foo("hello", "tlanyan", "morning", "noon", "afternoon", "evening", "night");
複製程式碼

只要個數不小於宣告的,傳多少引數PHP不管。所以引數個數不同,在PHP中不足以區分函式。

個人認為另一個不允許名函式存在的重要原因是function_existsmethod_existsis_callable這些API的存在。作為簡單易用的語言,PHP為開發人員提供了查詢函式名是否存在/可用的便利API,這在程式語言中很少見(尤其是get_defined_functions這類API)。可以看到,這些API都不需要引數資訊。如果能定義引數不同的過載函式,這些API都要跟著改,勢必引入額外的複雜性。正所謂魚與熊掌不可兼得,方便你用時沒想到引數不同,不方便你定義就抱怨,好像不好吧?

PHP5引入了反射API,這是非常強大的型別資訊查詢工具。就函式宣告而言,ReflectionMethod/ReflectionFunction類的getParameters/getNumberOfParameters/getNumberOfRequiredParameters等API,功能上甩function_exists等好幾條街。有了反射機制,按理說function_exists這些API可以安心的退休。雖然反射這一套東西功能強大,但遠沒有舊式API簡單好用。再加上看看市面上的程式碼,有多少類庫和框架依賴舊式API。從相容性和實用性考慮,個人認為短時間內能以同名函式方式過載的概率非常小。

PHP中的過載

只看完上面的內容就說PHP不支援過載,我想隨便一個資深的PHP開發都會不由自主的取下拖鞋,然後教你什麼是PHP中的過載,並保證至少有好幾種實現方法;官方人員對這種認知估計也無力吐槽:能不能好好看官方文件?!官網中可是有一節專門講過載

因為種種原因,PHP不支援傳統的過載,也就是同名函式的過載,但PHP是支援過載的,而且姿勢還不少。簡單來說,PHP中主要有以下幾種過載方式:

  1. 預設引數,定義一個全面的函式版本,不是必須的值在宣告時賦予預設值;
  2. 定義一個不宣告引數的入口函式,函式內使用func_num_args/func_get_args獲取引數個數/陣列,然後根據引數個數轉發到具體實現的函式;
  3. 自PHP5.6起,可以用變長引數實現過載,func_get_args的另一種形式;
  4. 對於類中的成員函式,可以通過__call__callStatic實現過載。

如果你還知道其他方式,歡迎評論給出方案。

總結

PHP的特性決定了其不支援同名函式方式的過載,但並不意味著PHP不支援過載。實際上PHP可以多種方式實現過載,並保持其一貫的簡單易用性。

感謝閱讀!

參考

  1. php.net/manual/it/l…

相關文章