C++ 并非 C 的超集,但它在设计之初就致力于与 C 高度兼容,并在此基础上增加了面向对象、泛型编程等现代特性,理解它们之间的“兼容”与“不兼容”之处,对于编写高质量、可移植的代码至关重要。

核心观点:兼容性是目标,但不是绝对
可以把 C++ 看作是 C 的一个“扩展和进化版本”,大部分标准的 C 代码可以在 C++ 编译器下直接编译运行,但存在一些关键的差异和不兼容点。
兼容的部分(C++ 继承自 C 的特性)
C++ 保留了 C 语言的核心特性,这部分代码通常是兼容的。
-
基本语法:
- 数据类型(
int,char,float,double,void) - 运算符(算术、关系、逻辑、位运算等)
- 控制流(
if-else,for,while,do-while,switch-case,goto,break,continue) - 函数定义和调用方式
- 数据类型(
-
内存管理:
(图片来源网络,侵删)- 栈上分配的变量(与 C 相同)
- C 风格的堆内存管理:
malloc(),calloc(),realloc(),free(),在 C++ 中,这些函数仍然可用,并且可以与 C++ 的new/delete混用(但不推荐,容易导致内存管理混乱)。
-
预处理指令:
#include,#define,#ifdef,#ifndef,#endif,#if,#else等宏和条件编译指令在 C++ 中都有效。
-
标准库:
- C++ 标准库包含了 C 标准库的大部分内容,通常位于
<cname>头文件中(C 的<stdio.h>在 C++ 中是<cstdio>)。 printf(),scanf(),fopen(),fclose(),strlen(),strcpy(),malloc(),free()等函数在 C++ 中依然可用。
- C++ 标准库包含了 C 标准库的大部分内容,通常位于
示例:兼容的代码 这段代码在 C 和 C++ 中几乎完全一样,可以无缝编译。
// compatible.c / compatible.cpp
#include <stdio.h> // 或 #include <cstdio>
#define PI 3.14159
int main() {
int radius = 5;
float area = PI * radius * radius;
if (radius > 0) {
printf("The area of a circle with radius %d is: %f\n", radius, area);
} else {
printf("Radius must be positive.\n");
}
return 0;
}
不兼容和存在差异的部分(“坑”所在)
这是最需要注意的地方,直接将 C 代码拿到 C++ 编译器中编译,可能会遇到错误或意想不到的行为。
关键字
C++ 引入了许多新的关键字,这些在 C 中是有效的标识符,但在 C++ 中是保留字,不能用作变量名、函数名等。
| C++ 关键字 (C 中不是) | 描述 |
|---|---|
class |
定义类 |
private |
私有访问控制 |
public |
公有访问控制 |
protected |
受保护访问控制 |
virtual |
虚函数,用于多态 |
template |
模板,用于泛型编程 |
typename |
在模板中声明类型参数 |
try, catch, throw |
异常处理 |
new, delete |
内存运算符(C++ 推荐) |
this |
指向当前对象的指针 |
bool, true, false |
布尔类型和值 |
explicit |
防止隐式类型转换的构造函数 |
friend |
友元函数/类 |
namespace |
命名空间 |
示例:不兼容
// C 中合法,C++ 中非法 int class = 10; // 错误:'class' 是 C++ 关键字
类型检查更严格
C++ 是强类型语言,其类型检查比 C 更严格。
-
*`void
的使用**:在 C 中,void类型的指针可以不经转换就赋值给任何其他类型的指针,在 C++ 中,void必须先进行static_cast或reinterpret_cast` 转换才能赋值给其他类型的指针。// C 中合法 void* ptr; int* int_ptr = ptr; // C++ 中会报错 // C++ 中必须这样写 int* int_ptr = static_cast<int*>(ptr);
-
隐式类型转换:C++ 对隐式类型转换的限制更多,以防止意外的数据丢失。
// C 中可能合法(有警告),C++ 中更严格 // 将 double 赋给 float 可能需要 explicit cast
struct 和 union 的差异
在 C 中,struct 和 union 定义的类型是独立的,访问其成员需要使用 或 ->。
在 C++ 中,struct 和 union 被视为类,除了默认访问权限是 public 外,还可以包含成员函数、构造函数、析构函数等。
函数声明
在 C++ 中,你可以不写函数的参数名,只写类型。 在 C89 标准中,函数声明也必须省略参数名,但在 C99 标准中,要求与函数定义中的参数名保持一致(或者省略),这在不同标准下行为不同,但 C++ 的规则是明确的。
const 的语义
这是两者一个非常重要的区别:
-
在 C 中:
const变量的值可以通过指针来修改。const只是一个“编译期提示”,并不保证变量在运行时不可变。 -
在 C++ 中:
const变量是真正的“常量”,存储在只读内存区。任何试图直接或间接修改它的行为都是非法的,C++ 的类型系统会对此进行检查。// C 代码 int main() { const int c = 10; int* p = (int*)&c; // 强制转换 *p = 20; // 在 C 中,这通常可以成功修改 c 的值 printf("c = %d\n", c); // 输出可能是 20 return 0; } // C++ 代码 // int main() { // const int c = 10; // int* p = (int*)&c; // *p = 20; // 编译器可能不会报错,但行为是未定义的 // // 更严格的情况下,编译器会拒绝这种转换 // printf("c = %d\n", c); // c 的值保证是 10 // return 0; // }
bool 类型
C++ 引入了 bool 类型,其值为 true 和 false,在表达式中,true 转换为 1,false 转换为 0。
在 C 中,没有内置的 bool 类型,通常用 int 代替(0 为假,非零为真)。
main 函数
虽然两者通常都是 int main(),但 C++ 标准规定 main 函数的返回类型必须是 int,这是强制的,而一些旧的 C 编译器可能允许 void main()。
C++ 对 C 的扩展和增强
除了兼容性,C++ 最重要的价值在于其扩展。
-
面向对象编程:
- 类:封装数据和行为。
- 继承:创建可重用的代码层次结构。
- 多态:通过虚函数和继承实现“同一接口,不同行为”。
-
泛型编程:
- 模板:允许编写与类型无关的代码,
std::vector,std::list等容器。
- 模板:允许编写与类型无关的代码,
-
标准模板库:
提供了大量预先编写好的高质量数据结构(容器)和算法,极大地提高了开发效率。
-
异常处理:
try,catch,throw提供了结构化的错误处理机制,比 C 中的errno和返回值更强大、更清晰。
-
命名空间:
namespace解决了大型项目中名称冲突的问题。
-
RAII (Resource Acquisition Is Initialization):
- 这是 C++ 的核心设计哲学,资源的获取(如内存、文件句柄、锁)与对象的构造绑定,资源的释放与对象的析构绑定,这是智能指针(
std::unique_ptr,std::shared_ptr)和 C++ I/O 流(std::ifstream)等现代 C++ 特性的基础,能有效避免资源泄漏。
- 这是 C++ 的核心设计哲学,资源的获取(如内存、文件句柄、锁)与对象的构造绑定,资源的释放与对象的析构绑定,这是智能指针(
如何混合编译 C 和 C++ 代码?
在实际项目中,我们常常需要将现有的 C 库集成到 C++ 项目中,或者反之,这涉及到链接规范的问题。
C++ 支持函数重载,C++ 编译器会对函数名进行名称修饰(Name Mangling),即在函数名中加入参数类型等信息,以便区分重载的函数,而 C 编译器不会进行这种修饰。
直接链接 C 和 C++ 的目标文件会失败,因为 C++ 编译器在寻找 foo(int) 时,会去查找一个像 _Z3fooi 这样的符号,而 C 库中只有 foo。
解决方案是使用 extern "C" 告诉 C++ 编译器:“这部分代码使用 C 的链接规范,不要进行名称修饰”。
场景1:在 C++ 中调用 C 函数或使用 C 头文件
// my_c_library.h (C 头文件)
#ifdef __cplusplus
extern "C" {
#endif
int add(int a, int b);
#ifdef __cplusplus
}
#endif
// my_c_library.c (C 实现)
int add(int a, int b) {
return a + b;
}
// main.cpp (C++ 使用)
#include "my_c_library.h"
#include <iostream>
int main() {
std::cout << "add(3, 4) = " << add(3, 4) << std::endl;
return 0;
}
场景2:在 C 中使用 C++ 代码(非常罕见且复杂)
这通常需要创建一个 C 兼容的“包装层”,并将 C++ 的复杂实现隐藏起来,同样需要 extern "C"。
| 特性 | C 语言 | C++ 语言 | 兼容性说明 |
|---|---|---|---|
| 基本语法 | 基础 | 继承并扩展 | 高度兼容 |
| 关键字 | 较少 | 大量新增 | 不兼容,C 的关键字在 C++ 中可用,反之不行 |
| 类型检查 | 相对宽松 | 非常严格 | 不兼容,C++ 的规则更严格 |
| *`void`** | 可自由转换 | 需要显式转换 | 不兼容 |
const |
可通过指针修改 | 真正的常量 | 语义不兼容 |
struct |
仅数据成员 | 可包含函数和成员 | 扩展,不完全兼容 |
| 内存管理 | malloc/free |
new/delete (推荐) |
malloc/free 仍可用,但不推荐混用 |
| 标准库 | C 标准库 | 包含 C 标准库 (<cname>) |
兼容,但有 C++ 的增强版本 |
| 核心范式 | 过程式 | 多范式(OOP, 泛型等) | C++ 是 C 的超集和增强 |
- C++ 不是 C 的超集,但兼容了 C 的绝大部分核心语法。
- 直接移植有风险:由于关键字、
const语义、类型检查等差异,将 C 代码不经修改就放入 C++ 编译器中编译,可能会失败或产生不同行为。 extern "C"是桥梁:它是 C++ 与 C 代码互操作的关键,用于解决链接问题。- 优先使用 C++ 特性:在 C++ 项目中,应优先使用
new/delete、std::string、std::vector、RAII 等现代 C++ 特性,而不是混用 C 风格的代码,这能带来更好的安全性、可读性和效率。
