【 C++入門 】函數重載、extern“C“

目錄

一、函數重載

        1、函數重載概念

        2、函數重載註意點

        3、問題:為何C語言不支持函數重載,反倒C++可以?

              Linux環境下演示函數重載

              回顧程序的編譯鏈接

              采用C語言編譯器編譯後結果

                      gcc的函數名修飾規則

              采用C++編譯器編譯後結果

                      g++的函數名修飾規則

              結論

二、extern”C”

        1、C++如何調用C的靜態庫

              建立C的靜態庫

              在C++工程裡配置鏈接C的靜態庫目錄

              調用過程

        2、C如何調C++的靜態庫

              建立C++的靜態庫

              在C工程裡配置鏈接C++的靜態庫目錄

              調用過程


一、函數重載

自然語言中,一個詞可以有多重含義,人們可以通過上下文來判斷該詞真實的含義,即該詞被重載瞭。比如:以前有一個笑話,國有兩個體育項目大傢根本不用看,也不用擔心。一個是乒乓球,一個是男足。前者是“誰也贏不瞭!”,後者是“誰也贏不瞭!”

1、函數重載概念

函數重載:是函數的一種特殊情況。C語言不支持函數重載,而C++允許在同一作用域中聲明幾個功能類似的同名函數,這些同名函數的形參列表(參數個數類型 順序)必須不同,常用來處理實現功能類似數據類型不同的問題

  • 參數類型不同:

  • 參數個數不同:

  • 順序不同 

2、函數重載註意點

僅僅修改函數返回類型不是函數重載

因為無法區分你要調用的是誰

3、問題:為何C語言不支持函數重載,反倒C++可以?

有瞭函數重載,確實要比不支持函數重載的C語言上方便瞭許多,這難免會有人提問到:

  1. 既然函數重載這麼好,為何C語言就不行呢?
  2. C++又是如何支持函數重載的呢?

接下來,我就展開來討論下:首先,為瞭更好的顯現出其具體操作過程,我將在Linux的環境下向大傢展示其具體過程。其次,解釋其原理需要借助我們之前講解過的程序的編譯鏈接,所以下文我也會帶著再簡要講解下。所以,正文開始:

Linux環境下演示函數重載

  • gcc編譯 — C語言版本

首先,我們在Linux環境下創建3個目錄:f.hf.ctest.c。來分別進行聲明、定義、實現。

註意後綴,都是以.c命名,這說明以下操作是在C語言的情況下進行的

並且對上述代碼編譯運行後沒有錯誤,接下來用gcc編譯對它生成tc可執行程序,用g++編譯對它生成tcpp可執行程序,並且兩個文件編譯運行均沒錯誤

接下來,我們在原有文件的基礎上再寫一個函數來確保其是函數重載 

 此時我們用gcc對它進行編譯:

很明顯發生錯誤,再強調下,上述操作是在C語言的基礎上完成的。這就足矣說明,C語言是不支持函數重載的,想要搞清楚原因前,就要先明白程序的編譯鏈接,看下文:

回顧程序的編譯鏈接

程序的編譯鏈接我在曾經的一篇博文中已詳細講解過,這裡直接給出鏈接:

程序的環境

針對上述的三個目錄文件:f.hf.ctest.c,接下來展開討論:

程序的編譯鏈接分為四大過程:

  1. 預處理 — 頭文件展開、宏替換、條件編譯、去掉註釋。預處理後生成f.itest.i文件
  2. 編譯 — 檢查語法,生成匯編代碼。編譯後生成f.stest.s文件
  3. 匯編 — 把匯編代碼轉換成二進制的機器碼。匯編後生成f.otest.o文件
  4. 鏈接 — 合並段表、符號表的合並和符號表的重定位。通俗講就是找調用函數的地址,鏈接對應上,合並到一起

看圖:

  • 首先預處理:

  •  其次編譯,會生成符號表(記錄的是函數定義和函數地址的映射)以及函數調用指令

這裡生成瞭main函數的指令,其中有f,因為還不知道確切的地址,隻是有聲明,所以先用” >采用C語言編譯器編譯後結果

