首頁 > 軟體

C++超詳細分析函數過載的使用

2022-04-20 19:00:47

一、函數過載分析(上)

1.1 過載的定義

定義:同一個識別符號在不同的上下文有不同的意義

1.2 函數過載的定義

  • 用同一個函數名定義不同的函數
  • 當函數名和不同的引數搭配時函數的含義不同

如下:

下面看一段程式碼,感受一下:

#include <stdio.h>
#include <string.h>
int func(int x)
{
    return x;
}
int func(int a, int b)
{
    return a + b;
}
int func(const char* s)
{
    return strlen(s);
}
int main(int argc, char *argv[])
{
    printf("%dn", func(3));
    printf("%dn", func(4, 5));
    printf("%dn", func("D.T.Software"));
    return 0;
}

下面為輸出結果:

1.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(int argc, char *argv[])
{
    int c = func(1, 2);
    return 0;
}

下面為輸出結果:

編譯報錯,因為模稜兩可。如果說呼叫第一個函數說的過去,因為符合函數預設引數規則,c 的值已經確定;呼叫第二個函數也符合常理,所以編譯不會通過。

1.4 編譯器呼叫過載函數的準則

將所有同名函數作為候選者

嘗試尋找可行的候選函數

  • 精確匹配實參
  • 通過預設引數能夠匹配實參
  • 通過預設型別轉換匹配實參

匹配失敗

  • 最終尋找到的候選函數不唯一,則出現二義性,編譯失敗。
  • 無法匹配所有候選者,函數未定義,編譯失敗。

1.5 函數過載的注意事項

  • 過載函數在本質上是相互獨立的不同函數
  • 過載函數的函數型別不同
  • 函數返回值不能作為函數過載的依據

函數過載是由函數名和參數列決定的!!!

函數過載的本質是什麼?下面通過一段程式碼深入分析,編譯環境為VS2012。

#include "stdafx.h"
#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("%pn", (int(*)(int, int))add);
    printf("%pn", (int(*)(int, int, int))add);
    return 0;
}

由C語言的知識可以知道,函數名就是函數的入口地址,所以輸出結果如下:

可以看到,兩個 add() 函數的入口地址不一樣,所以這兩個 add 是兩個不同的函數。

編譯器是如何看待這兩個 add() 函數的呢?下面來深入分析。先看一下編譯器產生的中間結果,在Test -> Debug -> Test.obj 檔案中。

然後使用VS2012裡面自帶的命令列工具檢視 Test.obj 裡面有什麼東西。

上圖示為VS2012 命令列所在位置

輸入 dumpbin,如下:

這裡只需要關係 SYMBOLS(符號表),符號表就是編譯器在編譯過程中根據原始碼所生成的一張表,這張表有程式的函數名變數等等。

輸入以下命令,其中 /symbols 後面為 Test.obj 所在的位置。

找到下面的地方,可以看到編譯器編譯 (int __cdecl add(int,int)) 時識別符號為?add@@YAHHH@Z;而編譯器編譯(int __cdecl add(int,int,int)) 時識別符號為?add@@YAHHHH@Z ,也就是說編譯器在編譯這兩個函數時已經把這兩個函數分別對待,儘管它們名字一樣,所以兩個 add() 函數的入口地址不一樣,這就很好理解了。

1.6 小結

  • 函數過載是 C++ 中引入的概念
  • 函數過載用於模擬自然語言中的詞彙搭配
  • 函數過載使得 C++ 具有更豐富的語意表達能力
  • 函數過載的本質為相互獨立的不同函數
  • C++ 中通過函數名和函數引數確定函數呼叫

二、函數過載分析(下)

2.1 函數過載遇上函數指標

將過載函數名賦值給函數指標時

  • 根據過載規則挑選與函數指標參數列一致的候選者
  • 嚴格匹配候選者的函數型別與函數指標的函數型別

下面看一段程式碼:

#include <stdio.h>
#include <string.h>
int func(int x)
{
    return x;
}
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 = %dn", c);
    return 0;
}

下面為輸出結果:

這也就是前面說的通過函數指標所指向的函數型別參數列來進行選擇。

