新闻详情

新闻详情

首页 / 资讯中心 / 详情

【C++笔记】从 C 到C++:核心过渡 (上)

发布时间:2026/10/1 12:41:51来源:尧图网络
【C++笔记】从 C 到C++:核心过渡 (上)
前言C 和 C 的关系经常被两种极端说法描述一种说C 就是 C 加上了类另一种说C 是全新的语言C 的写法都不作数。两种都不准确。C 确实脱胎于 C早期叫 C with Classes但它一开始就引入了不同的抽象手段引用、重载、模板、命名空间、构造与析构函数、异常安全exception safety。这些东西不是语法糖它们改变了写代码的基本思路。本文是上篇只讲从 C 迁移到 C 时最先需要建立的四个核心观念头文件与标准库的名字体系、引用reference与指针的分工、函数重载与名字修饰name mangling、类型系统上的几处补强bool、nullptr、const。类与对象、构造析构、资源管理留到后面。一个贯穿全文的原则C 的重点不是C 语法加 class而是用类型和生命周期去管理资源。想明白这一点后面很多为什么非得这么写的规则就不用死记了。另外提醒一句本文所有 C 代码示例都以gcc编译为准C 代码以 C17 为准GCC 13 / Clang 17 / MSVC 19.3x 都应能通过。一、头文件与标准库stdio.h 与 cstdio 的差别C 的标准库头文件是stdio.h、stdlib.h、string.h这种带.h的名字。C 把标准库的名字收进了std命名空间同时提供了两套头文件名字C 名字C 名字内容所在命名空间说明stdio.hcstdiostd推荐用cstdio函数在std里stdlib.hcstdlibstd含malloc、free、atoi等string.hcstringstd含strlen、memcpy等math.hcmathstd含sqrt、fabs等stdbool.h无对应——C 自带bool不需要它这里有个常被忽略的细节标准里写得很清楚cstdio是否同时把名字放进全局命名空间是实现定义的。也就是说#include cstdio之后直接写printf(...)在 GCC 的 libstdc 上通常能编过它同时也注入到全局但标准并不保证这一点。想要可移植就写std::printf(...)并且包含cstdio。// 推荐写法C17 #include cstdio #include cstring #include cmath int main() { // 显式加 std::可移植性最好 std::printf(%d\n, static_castint(std::strlen(hello))); std::printf(%.3f\n, std::sqrt(2.0)); return 0; }而stdio.h这套旧名字在 C 里也是合法的标准保留了它它保证把名字放进全局命名空间可能也放进std。所以C 头文件在 C 里能不能用的答案是能但新代码没有理由再选它。C 里还有一件 C 完全不需要的东西stdbool.h。C99 之前没有布尔类型C99 才用宏凑出一个boolC 从第一版起就有内建的bool并且true、false是关键字而不是宏。二、引用reference不是指针的语法糖C 只有指针。想在函数里改调用方的变量就得传地址、在函数里解引用。C 加了引用// C17 #include cstdio void swap_c(int* a, int* b) { // C 风格调用方要写 int t *a; *a *b; *b t; } void swap_cpp(int a, int b) { // C 风格调用方写起来像传值 int t a; a b; b t; } int main() { int x 1, y 2; swap_c(x, y); swap_cpp(x, y); // 编译器自动取地址 std::printf(%d %d\n, x, y); return 0; }引用和指针的关键差别不只是少写一个*特性指针pointer引用reference能否为空可以是空指针标准要求必须绑定到对象不存在空引用能否重新绑定可以改指向一旦绑定终身不变是否占存储占通常一个机器字语义上是别名是否占存储由编译器决定能否取地址p得到指针的地址r得到的是被引用对象的地址算术运算支持p1不支持典型用途可选的、可为空的关系函数参数、函数返回值必须保证有效有一条容易记错的规则引用本身不是对象所以你不能有引用的引用int r是右值引用不是引用的引用也不能有引用的数组。关于引用是否占存储在语言层面它只是别名但为了能作为函数参数传递编译器通常会用地址实现它。所以sizeof(T) sizeof(T)并不代表它一定不占空间——这属于实现细节取决于编译器如何降级lower。返回引用的写法在 C 里很常见因为它避免了拷贝// C17 #include string class Config { public: // 返回引用调用方拿到的是内部对象本身 std::string name() { return name_; } // const 引用版本供 const 对象调用 const std::string name() const { return name_; } private: std::string name_ default; };代价是调用方必须清楚拿到的是借来的东西一旦被引用的对象生命周期结束它就是悬垂引用dangling reference继续使用属于未定义行为undefined behavior, UB标准不保证任何行为。三、函数重载与名字修饰name manglingC 不支持同名函数同一个翻译单元里出现两个print就是重定义错误。C 支持重载overload只要参数列表不同// C17 #include cstdio void print(int v) { std::printf(int: %d\n, v); } void print(double v) { std::printf(double: %f\n, v); } void print(const char* v) { std::printf(str: %s\n, v); }编译器区分它们靠的是名字修饰把函数名、参数类型在某些 ABI 下还包括命名空间和返回类型参与编码的方式编成一个唯一的符号名。GCC/Clang 在 Linux 上用的是 Itanium C ABI上面三个函数编出来大致是_Z5printi、_Z5printd、_Z5printPKc。你可以自己验证g -c print.cpp -o print.o nm -C print.o # -C 会还原成可读形式 nm print.o # 不加 -C 就能看到修饰后的符号名而 C 编译器不做名字修饰print的符号名就是print。这就是 C/C 混编问题的根源C 编译器生成的符号名带了参数信息链接器按名字找符号时对不上。解决办法是链接规范linkage specificationextern C它告诉 C 编译器这个名字按 C 的规则修饰也就是不修饰。// c_side.h —— 可以被 C 和 C 同时包含 #ifdef __cplusplus extern C { #endif void c_function(int x); #ifdef __cplusplus } #endif__cplusplus宏只在 C 编译器下定义这是让同一个头文件被两种语言共享的标准做法。注意extern C只影响名字修饰和调用约定不影响参数类型检查它禁止的是重载不是类型安全。场景符号名Itanium ABI 示例链接器能否找到C 函数void f(int)f是C 函数void f(int)_Z1fi只有 C 侧同意用 C 规范时才能C 重载void f(double)_Z1fd与上面不冲突这正是重载的实现基础四、类型系统的补强bool、nullptr、constboolC 内建类型取值为true/false。它参与整型提升integral promotionbool转int得 0 或 1但反过来int转bool是非零即真不会帮你检查语义错误。C 里bool的sizeof由实现决定通常为 1。// C17 bool b 3; // 合法值为 true隐式转换 int i true; // 合法值为 1 // bool c abc; // 非法但历史上 C 里指针能隐式转 boolC 里字符串字面量转 bool 是合法且为 true 的 —— 一个经典陷阱 bool d abc; // 合法值为 true因为指针非空最后一行是真实存在的坑bool d abc;能编译但和你想表达的字符串内容是否为真完全无关它只是指针非空。写出来编译器通常也不会警告。nullptrC11 起类型是std::nullptr_t可以隐式转成任意指针类型和bool但不会被转成整数。这解决了 C 里NULL的老问题——NULL通常就是0于是在重载场景下会选错函数// C17 #include cstddef // NULL #include cstdio void f(int) { std::printf(f(int)\n); } void f(char*) { std::printf(f(char*)\n); } int main() { f(0); // 调 f(int)因为 0 是 int f(NULL); // 同样是 f(int)NULL 通常是 0—— 大多数编译器会给出警告但代码仍能编过 f(nullptr); // 调 f(char*)这才是传空指针的本意 return 0; }所以新代码一律用nullptr把NULL只留给必须兼容 C 的头文件。const这个关键字在 C 和 C 里的能力并不一样。C 里const主要表达我不想改但const对象不能用作数组长度C99 的变长数组是另一回事也不能替代编译期常量。C 里const整数如果由常量表达式初始化就是常量表达式constant expression可以用在数组维度、模板实参等场合constexprC11把这件事说得更明确。// C17 const int n 4; int arr[n]; // 合法n 是常量表达式 const int m some_func(); // m 是运行期常量 // int arr2[m]; // 非法m 不是常量表达式 constexpr int k 4; static_assert(k 4, ); // 合法k 可在编译期求值顺便说一下C 里关于const最容易被忽略的是const 正确性const-correctness会传染。一个成员函数如果没标constconst对象就调用不了它于是调用方不得不去掉const整条链子就散掉了。这是设计问题不是语法问题。实战一个 C 与 C 混编的最小可编译例子下面这个例子包含一个 C 源文件、一个 C 源文件和一个共享头文件演示extern C的必要性。三个文件都能直接用gcc/g编译。/* c_lib.hC 和 C 都能包含 */ #ifndef C_LIB_H #define C_LIB_H #ifdef __cplusplus extern C { #endif int c_add(int a, int b); #ifdef __cplusplus } #endif #endif /* C_LIB_H *//* c_lib.c用 C 编译器编译 */ #include c_lib.h int c_add(int a, int b) { return a b; }// main.cpp用 C 编译器编译C17 #include cstdio #include string #include c_lib.h namespace demo { // 重载C 里做不到 std::string describe(int v) { return int std::to_string(v); } std::string describe(double v) { return double std::to_string(v); } std::string describe(const std::string v) { return string v; } } int main() { int sum c_add(2, 3); // 调 C 函数靠 extern C 链接成功 std::printf(%d\n, sum); std::printf(%s\n, demo::describe(7).c_str()); std::printf(%s\n, demo::describe(1.5).c_str()); std::printf(%s\n, demo::describe(std::string(hi)).c_str()); return 0; }编译命令gcc -stdc11 -c c_lib.c -o c_lib.o g -stdc17 -c main.cpp -o main.o g main.o c_lib.o -o demo如果把c_lib.h里的extern C去掉main.o里会去找_Z5c_addii这个符号而c_lib.o里只有c_add链接阶段会报undefined reference to _Z5c_addiiGCC或unresolved external symbolMSVC。这就是名字修饰造成的、最典型的 C/C 混编报错。常见坑点坑 1以为extern C只写在一侧就够。❌ 只在main.cpp里对函数指针写extern C头文件里不写。✅ 在头文件里用#ifdef __cplusplus包起来写extern C保证声明和定义的链接规范一致。坑 2把NULL当空指针用。❌void* p NULL;或f(NULL);重载场景下可能选到int版本。✅ 一律用nullptrC11 起。C 代码里继续用NULL。坑 3const char*与char*混用。❌char* p hello;——C11 起字符串字面量是const char[]这会报错GCC 下是 error历史上是废弃警告。✅const char* p hello;。确实需要可写缓冲就用char buf[] hello;复制一份或std::string。坑 4返回局部变量的引用。❌int bad() { int x 1; return x; } // 返回悬垂引用✅ 按值返回int bad()或返回静态/堆上对象的引用并明确所有权约定。使用悬垂引用是 UB标准不保证任何行为。坑 5函数重载只改返回类型。❌int f();与double f();同时出现——重定义错误因为重载只看参数列表。✅ 改参数列表或者改函数名。返回类型不参与重载决议。坑 6以为sizeof(bool)一定是 1。❌ 用sizeof(bool)当字符串序列化格式的假设常量。✅ 标准说sizeof(bool)由实现定义通常 1但不要写进线协议。需要固定宽度用cstdint的std::uint8_t。坑 7在 C 里用malloc/free管理带构造函数的对象。❌std::string* s (std::string*)malloc(sizeof(std::string)); // 没有构造后续使用是 UB free(s);✅ 用new/delete或者更好——用std::string本身、容器、智能指针让析构自动发生。坑 8把#include cstdio后直接调printf当成可移植写法。❌ 依赖名字被注入全局命名空间这个实现行为。✅ 写std::printf。反过来如果包含的是stdio.h标准保证全局名字存在此时能不能写std::printf反而是实现定义。总结主题C 的做法C 的做法关键点头文件stdio.hcstdio名字在std全局名字是否注入是实现定义传参修改指针引用引用不能为空、不能重绑定同名函数不允许重载靠名字修饰区分空指针NULL常为 0nullptrnullptr不会误转成int布尔stdbool.h的宏内建bool指针隐式转bool是陷阱编译期常量宏 /enumconst/constexprconstexpr语义更明确动态内存malloc/freenew/delete、RAII混用是 UB从 C 到 C 的过渡本质上是从手动管理一切转向让类型系统替你记住规则引用让必须存在成为类型的一部分重载让同一抽象的不同实现有了统一的调用形式constexpr和bool让编译器能更早发现问题。上篇讲到这里剩下的类、构造析构、RAII 会在后续展开。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Madeira 跨平台兼容层实战:Wine + FEX-Emu + DXMT 与 iOS 工具链整合 2026/10/1 13:25:42