我們采用如下指令進行編譯:

結果如下:

gcc的函數名修飾規則

有沒有發現直接以函數名命名,沒有任何其它的修飾,這麼做也就註定造成瞭出現多個相同函數名的時候,在鏈接時call不知道鏈接哪個,因為函數名都是一樣的,找不到其地址,這也就說明瞭C語言不支持函數重載,其鏈接過程的圖示和上述圖示一樣:

采用C++編譯器編譯後結果

我們采用如下指令編譯:

結果如下:

  • 函數一:

  • 函數二:

 

g++的函數名修飾規則

仔細觀察C++版本的匯編指令,觀察兩個不同函數的函數名修飾樣式:

  • 一個是<_Z1fid>:
  • 另一個是<_Z1fdi>:

有沒有發現它把參數類型的首字母帶進去瞭,那也就意味著你的參數的類型不同,個數不同,參數順序不同都會導致函數名不同

這個時候,C++編譯後生成的符號表裡以及鏈接時函數調用指令應該是這個樣子:

這個時候,C++在鏈接的過程中,call找的就是其修飾後的函數名,函數名不同,自然不會出錯,這就是C++支持函數重載的核心所在而C語言的函數命名規則是根據函數名設定的,函數名相同的話,鏈接就會出錯,找不到確切地址,自然不會支持函數重載

  • 再來確定下C++函數名的修飾規則:

_Z 函數名長度 函數名 類型首字母

返回值的不同並不會影響到函數名的修飾規則這也就是為什麼前面強調的函數返回類型不同不支持函數重載

結論

C++支持函數重載而C語言不支持是因為函數在內存中的存儲方式不相同,C語言直接以函數名修飾,而C++_Z 函數名長度 函數名 類型首字母,導致C++支持重載,而C語言不支持重載。

二、extern”C”

有時候在C++工程中可能需要將某些函數按照C的風格來編譯,在函數前加extern “C”,意思是告訴編譯器,將該函數按照C語言規則來編譯。比如:tcmalloc是google用C++實現的一個項目,他提供tcmallc()和tcfree兩個接口來使用,但如果是C項目就沒辦法使用,那麼他就使用extern “C”來解決。

接下來,我就向大傢展示下如何在C++和C之間互相調用:

1、C++如何調用C的靜態庫

我們以之前寫過的隊列為例,來演示:

建立C的靜態庫

首先,把我們之前寫好的棧拷貝到新項目來:

此時編譯運行是不通過的(沒有調用main函數接口),接下來將其改成靜態庫試試:

  • 1、右鍵屬性

  • 單擊配置類型更改為靜態庫

此時編譯運行,生成.lib後綴的文件

現在有一個C++的項目,想要調用剛才C語言的靜態庫,如下:

以括號匹配這道題為例,解決這道題需要用到棧的思想。

 現在C++的項目已經創建完畢,該到瞭調用的時刻瞭,看下文:

在C++工程裡配置鏈接C的靜態庫目錄

首先,最基本的我要先包頭文件,但是自己創建的這個項目工程裡並沒有棧.h文件,所以要在文件目錄的上層尋找:

此時編譯之後就會看到,編譯沒有問題,但是運行會報一堆錯誤:

為什麼會出現運行錯誤呢?

就是因為我們對C配置瞭靜態庫,現在對這個C++工程也要配置下,如下:

首先:

其次:

隨後,把附加庫目錄生成的.lib文件名字放到如圖所示位置:

此時鏈接器鏈接就會鏈接到它的靜態庫

此時再編譯運行,發現依舊會出錯

 但是當我把Stack.c的後綴改為cpp時

此時再運行看看:

此時編譯運行通過瞭,為什麼把Stack_C的後綴改為.cpp就可以通過呢?

這裡就牽扯到上文談到的C++和C在匯編中不同的函數名修飾規則瞭,在C語言中,隻有函數名,可是C++有函數類型個數什麼的,用原先.c後綴的話就會導致鏈接出錯,改為後綴.cpp就實現瞭C++調C++,就沒有問題瞭。

  • 可現在明確指出要用C++調用C,該如何操作呢?

