C++ 因为兼容 C (ANSI),所以基本上直接调用头文件到时候一起编译就完了,但是 C 调用 C++ 的情况要不是从公司代码看到,我还真不知道有(话说为什么不把 main 改到 C++)。
总之,C 调用 C++ 的代码(主要是函数),通过的是 ABI(Appliction Binary Interface),也就是从二进制文件层级提供接口出来。不光是 C++,其他语言,比如 Rust,Zig 都可以通过二进制的接口出来给其他语言调用。通常能编译到二进制可执行文件的编程语言都有这个功能,例如 C 自身也可以通过 ABI 的方式给 Node.js 或者 Python 调用。
示例一:利用 vector 排序的简单示例
以下是一个简单的示例,让 C 能够”享用到” C++ 的 STL 库:
// mycode.cpp
#include <vector>
#include <algorithm>
extern "C" {
void performSort(int* array, int size) {
// 通过构造函数将数组转换到 vector
std::vector<int> vec(array, array + size);
// 使用 vector 的排序接口
std::sort(vec.begin(), vec.end());
// 将排序后的数据复制回原始数组
for (int i = 0; i < size; i++) {
array[i] = vec[i];
}
}
}
- 上面是一个 C++ 代码,通过
extern "C"对外导出 C 可以调用的函数,如果不这样做的话,C++ 编译器一般会把函数名称优化修改,导致 C 编译器无法找到函数 - 当然了,我知道 C 语言有
qsort函数,这里只是举个排序的例子展示用法
// main.c
#include <stdio.h>
extern void performSort(int* array, int size); // from mycode.cpp
int main() {
int arr[] = {3, 1, 4, 1, 5, 9, 2, 6};
int size = sizeof(arr) / sizeof(arr[0]);
performSort(arr, size);
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
return 0;
}
- 上面是一个 C 代码,通过
extern的方式声明一个函数。但是这个函数既不是其他头文件引入的,也不是本地实现的,所以通过一些来工具来追代码,大概率就追到这里为止了,能够在注释中写明函数的来源最好不过了。
g++ -c mycode.cpp -o mycode.o
gcc -c main.c -o main.o
g++ mycode.o main.o -o myprogram
- 通过编译器链接起来就行了
需要注意的是,虽然能够使用到 STL 的模板类,但是本身数据不是兼容的,因为 C 不支持对象,传进去的时候需要转换一次,传出来的时候需要再转换一次,效率肯定不高。想在 C 里使用 C++ 的对象也无可厚非,这个请看示例二。
示例二:如何处理对象的简单示例
在 C 里面保存一个 C++ 对象是不可能的,但是可以通过结构体包装和结构体指针的方式,并同时使用 create 和 destroy 来包装对象的构造和析构。如下是使用 C++ 的 queue 模板库的方法:
// queue.cpp
#include <queue>
extern "C" {
// 定义队列结构体
typedef struct Queue Queue;
// 创建队列
Queue* createQueue();
// 销毁队列
void destroyQueue(Queue* queue);
// 入队
void enqueue(Queue* queue, int element);
// 出队
int dequeue(Queue* queue);
}
struct Queue { // 💖
std::queue<int> q;
};
Queue* createQueue() {
return new Queue;
}
void destroyQueue(Queue* queue) {
delete queue;
}
void enqueue(Queue* queue, int element) {
queue->q.push(element);
}
int dequeue(Queue* queue) {
if (queue->q.empty()) {
return -1; // 队列为空时返回-1,当然这里不是很严谨
}
int element = queue->q.front();
queue->q.pop();
return element;
}
- 比较重要的点是,我们用 Queue 这个结构体包裹了
queue<int>的数据,然后将这个结构体通过 ABI 导出,在所有操作相关的函数中传入这个结构体,然后通过结构体内的成员,也就是一个queue<int>类型的对象去操作数据
// main.c
#include <stdio.h>
typedef struct Queue Queue;
extern Queue* createQueue();
extern void destroyQueue(Queue* queue);
extern void enqueue(Queue* queue, int element);
extern int dequeue(Queue* queue);
int main() {
Queue* queue = createQueue();
// 入队
enqueue(queue, 5);
enqueue(queue, 3);
enqueue(queue, 8);
// 出队并打印元素
int element;
while ((element = dequeue(queue)) != -1) {
printf("%d\n", element);
}
// 销毁队列
destroyQueue(queue);
return 0;
}
- 单从 C 代码上来看和调用 C 的函数差不多
不过,如果有不同数据类型的队列,都得包装一下,不可能像 C++ 模板那样灵活。
示例三:使用头文件的例子
上面的例子中没有使用头文件,所以必须用 extern 来声明外部函数。因为 C 和 C++ 有不兼容的地方,所以如果头文件中 #include 了 C++ 的头文件或者使用了 C++ 的特性就会导致 C 代码无法编译。
不过,C++ 中有一个宏定义 __cplusplus,在 C++ 中,因为这个宏变量被定义,所以能够区分是 C 环境还是 C++ 环境,可以通过它进行条件编译。
通过这个办法,修改例子二中的代码,将函数定义放入头文件共享。
// queue.h
#ifdef __cplusplus
#include <queue>
struct Queue {
std::queue<int> q;
};
extern "C" {
#endif
// 定义队列结构体
typedef struct Queue Queue;
// 创建队列
Queue* createQueue();
// 销毁队列
void destroyQueue(Queue* queue);
// 入队
void enqueue(Queue* queue, int element);
// 出队
int dequeue(Queue* queue);
#ifdef __cplusplus
}
#endif
此时 C++ 的代码为:
// queue.cpp
#include "queue.h"
Queue* createQueue() {
return new Queue;
}
void destroyQueue(Queue* queue) {
delete queue;
}
void enqueue(Queue* queue, int element) {
queue->q.push(element);
}
int dequeue(Queue* queue) {
if (queue->q.empty()) {
return -1; // 队列为空时返回-1
}
int element = queue->q.front();
queue->q.pop();
return element;
}
此时 C 的代码为:
#include "queue.h"
#include "stdio.h"
int main() {
Queue* queue = createQueue();
// 入队
enqueue(queue, 5);
enqueue(queue, 3);
enqueue(queue, 8);
// 出队并打印元素
int element;
while ((element = dequeue(queue)) != -1) {
printf("%d\n", element);
}
// 销毁队列
destroyQueue(queue);
return 0;
}
至于编译过程还是一样的,就是省去了 extern 的语句。