C 与 C++ 的真正区别在哪里?

2026-08-12 09:10:49 RAIZ

C 和 C++,看起来只有两个加号的差别。

睿智创新RAIZ,一体化IT服务提供商

但这掩盖了一个事实——它们是两门完全不同的语言,而不是一个包含另一个。


一、真正拉开差距的,有三件事

C++ 不是带类的 C,即使你把 class 从 C++ 里拿掉,C++ 还有RAII、模板、constexpr、移动语义、智能指针、现代类型系统。这些和面向对象没有半毛钱关系。

所以谈 C 和 C++ 的区别,得从底层的三件事说起。

1、谁管资源的释放

C 自己管。

malloc 和 free 要配对,fopen 和 fclose 要配对,pthread_mutex_lock 和 unlock 要配对。少配一个,就是泄漏或者死锁。

C++ 编译器管。

你写 init,编译器自动生成 release。对象离开作用域的那一刻,析构函数自动执行,资源释放归还。你不需要在每个 return 分支、每个 continue、每个抛异常的地方手动清理。

这不是语法糖,这是一个范式的根本转变。

用行话说,叫 RAII,直白点就是「出了作用域,资源一定还」。

// C 每个分支都要记得 free
int process(const char* path) {
    FILE* fp = fopen(path, "r");
    if (!fp) return-1;
    char* buf = malloc(BUFSIZ);
    if (!buf) {
        fclose(fp);  // buf 分配失败,fp 还没关,必须在这里关
        return-1;
    }
    // ... do work ...
    free(buf);
    fclose(fp);
    return0;
}
// C++ 编译器替你做 release
void process(const char* path) {
    std::unique_ptr<FILE, decltype(&fclose)fp(
        fopen(path, "r"), fclose)
;
    std::vector<charbuf(BUFSIZ);
    // ... do work ...
    // fp 和 buf 在作用域结束时自动清理
    // 即使中途抛异常,清理仍然执行
}

显然 C++ 的做法更人性,把纪律问题变成了更可靠的编译器问题。

具体到资源类型,智能指针是这个逻辑的延伸:unique_ptr 表达独占所有权,shared_ptr 表达共享所有权,weak_ptr 打破循环引用。这些东西在 C 里没有对应物,只能靠文档和团队纪律来约束。而文档和纪律的效力,大家在真实项目里都清楚。

2、谁在编译期替你干活

C 的计算,全部发生在运行期。编译器只负责翻译,不负责求解。

C++ 把编译器变成了一台能跑程序的虚拟机。

constexpr int fib(int n) {
    return (n <= 1) ? n : fib(n-1) + fib(n-2);
}

constexpr int f10 = fib(10);   // 编译器算出 55,程序里直接是常量
int f = fib(10);               // 运行期执行,编译器根据参数自动选择

大量计算可以从运行期卸载到编译期,在极端性能敏感的场景里,这是数量级的差异。

C 靠宏做编译期计算。但宏是文本替换,没有类型、没有调用语义、没有返回值校验。

constexpr 是宏的安全替代品,同时保留了编译期求值的能力。

C++17 的 constexpr if 让编译器根据类型选择编译哪个分支:

template<typename T>
auto process(const T& val) 
{
    if constexpr (std::is_integral_v<T>) {
        return val * 2;
    } else {
        return val;
    }
}

未命中的分支根本不编译。

编译器不会为整数类型生成处理浮点数的代码,也不会为浮点数生成整数逻辑。这叫无分支代码生成,C 靠预处理器做不完全的近似。

static_assert 在编译期卡死

static_assert(sizeof(void*) == 8"64-bit only");

条件不满足,编译器直接报错,程序根本生成不了。

C 的 assert 是运行时的,程序跑起来了才发现问题,代价完全不同。

3、类型系统替谁把关

C 的类型检查是建议性的,C++ 的类型检查是强制性的。

典型的例子是 void 指针。

// C,void* 隐式转任意指针,合法,编译器不报警
void* p = malloc(100);
int* ip = p;
// C++,隐式转换直接报错
void* p = malloc(100);
int* ip = p;  // 编译报错误,必须 static_cast

void* 在 C 中是一个隐形的类型安全漏洞,可能随时触发。

再比如枚举。

