ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

【C语言】C++和 C 的优缺点是什么?

【C语言】C++和 C 的优缺点是什么? 前言先把一句容易被忽略的话说清楚C 和 C 不是新旧两代的关系而是两种设计目标不同的语言。C 的目标是用最小的抽象层描述一台机器它的核心资产是确定性——你能大致预判每条语句会变成什么机器码。C 的目标是在保留 C 的性能与内存模型的前提下提供零开销的抽象能力它的核心资产是表达力——你能用类型系统把协议、所有权、生命周期写成编译期可检查的东西。关于这个题目本身需要说明一点它被归在【C语言】分类下但内容是两个语言的横向对比不是 C 语言内部的某个特性。本文按这个理解来写代码示例以 C 为主用gcc -stdc11就能编过涉及 C 的部分会明确标注并说明它在 C 里对应的写法是什么。最常见的两种误解正好相反一种认为C 就是加了类的 C学 C 就够了忽略了 RAII、模板、重载决议这些会彻底改变代码结构的能力另一种认为C 性能一定比 C 高忽略了优化器对restrict、内联、模板展开的处理差异也忽略了用 C 手写的动态数组也很容易写出低效版本。本文会把这两种误解都掰开讲。一、从一段真实代码看两者的差异同一个动态数组求平均的需求C 和 C 的写法差异很能说明问题。先看 C 版本/* 文件名 avg.c用 gcc -stdc11 -Wall avg.c 编译 */ #include stdio.h #include stdlib.h int main(void) { size_t cap 4; size_t size 0; int* data (int*)malloc(cap * sizeof(int)); if (data NULL) { fprintf(stderr, malloc failed\n); return 1; } for (int i 0; i 10; i) { if (size cap) { size_t newCap cap * 2; int* p (int*)realloc(data, newCap * sizeof(int)); if (p NULL) { free(data); /* 必须自己记得释放否则泄漏 */ return 1; } data p; cap newCap; } data[size] i * i; } long sum 0; for (size_t i 0; i size; i) { sum data[i]; } printf(sum%ld\n, sum); free(data); /* 每一个 return 路径都要照顾到 */ return 0; }这段 C 代码里资源释放的责任在程序员身上而且分散在多条退出路径上。增加一个提前return就多一个可能泄漏的点。C 里同样的需求容器的缓冲管理由标准库负责退出路径上有异常也不会泄漏// 文件名 avg.cpp需要 C17 #include iostream #include numeric #include vector int main() { std::vectorint data; for (int i 0; i 10; i) { data.push_back(i * i); } const long sum std::accumulate(data.begin(), data.end(), 0L); std::cout sum sum \n; }注意这里的差别不是谁短而是失败路径的数量C 版本没有需要人工维护的清理分支。这正是 RAII 的价值所在。二、C 的优势第一是可预测性。C 的语言构造与机器模型的对应关系很直接结构体就是一段连续内存函数调用就是一个调用指令。没有隐式构造、没有析构、没有重载决议、没有模板实例化带来的代码膨胀因此审计二进制体积和行为时更容易。第二是 ABI 稳定与互操作性好。C 的对象表示与调用约定在主流平台上高度稳定几乎所有语言都提供与 C 互操作的机制。一个典型证据是C 标准甚至专门提供了extern C这个链接规范linkage specification让 C 能按 C 的规则导出符号。第三是工具链简单、编译快。单个.c文件可以独立编译成目标文件几乎没有需要全程序信息才能完成的语义决策。这使得 C 的增量构建和大规模并行构建极其高效。第四是语言本身小。完整的 C 语言规范比 C 小一个数量级团队约定的分歧点少新成员上手成本低。三、C 的优势第一是零开销抽象。C 的设计原则之一是不用就不付代价you dont pay for what you dont use。模板在编译期展开内联后与手写代码等价std::vector的operator[]与裸数组下标在优化后没有差异。第二是类型系统能表达约束。C 里这个指针不能为空只能写在注释里C 里可以用引用表达一定非空用const表达只读用enum class表达有作用域的枚举用override让拼写错误变成编译错误。这些是让大型代码库能长期维护的关键。第三是资源管理自动化。这是 C 相对 C 最大的工程收益。析构函数在作用域退出、异常展开、容器整体销毁时都会被调用因此文件句柄、锁、网络连接、堆内存都可以绑定到对象上不再依赖记得写清理代码。第四是标准库的广度。容器、算法、字符串、正则、并发、文件系统、时间日期C 标准库的覆盖面远超 C 的libc。四、代价与取舍优点都是要付代价的这一节把代价说清楚。维度CC编译速度快翻译单元之间耦合小慢模板与头文件导致重复实例化二进制体积可预测模板/内联可能膨胀也可能更小语言复杂度小规则少大重载决议与模板规则复杂学习曲线平缓但要求懂机器陡峭且存在大量历史遗留写法内存安全全靠程序员靠 RAII 与智能指针也可以很安全大型项目维护需要严格规范约束类型系统可分担保与其它语言互操作事实标准需要extern C导出 C 接口运行时依赖极小可 freestanding常需要标准库但可裁剪一个必须讲清的技术点是名字修饰name manglingC 编译器把函数名原样写进目标文件符号表而 C 为了支持重载会把参数类型编码进符号名。这就是为什么 C 代码要调用 C 库时必须写extern C——它告诉编译器这部分声明按 C 的链接规范处理。// 文件 main.cpp需要 C17与 C 编译出的对象文件一起链接 #include cstdio extern C int add(int a, int b); // 定义在 add.c 中按 C 规则修饰 int main() { std::printf(add%d\n, add(2, 3)); }/* 文件 add.c用 gcc -stdc11 -c add.c 编译 */ int add(int a, int b) { return a b; }gcc -stdc11 -c add.c -o add.o g -stdc17 main.cpp add.o -o demo ./demo头文件通常写成#ifdef __cplusplus加extern C的形式这样同一份声明能同时被 C 和 C 使用。常见坑点❌ 认为C 编译出来的程序一定比 C 慢。✅ 性能取决于算法与数据结构的选择。C 的模板与内联有时能比 C 里靠函数指针实现的泛型更快反过来C 也能写出很慢的代码。要比较就用自己项目的真实负载去测而不是凭语言下结论。❌ 在 C 代码里用//注释、变长数组、混合声明与语句然后拿去用严格的 ANSI C 编译器编译。✅ 先确认目标语言的版本如-stdc11、-stdc99并按该版本的语法写C89 不支持//注释。❌ 把.c文件直接交给 C 编译器编译然后困惑于void*不能隐式转成T*。✅.c交给 C 编译器、.cpp交给 C 编译器C 里malloc的返回值必须显式转换且不建议在 C 中用malloc。❌ 在 C 里用malloc/free管理含有构造函数的对象。✅ 用new/delete或智能指针因为malloc只分配内存、不调用构造函数free也不调用析构函数。❌ C 调用 C 库时忘记extern C链接期报找不到符号看到的却是一个被修饰过的名字。✅ 在 C 侧用extern C包裹 C 的头文件声明这是名字修饰规则差异导致的不是库没编译进去。❌ 用reinterpret_cast或 C 风格强制转换在int*与char*之间来回转然后按对齐要求更高的类型访问。✅ 这类访问涉及对齐违规是 UB标准不保证任何行为需要字节级访问时用std::memcpy或C17 起std::byte表达原始字节。❌ 把 C 的结构体直接按位拷贝到 C 类对象上。✅ 只有满足特定条件如 trivially copyable的类型才能这样处理含虚函数或非平凡成员的类不满足条件强行走memcpy是 UB。总结关注点C 更占优C 更占优学习与团队上手规则少、易统一表达力强但门槛高构建速度与体积增量构建快、体积可预测模板可能膨胀资源管理需人工保证释放RAII 自动释放抽象能力只有结构体与函数指针模板、重载、继承、概念大型项目维护依赖规范与工具约束类型系统承担约束与外部系统互操作事实上的标准接口通过extern C导出接口C 和 C 的优缺点不能用谁更好来回答只能按场景回答追求极致的可预测性、极小的运行时、最广的互操作性以及团队里每个人都能完整掌握语言全貌时C 是非常合理的选择需要在几十年生命周期里持续演进、需要类型系统帮着防止错误扩散、需要现成的容器与算法时C 的抽象成本远低于它带来的维护收益。真正要警惕的不是选错语言而是用 C 的思维写 C到处裸new/delete或者用 C 的思维写 C指望某个机制替你做清理。
返回列表