見下文:

調用過程

此時就要用到我們的extern”C”。要知道C++是兼容C的,它認識C語言的命名規則,

加上extern”C”後,我Stack.h聲明的這些東西,都會展開在extern”C”這個括號裡面,核心作用就是告訴編譯器,extern “C” 聲明的函數,是C庫,要用C的方式鏈接調用

此時我再運行下看看:

此時發現就沒有任何問題瞭,這就成功實現瞭C++調用C。 

2、C如何調C++的靜態庫

實現瞭C++調C,接下來實現下C調C++。

建立C++的靜態庫

老樣子,依舊是新建一個空工程,還是以棧為例,把原先寫的棧拷貝到新工程,不過此工程是封給C++的,這裡我們要先把此工程配置為靜態庫。

首先 

其次,改為靜態庫:

此時編譯運行:

可以發現在如下的目錄下生成.lib後綴的文件

 此時配置靜態庫就完成瞭。

在C工程裡配置鏈接C++的靜態庫目錄

接著,創建一個C的項目:

第一步依舊是把頭文件包上,依然是去上層目錄尋找:

此時你會發現,編譯沒有錯誤,其實這裡的編譯是有問題的,這裡鏈接的其實是C的庫,這個時候是C調C,這裡我們需要重新配置下鏈接庫目錄。

隨後,再把Stack_CPP.lib拷貝到附加依賴項的前面:

此時編譯運行依舊會報錯

此時就是鏈接錯誤

依舊是跟C++的函數名修飾規則有關,C語言是沒有修飾的,而C++是修飾過的,這裡當然會出現鏈接錯誤。

這裡的解決方案也並不是像C++那樣僅僅加個extern”C”就可以解決的,因為extern”C”隻是在C++支持,C語言不支持。

具體如何操作呢?隻需要在extern”C”的基礎上加上條件編譯即可解決,具體過程見下文:

調用過程

這裡我們針對Stack_CPP靜態庫進行修改,這裡有兩種方法:

  • 法一:在Stack.h文件聲明的所有函數前加上extern”C”

在C++這個工程裡面對每一個聲明加上extern “C”是為瞭告訴C++這些函數要用C的方式去編譯,此時我到C工程項目裡面去編譯運行:

發現又出錯瞭,這又是為什麼呢?(頭文件展開出錯

很簡單:在這個C的項目前面我們包瞭頭文件Stack.h,包上的這個頭文件中裡面就加上瞭我們先前的extern “C”,此時出錯不理所應當,因為C語言不支持extern”C”

此時我們針對C++工程巧用條件編譯

解釋下此圖紅框框裡的意思:

如若滿足C++的標準,那麼就把EXTERN_C替換成extern”C”,讓其在C++工程中將這些函數用C語言的標準去訪問,如若不滿足C++標準,那麼就把EXTERN_C看為空,啥也沒有,這樣在C項目工程那鏈接的時候,根本不會出現EXTERN_C,又滿足瞭鏈接要求

我們在C項目工程裡編譯運行看看:

編譯運行成功瞭,是不是非常神奇。 不得不在這感慨一句條件編譯學到現在沒想到是在C調C++的時候體現出來瞭。

當然其實這裡還有一種更為簡潔的寫法,看下文:

  • 法二:

為瞭避免重復寫EXTERN_C,我們可以這樣:

先在C++項目工程裡面把聲明的函數用extern”C”整體包起來:

其次,加上條件編譯:

再對C項目工程編譯運行:

此時編譯運行同樣是沒有任何錯誤。

此時C調C++就結束瞭,不得不再次感慨條件編譯牛皮!!


總結

本篇博文旨在強調瞭函數重載以及其內部原理,詳解瞭為何C++支持函數重載,而C不支持。

此博文另一核心知識點旨在講述瞭C++和C之間如何實現互相調用

創作不易、還望給個三連哈~

本文來自網絡,不代表程式碼花園立場,如有侵權,請聯系管理員。https://www.codegarden.cn/article/28052/
返回顶部