注意事項

  • 函數過載必然發生在同一個作用域中
  • 編譯器需要用參數列或函數型別進行函數選擇
  • 無法直接通過函數名得到過載函數的入口地址(可以通過指標來獲取)

如下,這段程式碼想通過函數名獲取過載函數的入口地址:

#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("%pn", add);
    printf("%pn", add);
    return 0;
}

編譯的時候會報錯,無法確定是哪個函數。

2.2 C++和C的相互呼叫

  • 實際工程中C++和C程式碼相互呼叫是不可避免的
  • C++編譯器能夠相容C語言的編譯方式
  • C++編譯器會優先使用C++編譯的方式
  • extern 關鍵字能強制讓C++編譯器進行C方式的編譯

如下:

在 Linux環境下新建一個 9-2 資料夾,先在資料夾下新建 add.c 和 add.h 檔案,如下:

add.c :

#include "add.h"
int add(int a, int b)
{
    return a + b;
}

add.h :

int add(int a, int b);

通過 linux 命令 cd 進入 9-2 資料夾,再將 add.c 轉換成 add.o 檔案,如下所示:

然後在 9-2 資料夾下建一個 main.cpp 檔案,如下:

mian.cpp :

#include <stdio.h>
#include "add.h"
int main()
{
    int c = add(1,2);
    printf("c = %dn", c);
    return 0;
}   

對程式進行編譯,發現程式報錯,沒有定義 add() 函數,但是函數確實已經定義了,可以使用 linux 中的 nm 指令檢視 add.o 裡面的資訊,列印出來的就是符號表資訊,可以看到確實有 add 。

這個時候就需要使用 extern關鍵字強制讓C++編譯器進行C方式的編譯,所以 main.cpp就要修改成這樣:

#include <stdio.h>
extern "C"
{
  #include "add.h"  
}
int main()
{
    int c = add(1,2);
    printf("c = %dn", c);
    return 0;
}   

這樣編譯就能通過了:

如果在 9-2 檔案中新建一個 main.c 檔案,main.c 裡面的程式碼 與 main.cpp 中的相同。

進行編譯,發現會報錯誤,因為 extern 關鍵詞寫法是 C++ 中的, C語言不支援該寫法。那有沒有一種寫法既能被 C語言編譯通過,又能讓 C++編譯通過呢?且看下面。

2.3 使得C程式碼只會以C的方式被編譯的解決方案

  • _cplusplus 是C++編譯器內建的標準宏定義
  • _cplusplus 的意義是確保C程式碼以統一的C方式被編譯成目標檔案

如下:

所以上述程式碼可以寫作,main.c和 main.cpp 均為:

#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif
#include "add.h"
#ifdef __cplusplus
}
#endif
int main()
{
    int c = add(1, 2);
    printf("c = %dn", c);
    return 0;
}

這樣程式在 C語言和 C++ 的編譯環境下均能通過,如下:

注意事項

C++編譯器不能以C的方式編譯過載函數

編譯方式決定函數名被編譯後的目標名

  • C++編譯方式將函數名和參數列編譯成目標名
  • C 編譯方式只將函數名作為目標名進行編譯

下面通過一個例子說明一下:

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;
}

將該程式碼編譯成目標檔案,取名為 test.oo,然後通過 linux 中的 nm 命令檢視 test.oo 中的東西,可以看到 test 符號表裡面有兩個東西 T _Z3addii 和T _Z3addiii,這就是 add 函數被編譯過後的目標函數名,ii 表示兩個引數, iii 表示三個引數。

如果採用 C 方式編譯過載函數,程式碼如下:

extern "C"
{
    int add(int a, int b)  // ==>add
    {
        return a + b;
    }
    int add(int a, int b, int c) // ==>add
    {
        return a + b + c;
    }
}

下面為編譯結果,可以看到編譯報錯,說兩個 add() 函數衝突了。

2.4 小結

  • 函數過載是 C++ 對 C 的一個重要升級
  • 函數過載通過函數參數列區分不同的同名函數
  • extern 關鍵字能夠實現 C 和 C++的相互呼叫
  • 編譯方式決定符號表中的函數名的最終目標名

到此這篇關於C++超詳細分析函數過載的使用的文章就介紹到這了,更多相關C++函數過載內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com