1.什么时候混合C和c++代码?
以下是一些要点:
- 1.编译 main() 时必须使用 C++ 编译器(例如,用于静态初始化)
- 2.您的 C++ 编译器应该指导链接过程(例如,以便它可以获取其特殊库)
- 3.您的 C 和 C++ 编译器可能需要来自同一供应商并具有兼容的版本(例如,因此它们具有相同的调用约定)
此外,您需要阅读本节的其余部分,以了解如何使您的 C 函数可被 C++ 调用和/或您的 C++ 函数可被 C 调用。
顺便说一句,还有另一种方法可以处理这整个事情:使用 C++ 编译器编译所有代码(甚至是 C 风格的代码)。这几乎消除了混合C和c++的需要,而且它会使您更加小心(在你的c风格代码可能发现一些错误)。缺点是,您需要以某种方式更新C风格的代码,主要是因为c++编译器比C编译器更仔细/挑剔。重点是,清理C风格代码所需的工作可能比混合C和c++所需的工作要少,而且作为奖励,您可以清理C风格代码。显然,如果你不能改变你的c风格代码(例如,如果它来自第三方),你没有太多的选择。
2.如何从 C++ 调用 C 函数?
只需声明C函数extern “C”(在你的c++代码中)并调用它(从你的C或c++代码中)。例如:
// C++ code
extern "C" void f(int); // one way
extern "C" { // another way
int g(double);
double h();
};
void code(int i, double d)
{
f(i);
int ii = g(d);
double dd = h();
// ...
}
函数的定义如下:
/* C code: */
void f(int i)
{
/* ... */
}
int g(double d)
{
/* ... */
}
double h()
{
/* ... */
}
请注意,使用的是 C++ 类型规则,而不是 C 规则。因此,您不能使用调用声明为 extern “C” 函数的错误数量的参数。例如:
// C++ code
void more_code(int i, double d)
{
double dd = h(i,d); // error: unexpected arguments
// ...
}
3.如何从 C 调用 C++ 函数?
只需声明c++函数extern “C”(在你的c++代码中)并调用它(从你的C或c++代码中)。例如:
// C++ code:
extern "C" void f(int);
void f(int i)
{
// ...
}
现在 f() 可以这样使用:
/* C code: */
void f(int);
void cc(int i)
{
f(i);
/* ... */
}
当然,这仅适用于非成员函数。如果要从 C 调用成员函数(包括虚函数),则需要提供一个简单的包装器。例如:
// C++ code:
class C {
// ...
virtual double f(int);
};
extern "C" double call_C_f(C* p, int i) // wrapper function
{
return p->f(i);
}
现在 C::f() 可以这样使用:
/* C code: */
double call_C_f(struct C* p, int i);
void ccc(struct C* p, int i)
{
double d = call_C_f(p,i);
/* ... */
}
如果要从 C 调用重载函数,则必须为要使用的 C 代码提供具有不同名称的包装器。例如:
// C++ code:
void f(int);
void f(double);
extern "C" void f_i(int i) { f(i); }
extern "C" void f_d(double d) { f(d); }
现在可以像这样使用 f() 函数:
/* C code: */
void f_i(int);
void f_d(double);
void cccc(int i,double d)
{
f_i(i);
f_d(d);
/* ... */
}
请注意,即使您不能(或不想)修改 C++ 头文件,这些技术也可用于从 C 代码调用 C++ 库。
4.如何在 C++ 代码中包含标准 C 头文件?
要#include 标准头文件(例如 ),您不必做任何不寻常的事情。例如:
// This is C++ code
#include <cstdio> // Nothing unusual in #include line
int main()
{
std::printf("Hello world\n"); // Nothing unusual in the call either
// ...
}
如果您来自 C,则 std::printf() 调用的 std:: 部分可能看起来不寻常,但这是用 C++ 编写的正确方法。
如果您正在使用c++编译器编译C代码,您不希望将所有这些调用从printf()调整为std::printf()。幸运的是,在这种情况下,C代码将使用老式的头文件<stdio.h>,而不是新式的头文件,命名空间的魔力将会处理好其他一切:
/* 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 头文件,我们为您提供了一些不同的指南。有两种情况:要么你不能改变头文件,要么你可以改变头文件。
5.如何在 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 头文件(例如 )和您可以更改的 C 头文件。
6.如何修改我自己的 C 头文件,以便在 C++ 代码中更容易地#include 它们?
如果你包含的C头文件不是系统提供的,如果你能够改变C头文件,你应该强烈考虑添加extern“C”{…},以便c++用户更容易在他们的c++代码中使用#include。由于C编译器无法理解extern "C"结构,所以必须将extern “C”{和}行包装在#ifdef中,这样普通C编译器就不会看到它们。
第 1 步:将以下几行放在 C 头文件的最顶部(注意:当/仅当编译器是c++编译器时,符号__cplusplus是#defined)
#ifdef __cplusplus
extern "C" {
#endif
第 2 步:将以下几行放在 C 头文件的最底部:
#ifdef __cplusplus
}
#endif
现在你可以在你的 C++ 代码中#include 你的 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 头文件(例如 )和您无法更改的 C 头文件。
7.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); // Note: nothing unusual in the call
// ...
}
8.C 代码如何调用 C++ 函数 f(int,char,float)?
f(int,char,float)将由C编译器使用extern "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 调用约定和名称修饰(例如,前面有一个下划线)。由于 C 不支持名称重载,因此您无法让 C 程序同时调用多个重载函数。
9.当使用C(C++)调用C++(C)代码,连接器为什么会报错?
如果你没有得到正确的extern“C”,有时你会得到链接器错误而不是编译器错误。这是由于与C编译器不同,c++编译器通常“修改”函数名(例如,支持函数重载)。
10.如何将c++类的对象传递给给C函数,或者将C函数传递给C++类对象?
这是一个示例(有关 extern “C” 的信息,请参阅前两个常见问题解答)。
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++中,很容易检查一个名为dp的Derived是否指向一个名为bp的Base所指向的相同对象:只需说if (dp == bp) …c++编译器自动将这两个指针转换为相同类型,在本例中是Base*,然后进行比较。根据c++编译器的实现细节,这种转换有时会改变指针值的位。
技术旁白:大多数 C++ 编译器使用二进制对象布局,导致这种转换发生在多重继承和/或虚拟继承中。然而,C++ 语言并没有强加这种对象布局,因此原则上即使使用非虚拟单继承也可能发生转换。
指针很简单:您的 C 编译器不知道如何进行指针转换,因此例如从 Derived* 到 Base* 的转换必须在用 C++ 编译器编译的代码中进行,而不是在用 C 编译器编译的代码中进行。
注意:将两者都转换为 void* 时必须特别小心,因为这种转换不允许 C 或 C++ 编译器进行正确的指针调整!即使 (b == d) 为真,比较 (x == y) 也可能为假:
void f(Base* b, Derived* d)
{
if (b == d) { // Validly compares a Base* to a Derived*
// ...
}
void* x = b;
void* y = d;
if (x == y) { // BAD FORM! DO NOT DO THIS!
// ...
}
}
如果你真的想使用 void* 指针,这里是安全的方法:
void f(Base* b, Derived* d)
{
void* x = b;
void* y = static_cast<Base*>(d); // If conversion is needed, it will happen in the static_cast<>
if (x == y) { // Validly compares a Base* to a Derived*
// ...
}
}
11.我的 C 函数可以直接访问 C++ 类对象中的数据吗?
有时。
你可以安全地从C函数访问c++对象的数据,如果c++类:
- 1.没有虚函数(包括继承的虚函数)
- 2.将其所有数据放在同一访问级别部分(私有/受保护/公共)
- 3.没有完全包含虚函数的子对象
如果c++类有任何基类(或者任何完全包含的子对象都有基类),访问数据在技术上是不可移植的,因为继承下的类布局不是由语言强加的。但是在实践中,所有c++编译器都采用相同的方法:基类对象首先出现(在多重继承的情况下按从左到右的顺序出现),然后是成员对象。
此外,如果类(或任何基类)包含任何虚函数,几乎所有c++编译器都会在对象的第一个虚函数位置或对象的最开始处放入void*。同样,这不是语言所要求的,但这是“每个人”都这样做的方式。
如果类有任何虚基类,那么它就更加复杂,可移植性也更差。一种常见的实现技术是让对象最后包含虚拟基类(V)的对象(不管V在继承层次结构中的哪个位置显示为虚拟基类)。物体的其余部分按照正常顺序出现。每个以V作为虚基类的派生类实际上都有一个指向final对象中V部分的指针。