Madeira 跨平台兼容层实战:Wine + FEX-Emu + DXMT 与 iOS 工具链整合

1. 从“Madeira”说起:一个跨平台兼容层的真实项目复盘 第一次看到“Madeira”这个名字,很多人会以为是某个旅游项目或者葡萄酒品牌,毕竟热搜词里挂着 Wine。但如果你是一个长期折腾跨平台兼容层、模拟器、iOS 开发环境的人,就会立…

阅读更多 →
AI工程从零到落地:知识库问答系统全流程实战指南 2026/10/1 13:25:42

AI工程从零到落地:知识库问答系统全流程实战指南

看到“ai-engineering-from-scratch”这个标题,我第一反应不是去看它是不是又一个仓库名或者课程名,而是觉得这个词组值得认真拆开说。AI工程这个词被讨论了很多年,但真正能讲清楚“从零怎么入手”的内容并不多。市面上大多数教程要么让你直接…

阅读更多 →
Allegro学习笔记:封装库路径配置与网络表导入全流程 2026/10/1 13:25:42

Allegro学习笔记:封装库路径配置与网络表导入全流程

Allegro学习笔记这个系列,是我自己硬啃Cadence工具链的记录,第一篇讲了环境安装,这篇是系列第二篇,专门聊两件事:封装库路径指定和网络表导入。其实这两件事在Allegro的使用中属于“基础设施”。很多从OrCAD Capture转…

