☰
C 与 C++ 深度对比:不是“C + 类”这么简单
2026/10/1 14:31:21 网站建设 项目流程

一、先定调:关系与边界

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++

封装

注释级 / 约定级

public/private/protected

构造

手写 init 函数

构造函数

销毁

手写 cleanup

析构函数

继承

手动嵌套 struct

class Derived : public Base

多态

函数指针表

virtual函数

抽象成本

显式、可控

有虚表等成本,但可预测

C++ 的 OOP 不是装饰品,它是大型系统里“接口稳定、实现可替换”的基础设施。


五、内存与资源管理:手动释放 vs RAII

这是现代 C++ 最值钱的部分。

C 模型

int* arr = malloc(n * sizeof(int)); if (!arr) { /* 处理分配失败 */ } // ... free(arr);

人必须记得:

  • 每条路径都free

  • goto 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 / future

  • I/O:iostream、文件流

  • C++17 起:std::filesystem

  • C++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_get

C++ 有:

namespace net { class Socket; }

C++20 还有 modules,解决头文件宏污染、重复解析、编译慢的问题。

这对大团队很关键:

  • C 的接口边界靠文件名和前缀

  • C++ 的接口边界可以靠命名空间、类、模板、模块


十、兼容性与坑:哪些 C 不是 C++

典型不兼容点:

  1. void* p; int* q = p;—— C 合法,C++ 非法

  2. 函数隐式声明 —— C 老代码常见,C++ 不允许

  3. sizeof('a')—— C 里是sizeof(int),C++ 里是sizeof(char)

  4. C++ 关键字更多:class、virtual、new、template、namespace不能作为变量名

  5. C 支持 VLA(C99,部分实现),C++ 不支持

  6. C 有restrict,C++ 没有标准等价物

  7. 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++?

如果你目标是“真正懂系统”:

  1. 先学 C:指针、栈/堆、结构体、内存布局、ABI、调用约定

  2. 再学 C++:类、构造/析构、引用、重载、模板、RAII、STL

  3. 最后学现代 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”这么肤浅。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询