处于一些不可抗力,历史原因,我们不得不在C++中使用一些C语言代码,比如Linux 系统调用。在现代C++出现之前,大部分人当谈到“C与C++的区别是什么”时,普遍除了回答面向对象的类特性、泛型编程的模板特性之外,就没有其他看法了,甚至直接回答差不多。 这当然是错误的,C++并不是C的一个超集,如下图大致回答了C和C++相关的兼容情况:
在编写 C++ 时,也应该尽可能
的避免使用诸如void*
之类的程序风格。而在不得不使用 C 时,应该注意使用 extern "C"
这种特性,将 C 语言的代码与 C++ 代码进行分离编译,再统一链接这种做法,例如:
// foo.h
#ifdef __cplusplus
extern "C" {
#endif
int add(int x, int y);
#ifdef __cplusplus
}
#endif
// foo.c
int add(int x, int y) {
return x+y;
}
// 1.1.cpp
#include "foo.h"
#include <iostream>
#include <functional>
int main() {
[out = std::ref(std::cout << "Result from C code: " << add(1, 2))](){
out.get() << ".\n";
}();
return 0;
}
应先使用 gcc 编译 C 语言的代码:
gcc -c foo.c
编译出 foo.o 文件,再使用 clang++ 将 C++ 代码和 .o 文件链接起来(或者都编译为 .o 再统一
链接):
clang++ 1.1.cpp foo.o -std=c++2a -o 1.1
当然,你可以使用 Makefile 来编译上面的代码:
C = gcc
CXX = clang++
SOURCE_C = foo.c
OBJECTS_C = foo.o
SOURCE_CXX = 1.1.cpp
TARGET = 1.1
LDFLAGS_COMMON = -std=c++2a
all:
$(C) -c $(SOURCE_C)
$(CXX) $(SOURCE_CXX) $(OBJECTS_C) $(LDFLAGS_COMMON) -o $(TARGET)
clean:
rm -rf *.o $(TARGET)
注意:Makefile 中的缩进是制表符而不是空格符,如果你直接复制这段代码到你的编辑器中,制表符可能会被自动替换掉,请自行确保在 Makefile 中的缩进是由制表符完成的。
现在,我们来完整理解一下这个头文件:
// foo.h
#ifdef __cplusplus
extern "C" {
#endif
// *** 一些代码 ***
#ifdef __cplusplus
}
#endif
这种类型的头文件可以被#include到C和C++文件中进行编译。由于extern "C"
可以抑制C++对函数名、变量名等符号进行名称重整,因此编译出的C目标文件和C++目标文件中的变量、函数名称等符号都是相同的(否则不相同),链接器可以可靠的对两种类型的目标文件进行链接。
__cplusplus
宏通常被定义为一个整型值(比以往标准中更大的值)。如C++03中__cplusplus
为199711L
,C++11中被定义为201103L
。如果想确定支持C++11编译器进行编译时,那么可以按照如下方法进行检测:
#if __cpluscplue < 201103L
#error "should use c++11 implementation"
#endif
上面用了预处理指令#error
,这就使得不支持C++11的代码编译立即报错并终止编译。
other:
可以通过预处理指令#if
和#error
配合,使得在预处理阶段进行断言。比如:
#ifndef _COMPLEX_H
#error "Never use <bits/cmathcalls.h>" directly; include <complex.h> instead
#endif
如果程序员直接包含<bits/cmathcalls.h>
进行编译就会引发错误,#error指令会将后面的语句输出,提醒用<complex.h>
而不是 <bits/cmathcalls.h>
。这样,通过预处理的断言,库发布者就可以避免一些头文件的引用问题
面试题: 如何判断程序是C程序还是CPP程序
#include <iostream>
using namespace std;
int main()
{
#ifdef __cplusplus
cout << "c++" << endl;
#else
cout << endl;
#endif
}
进一步阅读的参考文件(待完成)