阅读更多 →
Madeira 跨平台兼容层:Wine、FEX-Emu 与 DXMT 三层翻译链路解析 2026/10/1 13:25:42

Madeira 跨平台兼容层:Wine、FEX-Emu 与 DXMT 三层翻译链路解析

1. 从"Madeira"这个名字说起:一个跨平台兼容层的真实需求 第一次看到"Madeira"这个项目名,很多人会以为是某个度假岛屿或者葡萄酒品牌——毕竟马德拉岛确实以加强型葡萄酒出名。但结合关键词里的 Wine、FEX-Emu、DXMT、x86-64 来看&…

阅读更多 →
MIMO-OFDM仿真从零搭建:链路建模、参数配置与避坑指南 2026/10/1 13:25:42

MIMO-OFDM仿真从零搭建:链路建模、参数配置与避坑指南

简介:这份资源面向无线通信、信号处理方向的学生与工程人员,提供一套可运行的MIMO-OFDM系统仿真代码,用于理解多天线与正交频分复用结合后的传输链路与性能评估方法。压缩包共2个文件,均为.m脚本,整体约4KB&#xff0c…

阅读更多 →
基于FEX-Emu、Wine与DXMT的跨平台兼容层:在iOS上运行x86-64 Windows应用 2026/10/1 13:25:29

基于FEX-Emu、Wine与DXMT的跨平台兼容层:在iOS上运行x86-64 Windows应用

1. 从“Madeira”说起:一个跨平台兼容层的真实需求 “Madeira”这个词在技术圈里并不算高频,但它背后指向的东西非常具体——一个围绕 FEX-Emu、Wine、DXMT 构建的跨平台兼容方案,目标场景落在 iOS 与 x86-64 之间的指令翻译与图形转换 上…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