函數重載(七)
重載是指同一個標識符在不同的上下文有不同的意義,重載在自然語言中是隨處可見的,那麽在程序設計中是否也有重載呢?C++ 是一門面向對象的語言,當然要支持函數重載了。C++ 中的函數重載表現為:a> 用同一個函數名定義不同的函數;b> 當函數名和不同的參數搭配時函數的含義不同。
下來我們以代碼為例進行分析
#include <stdio.h> #include <string.h> int func(int a) { return a; } int func(int a, int b) { return a + b; } int func(const char* s) { return strlen(s); } int main() { printf("%d\n", func(1)); printf("%d\n", func(1, 2)); printf("%d\n", func("hello")); return 0; }
我們看到用同一個函數名定義了 3 個函數,在 C 語言中這樣是不行的。我們看看在 C++ 中是否支持呢?
我們看到編譯通過並且也完成函數的功能了。那麽怎樣就能稱之為函數重載呢?它至少得滿足下面的一個條件:a> 參數個數不同;b> 參數類型不同;c> 參數順序不同;那麽下面的函數可以稱之為函數重載嗎?
int func(int a, const char* s) { return a; } int func(const char* s, int a) { return strlen(s); }
它們顯然是函數重載了,因為它們滿足上面的第 3 個條件。當函數默認參數遇上函數重載會發生什麽呢?下來我們再來看一份示例代碼
#include <stdio.h> int func(int a, int b, int c = 0) { return a * b * c; } int func(int a, int b) { return a + b; } int main() { printf("%d\n", func(1, 2)); return 0; }
我們看到定義了兩個 func 函數,第一個的最後一個是默認參數,這樣的函數可以稱之為函數重載嗎?編譯器究竟是否知道它該調用那個函數呢?我們看看編譯結果
我們看到編譯報錯了,它說這個函數重載是模糊的,它不知道該調用那個函數。雖然 C++ 相對於 C 添加了很多特性,顯然這個就是一個不好的特性,在後續的高級語言(如 Java、C#等)中都取消這些相互矛盾的特性。所以這樣的函數重載是不可取的,那麽編譯器調用重載函數時有哪些準則呢?A、將所有的同名函數作為候選者;B、嘗試尋找可行的候選函數:a> 精確匹配實參。 b> 通過默認參數能夠匹配實參 。 c> 通過默認類型轉換匹配實參;C、匹配失敗:a> 最終尋找到的候選函數不唯一,則出現二義性,編譯失敗。 b> 無法匹配所有候選者,函數未定義,編譯失敗
在函數重載時應註意的事項:a> 重載函數在本質上是相互獨立的不同函數;b> 重載函數的函數類型不同;c> 函數返回值不嫩作為函數重載的依據;函數重載是由函數名和參數列表決定的!!!
下來我們通過一份示例代碼來看看函數重載的本質
#include <stdio.h> int add(int a, int b) // int(int, int) { return a + b; } int add(int a, int b, int c) // int(int, int, int) { return a + b + c; } int main() { printf("%p\n", (int(*)(int, int))add); printf("%p\n", (int(*)(int, int, int))add); return 0; }
我們在之前學過函數名其實就是函數的入口地址,我們通過函數的類型來打印它的地址。我們來看看編譯結果
我們看到打印的是不同的地址,也就是說這兩個重載函數是不同的。我們再在 C 語言編譯器中編譯試下,看看結果是什麽
我們看到它報錯了,說 add 函數已經定義了。在 C 語言中,只要函數名相同,它便認為這是同一個函數。我們再來看看重載與指針,還是以代碼為例進行分析
#include <stdio.h> #include <string.h> int func(int a) { return a; } int func(int a, int b) { return a + b; } int func(const char* s) { return strlen(s); } typedef int(*PFUNC)(int a); int main(int argc, char *argv[]) { int c = 0; PFUNC p = func; c = p(1); printf("c = %d\n", c); return 0; }
我們在第 19 行定義了一個函數指針 PFUNC,那麽我們在第 25 行將它指向 func 函數,這時它會知道自己指向的是哪個函數嗎?我們來看看編譯結果
我們看到編譯通過,並完美運行。很明顯它指向的是第一個 func 函數,因為它的類型為 int(int);所以它匹配到了第一個 func 函數。
當函數重載遇上函數指針,將重載函數名賦值給函數指針時:a> 根據重載規則挑選與函數指針參數列表一致的候選者;b> 嚴格匹配候選者的函數類型與函數指針的函數類型。註意:1、函數重載必然發生在同一個作用域中;2、編譯器需要用參數列表或函數類型進行函數選擇;3、無法直接通過函數名得到重載函數的入口地址。
在實際的工程中 C++ 和 C 代碼間的相互調用時不可避免的,C++ 編譯器能夠兼容 C 語言的編譯方式。但 C++ 編譯器會優先使用 C++ 編譯的方式,extern 關鍵字能夠強制讓 C++ 編譯器進行 C 方式的編譯。下來我們來看個示例代碼
add.h 源碼
int add(int a, int b);
add.c 源碼
#include "add.h" int add(int a, int b) { return a + b; }
test.cpp 源碼
#include <stdio.h> extern "C" { #include "add.h" } int main(int argc, char *argv[]) { int c = add(1, 2); printf("c = %d\n", c); return 0; }
我們先將 add.c 編譯成 add.o 文件,再編譯 test.cpp 文件,看看結果是否如我們所想的那樣
我們看到結果已經實現了。我們如果將 test.cpp 裏面的代碼復制到 test.c 裏面,以 C 的方式編譯,能否通過呢?
我們發現它不認識 extern "C",那麽問題來了,我們如何保證一段 C 代碼只會以 C 的方式被編譯呢?__cplusplus 是 C++ 編譯器內置的標準宏定義,我們可以利用這個宏來確保 C 代碼以統一的 C 方式被編譯成目標文件。我們將上面 test.cpp 中的 extern "C" 這一段代碼改成下面這樣,我們再次試試看呢
#ifdef __cplusplus extern "C" { #endif #include "add.h" #ifdef __cplusplus } #endif
這樣便保證了在 C++ 編譯器中以 C 的方式進行編譯,如果是在 C 編譯器中,extern "C" 便不起作用。我們編譯下看看結果
我們看到在 C 編譯器下也能正常編譯。在這塊有兩個註意事項:A、C++ 編譯器不能以 C 的方式編譯重載函數;B、編譯方式決定函數名被編譯後的目標名:a> C++ 編譯方式將函數名和參數列表編譯成目標名。 b> C 編譯方式只將函數名作為目標名進行編譯。
通過對函數重載的學習,總結如下:1、函數重載是 C++ 中引入的概念,函數重載用於模擬自然語言中的詞匯搭配;2、函數重載使得 C++ 具有更豐富的語義表達能力,它的本質為相互獨立的不同函數;3、C++ 中通過函數名和函數參數確定函數調用;4、函數重載是 C++ 對 C 的一個重要升級;5、函數重載通過函數參數列表區分不同的同名函數;6、extern 關鍵字能夠實現 C 和 C++ 的相互調用;7、編譯方式決定符號表中的函數名的最終目標名。
歡迎大家一起來學習 C++ 語言,可以加我QQ:243343083。
函數重載(七)