一、先定调:关系与边界
C 是 1972 年前后诞生的过程式系统语言;C++ 是 1980 年代由 Bjarne Stroustrup 在 C 之上扩展出来的多范式语言。
常见说法“C++ 是 C 的超集”只对一半:
大多数写得好的 C89 代码可以当作 C++ 编译;
但 C 不是 C++ 的数学子集:有些 C 合法代码在 C++ 里非法;
C++ 也不是 C99/C11/C2x 的超集:C 的 VLA、
restrict、原生复数等 C++ 并不全盘接收。
Stroustrup 自己的表述更准确:
C++ 支持 C 支持的每一种编程技术,并且用更强的类型检查、更多记法支持来做。C++ 是“更好的 C”,但不是 C 的纯超集。
所以本质区别不是“多了几个关键字”,而是:
C 把复杂度留在程序员脑子里,C++ 把复杂度收进语言与标准库里。
二、编程范式:过程式 vs 多范式
C
过程式:数据 + 函数
组织单位是
.c / .h、函数、struct、宏设计思路:“先做什么、后做什么”
C++
过程式 + 面向对象 + 泛型 + 一定程度函数式
组织单位可以是类、模板、命名空间、模块(C++20)
设计思路可以是:
“怎么建模问题域”
“怎么在编译期消除重复”
“怎么让资源生命周期自动正确”
一句话:
C 适合写“机器要做什么”;C++ 适合写“系统是什么,以及它如何被安全地构造和销毁”。
三、类型系统:宽松直接 vs 严格抽象
这是工程上最容易被低估的一点。
C 的类型哲学
void*可以隐式转成任意指针函数未声明也能调用(老式 C)
很多转换靠程序员自觉
灵活,但错误常拖到运行时
C++ 的类型哲学
void* -> T*必须显式转换函数重载、模板、引用让“同样名字,不同类型”变得安全
const、访问控制、nullptr、强类型枚举提高静态检查能力模板在编译期做类型推导和约束(C++20 Concepts 更进一步)
例子:
// C void* p = malloc(10 * sizeof(int)); int* q = p; // 合法// C++ void* p = ::operator new(10 * sizeof(int)); int* q = p; // 编译错误 int* q2 = static_cast<int*>(p); // 必须显式说明意图C++ 不是“更啰嗦”,而是把隐式假设变成显式契约。
四、面向对象:C 用手写约定,C++ 用语言机制
C 也可以用 struct + 函数指针模拟对象:
typedef struct Animal { void (*speak)(struct Animal*); } Animal;但问题在哪?
没有访问控制,
struct字段谁都能改没有构造/析构,初始化靠约定
没有继承体系,多态靠手填函数指针
没有
this语义统一,API 容易乱
C++ 把它变成语言级能力:
class Animal { public: virtual ~Animal() = default; virtual void speak() = 0; };差异不在“能不能写对象”,而在:
维度 | C | C++ |
|---|---|---|
封装 | 注释级 / 约定级 |
|
构造 | 手写 init 函数 | 构造函数 |
销毁 | 手写 cleanup | 析构函数 |
继承 | 手动嵌套 struct |
|
多态 | 函数指针表 |
|
抽象成本 | 显式、可控 | 有虚表等成本,但可预测 |
C++ 的 OOP 不是装饰品,它是大型系统里“接口稳定、实现可替换”的基础设施。
五、内存与资源管理:手动释放 vs RAII
这是现代 C++ 最值钱的部分。
C 模型
int* arr = malloc(n * sizeof(int)); if (!arr) { /* 处理分配失败 */ } // ... free(arr);人必须记得:
每条路径都
freegoto cleanup/return前不能漏错误码返回时资源不能泄漏
C++ 模型
auto arr = std::make_unique<int[]>(n);或者:
std::vector<int> v(n);资源在对象生命周期结束时自动释放,这就是RAII:
Resource Acquisition Is Initialization
资源获取即初始化,析构即释放。
包括:
内存:
unique_ptr / shared_ptr / vector文件:
fstream锁:
lock_guard / unique_lock套接字、句柄、事务:都可以包装成对象
结果:
C 的错误模式是“忘了 free”
C++ 的错误模式是“对象所有权设计错了”
后者比前者好调试得多。
注意:C++ 仍然可以手写
new/delete,但现代 C++ 的共识是——不要手动new/delete,让对象管资源。
六、泛型编程:宏和 void* vs 模板
C 的“通用代码”通常是:
void sort(void* base, size_t nmemb, size_t size, int (*cmp)(const void*, const void*));问题:
类型信息丢失
比较函数通过函数指针调用,难内联
错误在运行时才暴露
C++ 的模板:
template<typename T> void sort(T* first, T* last);优势:
类型安全
编译器为每个类型生成专用代码
比较器可以是 lambda / functor,常被内联
衍生出 STL、Eigen、Abseil、Boost 这类库
C++ 的泛型不是“少写几行”,而是:
把运行期多态变成编译期特化,兼顾抽象与性能。
七、标准库:小内核 vs 大工业库
C 标准库
stdio.h、stdlib.h、string.h、math.h字符串是
char*没有容器、没有算法库、没有线程
多线程靠 pthread / platform API
C++ 标准库
容器:
vector / map / unordered_map / set算法:
sort / find / transform / reduce字符串:
std::string并发:
std::thread / mutex / atomic / futureI/O:
iostream、文件流C++17 起:
std::filesystemC++20 起:ranges、concepts、modules、coroutines
所以 C 项目常看到:
自研链表、哈希表、缓冲池、日志库、线程池
C++ 项目常看到:
业务代码直接用 STL,把精力放在领域模型上
这也是为什么:
C 写“组件”,C++ 写“系统”。
八、错误处理:错误码 vs 异常 + 不变式
C
FILE* f = fopen("a.txt", "r"); if (!f) { perror("open"); return -1; }特点:
错误码贯穿所有调用层
业务逻辑和错误处理混杂
忘了检查返回值就埋雷
C++
try { do_something(); } catch (const std::runtime_error& e) { log(e.what()); }但注意:现代 C++ 不是到处抛异常:
底层库、游戏引擎、交易系统常禁异常
高层业务可用异常
很多场景用
std::expected(C++23 起更正式)、错误码 + 强类型返回
C++ 的真正进步是:
错误不再是“约定”,而是类型/控制流的一部分。
九、名字与接口组织:全局平面 vs 命名空间/模块
C 没有命名空间,大型 C 项目靠前缀:
redis_free http_parse json_object_getC++ 有:
namespace net { class Socket; }C++20 还有 modules,解决头文件宏污染、重复解析、编译慢的问题。
这对大团队很关键:
C 的接口边界靠文件名和前缀
C++ 的接口边界可以靠命名空间、类、模板、模块
十、兼容性与坑:哪些 C 不是 C++
典型不兼容点:
void* p; int* q = p;—— C 合法,C++ 非法函数隐式声明 —— C 老代码常见,C++ 不允许
sizeof('a')—— C 里是sizeof(int),C++ 里是sizeof(char)C++ 关键字更多:
class、virtual、new、template、namespace不能作为变量名C 支持 VLA(C99,部分实现),C++ 不支持
C 有
restrict,C++ 没有标准等价物C 的结构体标签和类型名空间分离,C++ 里
struct Foo和Foo更接近同一类型名
所以“把.c改成.cpp就能用”在玩具程序里成立,在老项目里常翻车。
十一、性能:C 更快吗?
结论先给:
同水平程序员、同算法、同优化等级下,C 和 C++ 性能基本同级。
C++ 的抽象可以做到“零成本抽象”。
std::vector不等于慢虚函数有成本,但是可预测
模板常常比
void*+ 函数指针更快,因为可内联异常在部分平台有体积/分支成本,所以高频交易/游戏内核会禁用
C++ 真正的性能陷阱是:无脑继承、深拷贝、过度包装、模板递归爆炸
C 的性能优势主要来自:
语言小
运行时几乎为零
没有异常、RTTI、虚表等“可选负担”
C++ 的性能优势来自:
内联、特化、move 语义
编译期计算
更高层抽象但不牺牲机器效率
所以不是“C 快,C++ 慢”,而是:
C 的开销你看得见,C++ 的开销你得懂它才看得见。
十二、适用场景:别用锤子拧螺丝
用 C 更合适
操作系统内核、Bootloader
驱动、裸机嵌入式、MCU
跨语言 ABI 接口层(C 是事实标准)
极端资源受限环境
需要最小编译器依赖、最大可移植性的场景
用 C++ 更合适
游戏引擎
浏览器内核
数据库引擎
高频交易
图形图像、音视频管线
大型后台服务
需要容器/算法/并发/长期维护的系统
经验法则:
越靠近硬件、越怕运行时依赖 → 选 C
越靠近复杂业务、越怕人工管资源 → 选 C++
十三、学习路径:先 C 还是先 C++?
如果你目标是“真正懂系统”:
先学 C:指针、栈/堆、结构体、内存布局、ABI、调用约定
再学 C++:类、构造/析构、引用、重载、模板、RAII、STL
最后学现代 C++:智能指针、move、lambda、concepts、ranges
如果你目标是“做应用/游戏/工程”:
可以直接学现代 C++
但别只学语法,要学所有权与生命周期
最差的路线是:
用 C 的思维方式写 C++:
new满天飞、裸指针传参、手动delete或者用 C++ 的复杂特性写 C:硬上虚函数、模板、异常,却跑在 8KB RAM 的 MCU 上
十四、一句话总结
C 是一门让你理解机器的语言;C++ 是一门让你在理解机器之后,管理复杂系统的语言。
C 的哲学:信任程序员,给最小抽象
C++ 的哲学:在几乎不损失性能的前提下,把复杂性关进类型系统和标准库
所以:
C++ = C 的底层能力 + OOP + 泛型 + RAII + STL + 更强的静态约束
但不是“C 加上 class”这么肤浅。