如何混合使用C和C++(上)

 
[32] How to mix C and C++ 如何混合使用CC++
(Part of
C++ FAQ Lite, Copyright © 1991-2006, Marshall Cline, [email protected])
Translator: Qiu Longbin
 
FAQs in section [32]:
·       [32.1] 混合使用CC++代码时我需要知道什么?
·       [32.2] 如何在C++代码里include一个标准C头文件?
·       [32.3] 在我的C++代码中如何include一个非系统的C头文件?
·       [32.4] 我要如何修改我自己的C头文件,以使得在C++代码中更容易地#inlcude它们?
·       [32.5] 我如何才能在我的C++代码中调用一个非系统的C函数f(int, char, float)?
·       [32.6] 如何创建一个C++函数f(int, char, float),它可以被我的C代码调用?
·       [32.7] C/C++函数被C++/C函数调用时,为何链接器给出错误信息
·       [32.8] 如何能够传递一个C++类对象到/从一个C函数?
·       [32.9] 我的C函数可以直接访问C++类对象中的数据吗?
·       [32.10] 为什么我感觉到相对于C,在C++中我更远离机器层面
 
[32.1] 混合使用CC++代码时我需要知道什么?
下面是是一些要点(尽管一些编译器厂商可能并不需要所有这些;请检查你的编译器厂商的文档):
·       你必须使用你的 C++ 编译器编译你的 main() (例如,静态初始化)
·       你应该用 C++ 编译器接管链接过程(例如,这样可以获得它的特别的库)
·       你的 C C++ 编译器可能需要来自于相同的厂商,并且是兼容的版本(这样,它们才具备相同的调用约定)
另外,你应该需要继续阅读本文剩下的部分,找出如何才能在 C++ 中调用 C 函数 和/或 如何在 C 中调用 C++ 函数。
BTW ?也有另一个途径来处理这一整个事情,即:用一个 C++ 编译器来了编译你所有的代码(甚至是你的 C 风格代码)。那样,相当程度上就排除了混合 C C++ 的需要,加上它将使得你得在你的 C 风格代码中更细心(并且更可能 希望如此! 发现一些 bugs )。下面( down-side )你需要以某种方式升级你的 C 风格代码,这基本上是由于 C++编译器比C编译器更仔细/更挑剔的原因关键是清除你的C风格代码所需的努力可能少于混合CC++,并且额外的好处是你清除了你的C风格代码。显然,如果你不能改变你的C风格代码,你无需多种选择。(比如,C风格代码是来自第三方库)。
 
[32.2] 如何在C++代码里include一个标准C头文件?
#include 一个标准头文件(比如 , 你通常不必作任何事。比如 :
 //  这是C++代码
 
 #include   
// #inlcude行没有什么不寻常的

 
 int main()
 {
   std::printf("Hello world/n");  
// 调用也没什么不寻常的

   ...

 }