C 的 enum 可以隐式转 int,你把 99 赋给一个 Color 变量,编译器一声不吭。

C++11 的 enum class 强制你显式转换,把这个漏洞焊死。

函数重载也是。C++ 允许同名函数处理不同类型,编译器根据参数类型自动选择实现,不需要你手动写 add_int() 和 add_double()。C 做不到这一点,是因为 C 的链接器只看函数名字,不看参数类型。

C++ 编译器把参数类型编码进符号名,所以链接器能看到不同重载版本的区别。这也解释了 extern "C" 的存在意义,它告诉 C++ 编译器用 C 的命名规则,不要 Mangling,从而实现跨语言调用。


二、C++ 额外的那些东西,不是装饰

上面三件事是地基。

在地基之上,C++ 还有一层功能,改变了写代码的方式。

1、移动语义

C++11 的右值引用,让大型容器返回时不需要拷贝,资源在编译器层面转移所有权。

std::vector<intv1(1000000);
std::vector<int> v2 = std::move(v1);  // 三个指针交换,零拷贝

C 没有这个能力。返回大型容器必然触发拷贝,或者走指针。

2、Lambda 表达式

C++11 引入匿名函数,让回调函数定义在调用处,上下文不断裂。

std::sort(v.begin(), v.end(),
    [](int a, int b) { return a > b; });  // 定义和使用在一起

C 只能定义一个命名函数,传函数指针过去。

3、现代标准库容器

std::vector 替代 C 的手动数组管理,自动扩容,析构自动释放。

std::string 替代 char* + 手动字符串处理。std::map/std::unordered_map 替代手写哈希表。

std::regex 替代 POSIX 正则或者手写状态机。

这些都是语言能力的外化。没有模板,就没有 std::sort。C 的 qsort 靠 void* + 函数指针,编译器无法内联比较函数,无法根据类型做优化,速度差距是实测可感的。


三、谁该用哪个

1、C

操作系统内核、驱动、Bootloader。

这个层级需要最小运行时,C 的极简启动路径是优势,C++ 的抽象层在固件场景是负担。

Linux 内核、RTOS、Bare Metal,这些地方 C 是标准。

微控制器裸机开发。极端资源受限环境下,C++ 的异常机制、虚表、typeinfo 都是额外内存开销。

现代嵌入式里 C++ 份额在上升,但在最底层 C 仍是主流。

跨语言胶水层。Python/Go/ Rust 调用 native 库,用 C 写接口层是标准做法。

C 的 ABI 是跨语言的事实标准。

2、C++

游戏引擎。

Unreal Engine、Unity 核心、CryEngine 全是 C++。

高性能渲染、物理模拟、网络同步同时运行,虚函数开销完全可接受,RAII 管理对象生命周期,移动语义传递大型容器。

高频交易。

纳秒级延迟要求下,模板元编程把计算卸载到编译期,RAII 管理连接池,移动语义消除大型数据结构拷贝,constexpr 把常量表达式在运行前算完。这些能力叠加,是 C 给不了的。

浏览器引擎和大型桌面应用。

Chromium 的 Blink 渲染引擎有几万到几十万个 DOM 对象,没有 RAII 根本管不住内存泄漏。Qt 桌面应用同理。

科学计算和图形处理。

Eigen、OpenCV、现代神经网络推理框架的核心。


四、一个判断框架

不是哪个更好,是哪个更合适。

维度
选 C
选 C++
团队规模
1 到 3 人,技术深度高
5 人以上,有代码规范
运行时约束
无 OS 或 Bare Metal
有 OS,有标准库
维护周期
一次性或短期
3 年以上长期
内存安全要求
可接受手工管理
必须 RAII 保证
代码规模
几千行以内
万行以上

C++ 并不比 C 慢。由于模板允许编译器生成更精确的特化代码,加上 RAII 减少了手工管理中的边界检查代码,C++ 程序在多数场景下性能优于等效的 C 实现。

C 的优势从来不是更快,而是更少。更少的抽象层、更少的运行时依赖、更少的编译前提。用 C 写东西,你要理解硬件;用 C++ 写东西,你仍然要理解硬件,但你的代码可以更安全。

注:转载文章来源于网络,版权归原作者或企业所有,侵删!


我要咨询