# [32] 如何混合C和C++編程
## FAQs in section [32]:
* [32.1] 混合C和C++編程時我需要知道什么?
* [32.2] 如何在C++代碼中包含標準的C頭文件?
* [32.3] 如何在C++代碼中包含非系統的C頭文件?
* [32.4] 如何修改我自己的C頭文件 , 以便更容易的在C++代碼中包含他們?
* [32.5] 如何從C++代碼中調用非系統C 函數f(int,char和float)? from my C++ code?")
* [32.6] 如何創建一個C++ 函數f(int,char和float),可以由我的C代碼調用? that is callable by my C code?")
* [32.7] 為什么鏈接器報錯說C / C++函數調用C++ / C函數?
* [32.8] 如何傳遞一個C++ 類對象從/到一個C函數?
* [32.9] 我的C函數可以直接訪問一個C++ 對象的數據嗎?
* [32.10] 為什么C++而不是為C讓我覺得“更加遠離機器”?
## 32.1 混合C和C++編程時我需要知道什么?
以下是一些要點(雖然有些編譯器供應商可能不需要全部要點,查看你的編譯器供應商的文檔):
* 你必須使用你的C++編譯器來編譯的main()(也就是說靜態初始化)
* 你的C++編譯器應該能夠進行直接鏈接(也就是說,它有自己的專門類庫)
* 你的C和C++編譯器可能需要來自同一個供應商,并具有兼容的版本(也就是說,它們有相同的調用約定)
此外,您需要閱讀本節的其余部分,了解如何使你的C可調用的C++函數和/或C++可調用C函數。
順便說一下,還有另一種途徑來處理這件事:使用C++編譯器編譯所有代碼(甚至是你的C風格代碼)。這幾乎無需混合C和C++,但是你要格外小心(也可能是,希望! -發現了一些錯誤)你的C風格的代碼。缺點是你需要更新你的C代碼風格,主要是因為C++編譯器比C編譯器更加嚴謹/挑剔。值得一提的是,清理你的C代碼風格可能會比實際混合C和C++所付出的努力要少,并且清理C代碼風格還能給你帶來一份額外收入。但是很明顯,你幾乎沒有選擇的余地,如果你不能改變C代碼(例如,如果它是來自第三方的)。
## 32.2 如何在C++代碼中包含標準的C頭文件?
要包含一個標準(如`<cstdio>`)頭文件,你不需要做任何事情。例如:
```
//?This?is?C++?code
?#include?<cstdio>????????????????//?Nothing?unusual?in?#include?line
?int?main()
?{
???std::printf("Hello?world\n");??//?Nothing?unusual?in?the?call?either
...
?}
```
如果你認為`std::printf`的`std`部分很奇怪,那么最好的辦法是“適應它”。換句話說,它是使用標準庫函數的標準方法,所以你不妨現在開始習慣它。
然而,如果你正在使用你的C++編譯器編譯C代碼,你恐怕不想修改所有這些`printf`調用為`std::printf` 。幸運的是,這種情況下,C代碼將使用舊式頭`<stdio.h>`而不是新型頭`<cstdio>`,命名空間技術將會照顧一切:
```
/*?This?is?C?code?that?I'm?compiling?using?a?C++?compiler?*/
?#include?<stdio.h>??????????/*?Nothing?unusual?in?#include line?*/
?int?main()
?{
???printf("Hello?world\n");??/*?Nothing?unusual?in?the?call?either?*/
...
?}
```
最后的評論:如果你有不屬于標準庫C頭文件,你需要遵守一些不同的準則。有兩種情況:要么你不能改變頭文件,要么你可以改變頭文件。
## 32.3 如何在C++代碼中包含非系統的C頭文件?
如果你是其中一個C頭文件不是由系統提供的,你可能需要把`#include`行放到`extern"C"(/ * ... * /)`構造中。這告訴C++編譯器的功能在頭文件中聲明的C函數。
```
//?This?is?C++?code
?extern?"C"?{
//?Get?declaration?for?f(int?i,?char?c,?float?x)
???#include?"my-C-code.h"
?}
?int?main()
?{
???f(7,?'x',?3.14);???//?Note:?nothing?unusual?in?the?call
...
?}
```
注: 對于系統提供的C頭文件(如`<cstdio>`)和你可以更改的C頭文件,準則略有不同
## 32.4 如何修改我自己的C頭文件 , 以便更容易的在C++代碼中包含他們?
如果你包含了不是由系統提供的C頭文件,并且如果你能夠改變的C頭文件,你應該著重考慮通過添加 `extern"C"(...)`塊到頭文件,這樣使C++用戶在C++代碼中更容易使用`#include`。由于C編譯器通不過頭文件含有 `extern"C"`的結構,你需要把`extern"C"{}`行包裹在`#ifdef`預編譯塊中,這樣他們不會被正常的C編譯器編譯。
步驟#1:將以下行添加到你C頭文件的頂部(注:符號`__cplusplus `當且僅當編譯器是C++編譯器的時候被定義):
```
?#ifdef?__cplusplus
?extern?"C"?{
?#endif
```
步驟#2:將以下行添加到你C頭文件的最底部:
```
?#ifdef?__cplusplus
?}
?#endif
```
現在您可以`#include`你的C頭文件,不用在C++代碼包含任何的`EXTERN "C"`:
```
//?This?is?C++?code
//?Get?declaration?for?f(int?i,?char?c,?float?x)
?#include?"my-C-code.h"???//?Note:?nothing?unusual?in?#include?line
?int?main()
?{
???f(7,?'x',?3.14);???????//?Note:?nothing?unusual?in?the?call
...
?}
```
注: 對于系統提供的C頭文件(如`<cstdio>`)和你可以更改的C頭文件,準則略有不同
注:`#define`宏有4中罪惡: 罪惡#1 , 罪惡#2 , 罪惡#3 和罪惡#4 。但有時他們仍然有用。只要別忘了使用后洗清“罪惡”的雙手。
## 32.5 如何從C++代碼中調用非系統C 函數`f`(`int`,`char`和`float`)?
如果你有一個個人的C函數要調用,由于一些其他原因,你沒有或不想在函數聲明中`#include`一個 C頭文件,你可以在C++代碼通過`extern"C"`語法聲明單個的C函數。當然,你需要使用完整的函數原型:
```
extern?"C"?void?f(int?i,?char?c,?float?x);
```
可以使用大括號聲明幾個C函數:
```
?extern?"C"?{
???void???f(int?i,?char?c,?float?x);
???int????g(char*?s,?const?char*?s2);
???double?sqrtOfSumOfSquares(double?a,?double?b);
?}
```
在此之后你可以象調用C++函數那樣調用該函數:
```
?int?main()
?{
???f(7,?'x',?3.14);???//?Note:?nothing?unusual?in?the?call
...
?}
```
## 32.6 如何創建一個C++ 函數`f`(`int`,`char`和`float`),可以由我的C代碼調用?
通過使用`EXTERN的"C"`結構通知C++編譯器`f(int,char,float)`可由一個C編譯器調用 :
```
//?This?is?C++?code
//?Declare?f(int,char,float)?using?extern?"C":
?extern?"C"?void?f(int?i,?char?c,?float?x);
...
//?Define?f(int,char,float)?in?some?C++?module:
?void?f(int?i,?char?c,?float?x)
?{
...
?}
```
通過`extern"C"`行告訴編譯器應該使用C調用約定和名字校正(name mangling)(例如,以下劃線開頭)來進行鏈接。由于C不支持重載,所以你不能編寫可以由C程序調用的重載函數。
## 32.7 為什么鏈接器報錯說C / C++函數調用C++ / C函數?
如果你沒有設置對`EXTERN`的`"C"`,你有時會得到鏈接錯誤,而不是編譯器錯誤。這是由于C++編譯器通常是“校正(mangle)”函數名稱(例如,為了支持函數重載),這和C編譯器不同。
關于如何使用`EXTERN`的 `"C"`請參考前兩個的FAQs。
## 32.8 如何傳遞一個C++ 類對象從/到一個C函數?
下面是一個例子(關于`extern"C"`,見前面的兩個FAQs)。
```
//Fred.h:
/*?This?header?can?be?read?by?both?C?and?C++?compilers?*/
?#ifndef?FRED_H
?#define?FRED_H
?#ifdef?__cplusplus
???class?Fred?{
???public:
?????Fred();
?????void?wilma(int);
???private:
?????int?a_;
???};
?#else
???typedef
?????struct?Fred
???????Fred;
?#endif
?#ifdef?__cplusplus
?extern?"C"?{
?#endif
?#if?defined(__STDC__)?||?defined(__cplusplus)
???extern?void?c_function(Fred*);???/*?ANSI?C?prototypes?*/
???extern?Fred*?cplusplus_callback_function(Fred*);
?#else
???extern?void?c_function();????????/*?K&R?style?*/
???extern?Fred*?cplusplus_callback_function();
?#endif
?#ifdef?__cplusplus
?}
?#endif
?#endif?/*FRED_H*/
// Fred.cpp:
//?This?is?C++?code
?#include?"Fred.h"
?Fred::Fred()?:?a_(0)?{?}
?void?Fred::wilma(int?a)?{?}
?Fred*?cplusplus_callback_function(Fred*?fred)
?{
???fred->wilma(123);
???return?fred;
?}
//main.cpp:
//?This?is?C++?code
?#include?"Fred.h"
?int?main()
?{
???Fred?fred;
???c_function(&fred);
...
?}
//c-function.c
/*?This?is?C?code?*/
?#include?"Fred.h"
?void?c_function(Fred*?fred)
?{
???cplusplus_callback_function(fred);
?}
```
不像C++代碼,C代碼將無法告訴你兩個指針是否指向同一個對象,除非指針完全相同。例如,在C++可以很容易地檢查一個派生類`Derived*`指針`dp`和`Base*`指針`bp`是否指向同一個對象,你可以使用`if(dp == bp)`。C++編譯器自動轉換指針為相同的類型,在這種情況下,轉換為`Base*`,然后比較他們。根據不同的C++編譯器的實現細節,這種轉換有時會改變一個指針值的位數據(bits)。但是C編譯器不會知道該怎么做指針轉換,所以比如從`Derived*`到`Base*`的轉換,必須在由C++編譯器的編譯的代碼中,而不是在C編譯器編譯的C代碼中。
_注意:你必須特別小心轉換為`void*`指針,因為該轉換將不會允許__C__或__C++__編譯器做適當的指針調整!例如(繼續前段內容),如果你把`dp`和`bp`賦值到兩個`void *`指針比如說是`dpv`和`bpv`,有可能`dpv != bpv`即使`dp==bp `。不要說我沒有提醒你!_
## 32.9 我的C函數可以直接訪問一個C++ 對象的數據嗎?
有時。
(有關傳遞C+ + 對象到/從C函數的基本內容,閱讀以前的FAQ)。
你可以安全地從C函數訪問C++對象的數據,如果C++類:
* 沒有虛函數(包括繼承虛函數)
* 它的所有數據在相同的訪問級別(私有/保護/公共)
* 虛函數沒有完全包含的子對象
C++類有任何基類(或任何完全包含子對象具有基類),訪問數據將在技術上是不可移植的,因為繼承體系下的類的布局與語言無關。然而在實踐中,所有C++編譯器都使用相同的方式:首先是基類對象(多重繼承按照左到右的順序),然后是成員對象。
此外,如果類(或任何基類)含有虛函數,幾乎所有C++編譯器給對象添加一個 `void *`,在第一個虛擬函數的位置或在對象的開始位置。同樣,這也不是語言要求的,但幾乎所有的編譯器都是這樣實現的。
如果類有任何虛基類,這將更復雜,更不便于移植。一個常見的實現技術是,在對象的最后位置放置一個虛基類對象( `v` )(無論`v`在繼承層次結構中的位置)。對象的其它部分按照正常順序布局。每一個虛基類`v`的派生類,實際上都有一個指向`v`的指針 。
## 32.10 為什么C++而不是為C讓我覺得“更加遠離機器”?
因為你就是。
作為一個面向對象編程語言,C++允許你對問題域建模,這將允許你使用問題域的語言編程,而不是解決方案域的語言。
C的優勢之一是,它已“沒有任何隱藏的機制”:所見即所得。你可以閱讀一個 C程序,“看到”每個時鐘周期。在C++中可不是這樣,老的C程序員(如我們許多人曾經是),對于這個特性往往會很矛盾(也許是“敵視”?)。但當他們轉變到面向對象思想以后,他們往往認識到,雖然C++的隱藏一些機制,但是它也提供了更高的抽象和更簡潔的表達,從而能夠在保持運行時性能的同時降低后期維護成本。
當然你可能會編寫糟糕的代碼不管使用任何語言,C++并不保證好的質量,可重用性,抽象,或任何“優異的”測試指標。
_C++中無法阻止糟糕的程序員編寫的糟糕的程序,但是它能夠讓優秀的開發人員創建出色的軟件。_
- C++ FAQ Lite
- [1] 復制許可
- [2] 在線站點分發本文檔
- [3] C++-FAQ-Book 與 C++-FAQ-Lite
- [6] 綜述
- [7] 類和對象
- [8] 引用
- [9] 內聯函數
- [10] 構造函數
- [11] 析構函數
- [12] 賦值算符
- [13] 運算符重載
- [14] 友元
- [15] 通過 &lt;iostream&gt; 和 &lt;cstdio&gt;輸入/輸出
- [16] 自由存儲(Freestore)管理
- [17] 異常和錯誤處理
- [18] const正確性
- [19] 繼承 — 基礎
- [20] 繼承 — 虛函數
- [21] 繼承 — 適當的繼承和可置換性
- [22] 繼承 — 抽象基類(ABCs)
- [23] 繼承 — 你所不知道的
- [24] 繼承 — 私有繼承和保護繼承
- [27] 編碼規范
- [28] 學習OO/C++
- [31] 引用與值的語義
- [32] 如何混合C和C++編程
- [33] 成員函數指針
- [35] 模板 ?
- [36] 序列化與反序列化
- [37] 類庫