如果你认为 std::printf() 调用中 std:: 部分不寻常,那么你最好 克服它( get over it 。这句话的意思是使用标准库中名字的标准方式,因此,你现在就应该习惯它。
然而,如果你正在使用 C++ 编译器编译 C 代码,你可能不想把所有的 printf() 的调用转换成 std::printf() 。幸运的是,这种情况下 C 代码可以使用旧风格的头文件 而不是新风格头文件 namespace 的怪诞。
 /* 这是C代码,这里用C++编译器编译 */
 
 #include           
/* #inlcude行没有什么不寻常的 */

 
 int main()
 {
   printf("Hello world/n");  
/* 调用也没什么不寻常的 */

   ...

 }
最后评论:如果你的 C 头文件 不是 标准库的一部分,我们为你准备有稍微不同的方针。存在两种情况 你不能改变头文件 ,或者 你可以改变头文件
 
[32.3] 在我的C++代码中如何include一个非系统的C头文件?
如果你要包含的 C 头文件不是由系统提供的,你可以把 #include 包裹在 extern “C” { /* here */ } 结构里。这就告诉 C++ 编译器在头文件中声明的函数是 C 函数。
 // 这是C++代码
 
 extern "C" {
   
// 获得声明
f(int i, char c, float x)
   #include "my-C-code.h"
 }
 
 int main()
 {
   f(7, 'x', 3.14);   
// 注意:调用没什么特别的

   ...

 }
注意:稍微不同的方针应用: 由系统提供的C头文件(如 你可以改变的C头文件。
 
[32.4] 我要如何修改我自己的C头文件,以使得在C++代码中更容易地#inlcude它们?
如果你要 include 一个不是有系统提供的 C 头文件,并且你可以修改这个 C 头文件,你应该着重考虑在此头文件中增加 extern “C” {} 逻辑,这就能使得对于 C++ 用户更容易地把它们包含进他们的 C++ 代码中去。因为 C 编译器不理解 extern “C” 结构,你必须在 #ifdef 里面包裹 extern “C” { } 行,让它们被 C 编译器忽略。
步骤 #1 :把下面三行放置在你的 C 头文件的最顶处(注意:符号 __cplusplus 只在编译器为 C++ 编译器时才被定义):
 #ifdef __cplusplus
 extern "C" {
 #endif
步骤 #2 :把如下三行放置在你的 C 头文件的最底部:
 #ifdef __cplusplus
 }
 #endif
现在你可以在你的 C++ 代码里 #includeC 头文件里而无需多余的 extern “C”
 //  这是C++代码
 
 
// 获得声明 
f(int i, char c, float x)
 #include "my-C-code.h"   
//  注意:#include 行没什么特别的

 
 int main()
 {
   f(7, 'x', 3.14);       
//  注意:调用没什么特别的

   ...

 }
注意: #include 4 中不同方式下是有缺陷( evil) 的: evil#1[1] , evil#2[2] , evil#3[3] , and evil#4。但是,它们常常仍很有用。
 
 
[32.5] 我如何才能在我的C++代码中调用一个非系统的C函数f(int, char, float)?
如果你有一个独立的 C 函数想调用,并且,由于一些原因,你不能或不想 #include 那个声明有此函数的头文件,你可以在你的 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);   
// 注意:调用没什么特别的

   ...

 }
 
 
[32.6] 如何创建一个C++函数f(int, char, float),它可以被我的C代码调用?
C++ 编译器必须使用 extern "C" 结构来知道f(int, char, float)将被C编译器调用。
 // 这是C++代码
 
 
// 使用 
extern "C"声明  f(int,char,float) :
 extern "C" void f(int i, char c, float x);
 
 
...

 
 
// 在某个C++模块中定义 
f(int,char,float):
 void f(int i, char c, float x)
 {
   
...

 }
extern “C” 行告知编译器传送给链接器的外部信息( external information )使用 C 调用约定和名字重整( name mangling )规则(比如,加一个前缀下划线)。因为名字重载( name overloading )不被 C 支持,所以你不能同时写几个被 C 程序调用的重载函数。
 
 
[32.7] C/C++函数被C++/C函数调用时,为何链接器给出错误信息?
如果你未能正确处理 extern “C” ,你将得到链接错误而不是编译错误。这归因于这样一个事实: C++ 编译器在函数名 重整( mangle )方面与 C 编译器常常不同。
请查看前面两个关于如何使用 extern “C” FAQ.
 
[32.8] 如何能够传递一个C++类对象到/从一个C函数?
这里是一个例子( extern “C” 的信息,参见前面两个 FAQs )。
Fred.h :
 /* 这个头文件可以被CC++编译器读取 */
 #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 :
 // 这是C++代码
 
 #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 :
 // 这是C++代码
 
 #include "Fred.h"
 
 int main()
 {
   Fred fred;
   c_function(&fred);
   
...

 }
c-function.c :
 /* 这是C代码 */
 
 #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 编译的代码中。
注意: 当把两者转换成 void* 时你必须特别小心,因为这种转换将不允许 C C ++ 编译器做适当的指针调整!例如(接着前面的章节),你把 dp bp 都赋值给 void* 指针 dpv bpv ,你可能获得 dpv != bpv 的情况,即使 dp == bp 。同时你会收到警告信息。
 
[32.9] 我的C函数可以直接访问C++类对象中的数据吗?(译者:符合“POD”类型,就可以了)
有时可以。
(传递一个 C++ 类对象到/从一个 C 函数的基本信息,参见前面的 FAQ
你可以安全地从 C 函数中访问 C++ 对象的数据,如果 C++ 类满足下面条件:
·       没有 virtual 函数(包括继承来的 virtual 函数)
·       所有数据都有处在同一个访问级段中( private/protected/public
·       没有完全包含( fully-contained ,译注:成员对象,即 包含 而非 聚合 )的带有 virtual 函数的子对象( subobjects
如果 C++ 类拥有很多基类(或者如果任一完全包含的子对象有多个基类),技术上而言访问其数据是不可移植的,因为继承之下的 class 布局( layout )上,语言并为强制。但是在实际上,所有 C++ 编译器都几乎以同样的方式处理:基类对象第一个出现(多继承时按从左到右的次序),然后是成员对象。
更进一步,如果类(或者任意基类)含有一些 virtual 函数,几乎所有的 C++ 编译器都放置一个 void* 在第一个 virtual 函数出现的位置或者在对象的最前面。还有就是,这些都不是语言要求的,但它是 每个编译器 都采取的方式。
如果类有 virtual 基类,这就更加复杂和移植性更差。通用的实现技术是让对象包含一个 virtual 基类对象 (V) (无论 V 作为 virtual 基类出现在继承层次结构的什么地方)。对象的其余部分以通常的次序出现。每个派生的含有 V 作为 virtual 基类的对象,实际上拥有一个指针,指向最终对象的 V 部分。(译者:更多这方面的信息参见《 Inside C++ Object Model 》)
 
 
[32.10] 为什么我感觉到相对于C,在C++中我更远离机器层面
因为你的确如此。
作为一个 OO 编成语言, C++ 允许你对问题域本身建模,这就允许你在问题域语言中编程而不是在解空间( solution domain )编程。
C 的一个强大之处在于它 没有隐藏什么机制( no hidden mechanism :你所见,即你所得。你可以读你的 C 程序并且 查看( see 每个时钟周期。 C++ 中就不是这么回事了;
一些老顽固的 C 程序员(比如我们中的一些曾)常常对这个特征很矛盾(你也可以说是 敌对 )。但是当他们完成了到 OO 思想的转变,他们就能时常体会到尽管 C++ 对程序员隐藏了一些机制,它也提供了一个抽象层和表达上的经济( economy of expression ( 译注:从成本上 ) ,这降低了维护成本又不会降低运行时性能。
C++ 并没有试图让差的程序员能够避免写出差的程序;它允许明理( reasonable )的开发者写出出众的软件。
 
 

你可能感兴趣的:(程序设计语言)