欢迎阅读本篇学习笔记。本篇作为个人计算机专业的学习记录这里将系统梳理关于C入门的相关知识点从基础概念到代码实现逐步展开已便于后续的复习巩固。如有不足欢迎大家在评论区交流指正感谢大家的阅读与支持一、C 整体学习路线C 的学习可以拆成三大核心板块C 基础语法、STL 标准库、高阶数据结构。 基础语法是地基用来理解面向对象、内存管理、重载、继承多态这些核心思想STL 是工程开发利器封装好容器、算法、迭代器告别重复造轮子高阶数据结构则是结合 C 特性实现更加高效复杂的数据组织方式也是笔试面试高频考点。三者层层递进是学好 C 的完整路径。二、C 发展历史C 的设计者是本贾尼・斯特劳斯特卢普Bjarne Stroustrup最早在 1979 年于贝尔实验室启动相关研究。当时 C 语言以高效简洁著称但面向大型复杂项目、系统仿真、操作系统开发时缺少对代码封装、模块复用的支持项目规模变大之后维护成本很高。 他的思路不是从零造一门新语言而是保留 C 语言高性能、贴近底层的全部优势在此之上增加面向对象能力。1983 年这门语言正式命名为 Cclass、封装、继承这些核心概念就此落地。 之后多年C 慢慢走进高校课堂也被大量工业项目选用。模板、泛型编程的思想也在这个阶段慢慢成熟。 标准化工作从 1989 年启动由 ANSI 和 ISO 联合委员会负责。大名鼎鼎的 STL 标准模板库由惠普实验室团队开发它的加入极大改变了 C 的开发方式不过因为标准委员会需要仔细评估 STL 的规范也推迟了标准定稿时间。1998 年C98 正式发布这是 C 历史上第一个国际标准。2.1 C 版本迭代时间版本核心亮点1998C98首个正式标准引入 STL模板、异常、命名空间落地2003C03修复 C98 漏洞侧重规范和稳定性补充 TR1 库2011C11划时代大更新lambda 表达式、范围 for 循环、右值引用、移动语义、智能指针、线程库现代 C 的起点2014C14C11 的小幅补强泛型 lambda、自动返回值推导、二进制字面量2017C17if constexpr、折叠表达式文件系统库、string 优化简化模板编程2020C20又一个里程碑协程、Concepts 约束、模块 Module、Ranges 范围库2023C23进一步简化语法flat_map、import std、if consteval 等未来C26还在制定中社区期待网络库等重磅特性简单来说C11 是分水岭在它之前是传统 CC11 及以后我们一般叫现代 C。2.2 一个有趣的社区小故事很多 C 开发者心心念念标准网络库 networking它曾经被规划进 C23大家都十分期待。但在标准讨论阶段因为委员会内部多方意见分歧、实现方案争论不休这个特性最终没能进入 C23。 这件事在 C 社区引发了很长一段时间的热烈讨论和玩梗。也能侧面看出C 标准的推进十分严谨一个特性想要纳入标准需要兼顾性能、兼容性、跨平台门槛极高。三、C 的行业价值3.1 TIOBE 编程语言排行榜TIOBE 榜单会综合全网教程、开发者讨论、搜索引擎检索热度等数据统计各编程语言的流行程度。需要注意这个榜单只反映语言的社区热度热度高不代表语言绝对更好不能单靠排名评判一门语言的优劣。从 2024 年 6 月这份榜单可以看到C 稳定稳居第二长期处在第一梯队。Python 凭借入门简单、生态丰富常年领跑而 C、C 这类偏底层、高性能的语言依旧牢牢占据高位。纵观多年的趋势曲线C 的生命力十分顽强即便不断有新语言诞生它的市场底盘一直很稳固。3.2 C 在工作领域中的应用C 凭借高性能、内存可控的优势落地场景非常广泛覆盖系统底层、音视频、游戏、服务端、嵌入式、AI 底层等方向。下面分场景介绍大型系统软件开发编译器、数据库内核、操作系统内核、浏览器底层等基础软件追求极致运行效率大多采用 C 开发是整个软件生态的基石。音视频处理我们熟知的 FFmpeg、WebRTC、MediaSoup、ijkplayer 这些主流开源音视频框架底层都是 C。直播、短视频、实时通话的音视频编解码核心技术栈离不开 C。PC 客户端开发Windows 桌面软件很多基于 C 搭配 QT 开发QT 是跨平台 GUI 框架可以一套代码适配多系统典型例子如 WPS 这类大型桌面应用。高性能服务端开发Java 在后端生态中应用很广而 C 多用于对延迟、吞吐要求严苛的高并发场景比如游戏后台、流媒体服务器、金融量化高频交易系统。游戏引擎开发主流商业与开源游戏引擎UE4、Cocos2d-x 等底层都是 C。做引擎或者游戏客户端除 C 语法之外还需要掌握数据结构、计算机图形学等知识。嵌入式开发把主控芯片嵌入硬件设备依靠软件控制硬件。像摄像头、扫地机器人、车载系统、智能手环、门禁设备都属于这个范畴分为嵌入式应用开发与驱动开发两大方向。机器学习引擎很多 AI 框架底层算子、推理内核是 C 实现上层再用 Python 封装接口。只做模型训练Python 就够用如果要做推理引擎、优化底层算子C 是必备技能。测试开发测试开发工程师需要自研性能测试工具、自动化框架很多高性能测试组件会用 C 实现而功能测试更多侧重业务用例不一定需要 C。四、C 学习难度经常有人问C 难不难客观来说C 属于上手门槛高、易学难精的语言学习曲线很陡峭。一方面是它背负了 C 语言的历史包袱另一方面语言本身经过多年迭代特性繁多、规则复杂。网上流传着一个很出名的梗21 天自学精通 C。这其实是一个搞笑漫画用来调侃 C 很难21 天根本不可能学明白。 漫画的趣味解读第 1–10 天啃基础语法变量、数组、函数、字符串这些入门内容第 11–21 天进入 C 核心难点指针、引用、类、继承、多态第 22–697 天不断写小项目读别人代码在 bug 和失败里积累经验第 698–3648 天参与团队项目和同行交流在实战中成长后面的阶段就是玩梗了学理论物理、生物化学最后造时光机回到第 21 天才能 “精通 C”这个段子的内核是提醒我们不存在短期速成精通 C 的捷径。语法只是起点真正吃透内存模型、泛型、STL、工程实践需要长期沉淀。五 、C 的第一个程序C 兼容 C 语言绝大多数语法所以 C 语言实现的 hello world 依旧可以运行。C 源文件后缀改为.cppVS 编译器识别到.cpp就调用 C 编译器编译Linux 下编译要用g不再是 gcc。// test.cpp #includestdio.h int main() { printf(hello world\n); return 0; }当然 C 有一套自己的输入输出严格说 C 版本的 hello world 应该是这样写的// test.cpp #includeiostream using namespace std; int main() { cout hello world\n endl; return 0; }六、 命名空间6.1 namespace 的价值在 C/C 中项目里会定义大量变量、函数后续我们还会学习类。如果所有变量、函数、类的名称全都放在全局作用域下很容易出现名字冲突。命名空间的作用就是把标识符的名称做区域隔离以此避免命名冲突、解决名字污染问题namespace关键字就是专门用来处理这类问题的。在 C 语言开发中这种命名冲突的情况十分常见。C 引入namespace就是为了更好地解决名字污染这个痛点。// 引入标准输入输出头文件里面包含printf函数的声明 #include stdio.h // 引入标准库头文件C标准库在这里声明了rand()随机数函数 #include stdlib.h // 这里定义了一个全局变量名字叫rand int rand 10; int main() { // 编译报错: error C2365: “rand”: 重定义; 以前的定义是“函数” /* 报错原因解析 1. #include stdlib.h 已经帮我们声明了库函数 rand()rand 是一个函数名 2. 我们自己又定义了全局变量 int rand;变量名和库函数名字完全一样 3. C语言中同一个作用域下标识符名字不能同时代表两个东西 编译器分不清你这里的rand到底是库函数还是你自己定义的变量触发重定义错误。 ✅ 简单一句话变量名和库rand函数名字冲突了 */ printf(%d\n, rand); return 0; }6.2 namespace 的定义定义命名空间需要使用namespace关键字后面跟上命名空间的名称再紧跟一对花括号{}花括号内部的内容就是该命名空间的成员。命名空间中可以定义变量、函数、自定义类型等多种成员。命名空间的本质是定义出一个独立的作用域。这个作用域和全局作用域相互隔离不同作用域内可以定义同名标识符因此原本会冲突的rand名字放在不同命名空间里就不再冲突。C 中常见的作用域有四种函数局部域、全局域、命名空间域、类域。作用域的核心作用是决定编译器在编译阶段查找变量、函数、类型声明或定义的查找规则有了作用域之间的隔离命名冲突问题就从根源上得到了解决。 其中局部域和全局域除了影响名字查找逻辑还会决定变量的生命周期而命名空间域和类域只影响名字查找不会改变变量的生命周期。命名空间只能定义在全局作用域下同时支持嵌套定义。 在大型项目的多个文件中定义同名的命名空间编译器会将它们自动合并为同一个命名空间不会产生冲突。我们常用的 C 标准库所有组件都统一放在名为std取自 standard的命名空间中。①基础命名空间解决重名冲突#includeiostream using namespace std; // 定义命名空间ZhangSan namespace ZhangSan { // 在命名空间ZhangSan内部定义变量score int score 10; } // 在全局域定义同名变量score不会和ZhangSan里面的score冲突 int score 20; int main() { // :: 是域解析符访问ZhangSan命名空间中的score输出10 cout ZhangSan::score endl; // 单独::score 代表访问全局域中的score输出20 cout ::score endl; return 0; }::叫做域解析符用来指定去哪个域里面找名字。②命名空间中定义函数#includeiostream using namespace std; // 定义命名空间Student namespace Student { // 在命名空间里面定义ShowInfo函数 void ShowInfo() { cout 这是Student命名空间里的信息打印函数 endl; } } int main() { // 使用域解析符调用Student空间中的ShowInfo函数 Student::ShowInfo(); return 0; }③命名空间嵌套#includeiostream using namespace std; // 外层命名空间School namespace School { int grade 1; // 在School里面嵌套定义命名空间Class namespace Class { int num 2; } } int main() { // 访问School空间下的变量grade cout School::grade endl; // 多层嵌套连续使用域解析符访问School里面Class空间的num cout School::Class::num endl; return 0; }④多文件同名 namespace 自动合并文件 1test1.cpp// 文件1中定义命名空间Game namespace Game { int hp 100; }文件 2test2.cpp// 文件2中写同名Game编译后会和test1里面的合并成同一个命名空间 namespace Game { void ShowHP() { // 这里可以直接使用Game::hp两个文件属于同一个命名空间 } }⑤区分域对生命周期的影响#includeiostream using namespace std; namespace Shop { // 命名空间域的变量仅隔离名字生命周期和全局变量一致程序结束才销毁 int money 10; } void func() { // 局部域变量只在func函数内部生效函数执行结束变量直接销毁 int a 20; cout a endl; } int main() { cout Shop::money endl; func(); return 0; }小结局部域、全局域控制名字查找 生命周期命名空间域、类域只控制名字查找不改变生命周期。6.3 命名空间使用编译器查找一个变量的声明 / 定义时默认只会在局部或者全局查找不会到命名空间里面去查找。所以下面程序会编译报错。所以我们要使用命名空间中定义的变量 / 函数有三种方式指定命名空间访问项目中推荐这种方式。using 将命名空间中某个成员展开项目中经常访问的不存在冲突的成员推荐这种方式。展开命名空间中全部成员项目不推荐冲突风险很大日常小练习程序为了方便推荐使用。#includestdio.h // 定义命名空间Student namespace Student { int chinese 0; int math 1; } int main() { // 编译报错: error C2065: “chinese”: 未声明的标识符 // printf(%d\n, chinese); return 0; }// 方式1指定命名空间访问项目推荐 int main() { // 使用域解析符明确指定去Student命名空间找chinese printf(%d\n, Student::chinese); return 0; }// 方式2using将命名空间中某个成员展开 using Student::math; int main() { printf(%d\n, Student::chinese); // math已经被using展开可以直接使用 printf(%d\n, math); return 0; }// 方式3展开命名空间中全部成员练习用工程慎用 using namespace Student; int main() { printf(%d\n, chinese); printf(%d\n, math); return 0; }八、C 输入 输出iostream是 Input Output Stream 的缩写也就是 C 的标准输入输出流库其中定义了标准的输入、输出对象。std::cin是 istream 类的实例对象对应面向 char 类型窄字符的标准输入流std::cout是 ostream 类的实例对象对应面向窄字符的标准输出流std::endl本质是一个函数在流插入输出时会输出一个换行符同时强制刷新输出缓冲区。称为流插入运算符称为流提取运算符。补充说明在 C 语言中这两个符号还用作位运算的左移、右移运算符到了 C 里通过运算符重载赋予了它们 IO 操作的新含义。相比 C 语言的printf/scanfC 输入输出使用更便捷不需要手动书写格式控制符编译器可以自动识别变量类型这个特性底层正是通过函数重载实现的。更重要的是C IO 流能够很好地支持自定义类型对象的输入输出扩展性更强。IO 流的完整实现涉及类与对象、运算符重载、继承等大量面向对象知识这些内容后续会逐步展开所以现阶段先掌握基础用法即可。cin、cout、endl都属于 C 标准库组件而 C 标准库的全部内容都统一放在名为std取自 standard的命名空间中因此使用时需要遵循命名空间的访问规则。 日常练习为了编码方便可以直接using namespace std;但在实际项目开发中不建议这么做避免引发命名冲突。最后补充一个细节即使我们没有显式包含stdio.h只要包含了iostream在 VS 系列编译器下也能正常使用printf和scanf这是因为iostream间接包含了相关头文件。但这种行为不具备通用性其他编译器可能直接报错正式代码里建议显式包含所需的头文件。#define _CRT_SECURE_NO_WARNINGS 1 #include iostream using namespace std; int main() { int a 0; double b 0.1; char c x; // 直接使用cout输出自动识别类型 cout lt;lt; a lt;lt; lt;lt; b lt;lt; lt;lt; c lt;lt; endl; // 不展开std的写法完整写法 std::cout lt;lt; a lt;lt; lt;lt; b lt;lt; lt;lt; c lt;lt; std::endl; // 混用C语言scanf scanf(%d%lf, amp;a, amp;b); printf(%d %lf\n, a, b); // cin自动识别变量类型不用写格式控制符 cin gt;gt; a; cin gt;gt; b gt;gt; c; cout lt;lt; a lt;lt; endl; cout lt;lt; b lt;lt; lt;lt; c lt;lt; endl; return 0; }#includeiostream using namespace std; int main() { // 在IO需求比较高的地方如部分大量输入的竞赛题中加上以下3行代码 // 可以提高CIO效率解除cin/cout和C stdio的同步绑定 ios_base::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); return 0; }九、 缺省参数缺省参数指的是在声明或定义函数时为形参预先指定一个默认值。调用该函数时如果没有传入对应的实参就会自动使用这个默认值如果传入了实参则优先使用传入的值。缺省参数分为全缺省和半缺省两类在部分资料中也将其称为默认参数。全缺省即函数的所有形参都设置了默认值半缺省则是仅为部分形参设置默认值。C 语法明确规定半缺省参数必须从右往左依次连续设置不能间隔跳跃地给默认值。 调用带缺省参数的函数时实参必须从左到右依次传递不能跳过前面的形参直接给后面的形参传值。当函数采用声明与定义分离的写法时缺省参数不能在声明和定义中同时出现语法规定默认值必须写在函数声明里。示例 1单个缺省参数#include iostream using namespace std; // 给形参a设置缺省值0 void Func(int a 0) { cout a endl; } int main() { Func(); // 不传实参使用默认值0 Func(10); // 传入实参使用10覆盖默认值 return 0; }示例 2全缺省 半缺省#include iostream using namespace std; // 全缺省所有参数都给默认值 void Func1(int a 10, int b 20, int c 30) { cout a b c endl; } // 半缺省必须从右往左连续给默认值不能跳着给 void Func2(int a, int b 10, int c 20) { cout a b c endl; } int main() { Func1(); // 全部用默认10 20 30 Func1(1); // a1b、c默认1 20 30 Func1(1,2); // a1,b2c默认1 2 30 Func2(100); // a100b、c默认100 10 20 Func2(100,200); // a100,b200c默认100 200 20 Func2(100,200,300); // 全部传参100 200 300 return 0; }示例 3声明定义分离#include iostream #include assert.h using namespace std; // 函数声明在这里给缺省值 void STInit(int n 4); // 函数定义定义处不能再写默认值 void STInit(int n) { assert(n 0); cout 初始化容量 n endl; } int main() { STInit(); // 使用默认值4 STInit(1000); // 自己指定容量 return 0; }十、 函数重载C 提供了函数重载特性在同一个作用域中可以定义多个函数名完全相同的函数。编译器会根据调用时传入实参的个数、类型、类型顺序自动匹配并选择最合适的函数版本执行让同名函数具备不同的处理逻辑代码写起来更加灵活。而 C 语言没有重载机制同一作用域内不允许出现同名函数否则直接编译失败。想要构成合法的函数重载形参列表必须存在差异满足下面三种情况之一即可① 参数类型不同#includeiostream using namespace std; // 两个Add同名参数类型不一样构成重载 int Add(int left, int right) { cout int版本Add endl; return left right; } double Add(double left, double right) { cout double版本Add endl; return left right; } int main() { Add(10, 20); // 匹配int版本 Add(10.1, 20.2); // 匹配double版本 return 0; }② 参数个数不同#includeiostream using namespace std; // 同名f参数数量不一样构成重载 void f() { cout 无参 f() endl; } void f(int a) { cout 单参数 f(int) endl; } int main() { f(); f(10); return 0; }③ 参数类型顺序不同#includeiostream using namespace std; // 参数个数一样、类型一样但顺序不同构成重载 void f(int a, char b) { cout f(int, char) endl; } void f(char b, int a) { cout f(char, int) endl; } int main() { f(10, a); f(a, 10); return 0; }❌ 易错点 1仅返回值不同不能构成重载#includeiostream using namespace std; // 报错只有返回值不同编译器无法区分 // void fxx(){} // int fxx(){ return 0; } int main() { // fxx(); 编译器不知道调用哪一个 return 0; }⚠️ 易错点 2重载 缺省参数容易产生歧义#includeiostream using namespace std; void f1() { cout f1() endl; } // 带缺省参数 void f1(int a 10) { cout f1(int) endl; } int main() { // f1(); // 打开这一行直接编译报错存在调用歧义 f1(20); // 这一句没问题明确匹配第二个 return 0; }十一、 引用11.1 引用的概念和定义引用并不是创建一个全新的变量而是给已经存在的变量起一个别名。编译器不会为引用单独开辟内存空间引用和原变量共用同一块内存。 可以通俗理解孙悟空外号齐天大圣又叫美猴王叫法不一样但指代的是同一个角色。语法格式类型 引用别名 引用对象;注意这里的是引用标记不是取地址符号需要结合上下文区分。#includeiostream using namespace std; int main() { int a 0; // b是a的别名 int b a; // c也是a的别名一个变量可以有多个别名 int c a; // 给别名再起别名d依旧是a的别名 int d b; d; // 修改d本质就是修改a // 打印地址四个地址完全一样 cout lt;lt; amp;a lt;lt; endl; cout lt;lt; amp;b lt;lt; endl; cout lt;lt; amp;c lt;lt; endl; cout lt;lt; amp;d lt;lt; endl; return 0; }11.2 引用的三大特性引用在定义的时候必须初始化同一个变量可以拥有多个引用多个别名 引用一旦绑定到某个变量就不能再绑定其他变量只能赋值不能改指向#includeiostream using namespace std; int main() { int a 10; // int ra; // 报错引用没有初始化 int b a; // b绑定a int c 20; b c; // ⚠️这是赋值操作不是让b改成绑定c // b依旧是a的别名只是把c的值拷贝给a cout lt;lt; amp;a lt;lt; endl; cout lt;lt; amp;b lt;lt; endl; cout lt;lt; amp;c lt;lt; endl; return 0; }11.3 引用的使用场景引用最常用的两个场景引用传参、引用作为返回值。示例 1引用传参实现交换函数Swap#includeiostream using namespace std; // rx、ry是x、y的别名 void Swap(int rx, int ry) { int tmp rx; rx ry; ry tmp; } int main() { int x 1, y 2; cout x y endl; Swap(x, y); cout x y endl; return 0; }示例 2引用在数据结构中的简化用法栈初始化演示#includeiostream #includecstdlib using namespace std; typedef int STDataType; typedef struct Stack { STDataType* a; int top; int capacity; }ST; // rs是外面栈对象的引用不用传指针 void STInit(ST rs, int n 4) { rs.a (STDataType*)malloc(n * sizeof(STDataType)); rs.top 0; rs.capacity n; } int main() { ST st1; STInit(st1); return 0; }示例3引用作为返回值除了传参引用也可以作为函数返回值本质是返回目标变量的别名省去值返回时的数据拷贝提升运行效率。这里有一条非常关键的约束被返回的变量它的生命周期一定要比函数更长。如果函数结束时这个变量已经被销毁返回的引用就变成野引用程序结果随机甚至直接崩溃。 普通局部变量存放在栈空间函数调用结束就会被回收因此不能返回它的引用而 static 静态变量、全局变量、外部传入的参数生命周期持久可以安全地返回引用。❌ 错误示例返回局部变量的引用野引用#includeiostream using namespace std; int Func() { int n 10; // 局部变量函数结束内存就回收 return n; // 危险产生野引用属于未定义行为 } int main() { int ret Func(); cout ret endl; return 0; }✅ 正确示例返回 static 静态变量的引用#includeiostream using namespace std; int Func() { static int n 10; // static变量存放在静态区直到程序结束才销毁 return n; } int main() { int ret Func(); cout ret endl; ret 20; // 通过引用直接修改原变量 cout Func() endl; return 0; }引用返回常见的用途一是减少大对象拷贝优化性能二是实现链式调用支持连续操作。 记住禁忌不要返回函数内部普通局部栈变量的引用补充C 引用和 Java 引用不一样C 引用绑定之后无法更换目标引用和指针各有优势不能完全互相替代。很多 C 语言写的数据结构教材改用引用传参来避开复杂的二级指针。11.4 const 引用权限原则引用过程中访问权限可以缩小但是不能放大普通引用不能引用 const 常量权限放大报错const 引用可以引用普通变量、const 常量临时对象具有 const 属性只能用 const 引用接收#includeiostream using namespace std; int main() { const int a 10; // int ra a; // 报错把只读的a交给可修改的引用权限放大 const int ra a; // ✅ 正确const引用匹配const变量 int b 20; const intamp; rb b; // ✅ 正确权限缩小本来可改现在只读 // 表达式计算产生临时对象临时对象自带常性 const intamp; rc 12; const intamp; rd b * 3; return 0; }11.5 指针和引用的关系很多同学学到这里容易把指针和引用搞混C 里指针和引用就像性格不一样的一对兄弟功能上有重叠、可以实现相似效果但特点不同不能互相替代。概念空间引用是变量的别名不单独开辟内存指针是独立变量用来存放地址需要开辟内存。初始化引用定义时必须初始化指针语法上不强制初始化但开发里建议初始化避免野指针。指向变更引用一旦绑定某个变量就不能再绑定别的对象指针可以随时修改指向不同变量。访问方式引用直接访问原变量指针需要解引用*才能访问指向的内容。sizeof结果sizeof(引用)得到的是原变量类型的大小sizeof(指针)永远是地址的字节大小32 位 4 字节64 位 8 字节。安全风险指针很容易出现野指针、空指针问题引用约束更多使用起来相对安全十二、 inline 内联函数inline用来修饰函数被修饰的函数叫做内联函数。编译阶段编译器会尝试在调用内联函数的地方直接把函数代码原地展开省去函数调用建立栈帧的开销以此提升效率。⚠️ 重点inline 只是给编译器的建议不是强制命令。编译器觉得不合适的时候可以选择不展开。递归函数、代码量大的函数加 inline 基本都会被编译器忽略inline 适合频繁调用、代码简短的小函数。设计 inline 的核心目的替代 C 语言的宏函数。宏是预处理阶段简单文本替换写法繁琐、容易出错、不方便调试内联函数兼具宏的高效又拥有普通函数的类型检查更加安全。补充小细节VS Debug 模式默认不会展开 inline 方便调试如果想在 Debug 下观察展开效果需要手动修改编译选项inline 不建议声明和定义分离到不同文件分离会出现链接报错因为内联展开之后函数没有独立地址链接找不到符号。✅ inline 示例#includeiostream using namespace std; // 内联函数 inline int Add(int x, int y) { int ret x y; ret 1; ret 1; ret 1; return ret; } int main() { // 可以看汇编判断是否展开有call指令代表没展开 int ret Add(1, 2); cout Add(1, 2) * 5 endl; return 0; } 对比 C 语言宏的坑#includeiostream using namespace std; // 宏很容易踩坑括号少一个结果就出错 #define ADD(a, b) ((a) (b)) int main() { int ret ADD(1, 2); cout ADD(1, 2) endl; cout ADD(1, 2)*5 endl; int x 1, y 2; ADD(x y, x | y); return 0; }❌ 错误写法inline 声明定义分离链接报错// F.h #include iostream using namespace std; inline void f(int i); // F.cpp #include F.h void f(int i) { cout i endl; } // main.cpp #include F.h int main() { f(10); return 0; }报错原因inline 展开后没有函数地址外部文件找不到这个函数符号。十三、nullptr在 C 语言里我们习惯用NULL表示空指针本质上NULL是一个宏。#ifndef NULL #ifdef __cplusplus #define NULL 0 #else #define NULL ((void*)0) #endif #endifC 中NULL会被定义成字面常量 0这里就会带来重载歧义的坑当同时存在f(int)和f(int*)重载版本传入NULL会优先匹配f(int)而不是我们想要的指针版本和预期不符。所以 C11 引入了nullptr它是专门代表空指针的关键字是指针类型字面量只能隐式转换成各种指针类型不能转为整数完美解决 NULL 带来的重载二义性问题。示例NULL 和 nullptr 的区别#includeiostream using namespace std; void f(int x) { cout f(int x) endl; } void f(int* ptr) { cout f(int* ptr) endl; } int main() { f(0); f(NULL); // 匹配 f(int)不是预期的指针版本 f(nullptr); // ✅ 匹配 f(int*)专门代表空指针 return 0; }好啦那这篇内容就先整理到这里这次把C入门基础的相关知识整理了一下为了方便自己日后复习回顾也希望能给正在学习的小伙伴一些参考。如果文中有理解不到位或是书写有误的地方欢迎大家在评论区指出一起交流共同进步。谢谢大家
企业数字化 ERP 产品动态
相关推荐
QQ流量分析实验 QQ流量分析实验
一、实验准备
准备了一台电脑、一部手机,还借了朋友的一台电脑,都用QQ登录。抓包工具是Wireshark。两组实验:电脑和电脑互发、电脑和手机互发,发的内容是文字消息和一张图片。
二、第一组:电脑和电脑
用… · 2026/11/5 14:51:10
项目框架搭建(1) 1.总结学习今天要学的是搭建项目的框架,整体框架是根据需求选择合适传感器,大的方向分为五个单元,数据采集单元,数据存储单元,异常预警单元,还有数据显示单元,网络发送单元,在编写软… · 2026/11/10 2:39:38
AI3T 2026 | 分布式 AI 训练与工作负载编排 Track 征稿通知 会议简介
2026人工智能基础设施与互联技术国际会议(AI3T 2026)计划于 2026年12月18日至20日在中国合肥举办。该会议聚焦智能时代的基础设施与互联技术,议题覆盖AI基础设施与架构 、具身智能及网络、光学与AI、AI安全、AI数据中心、分布式AI训… · 2026/11/5 1:12:45
高级技能电工证怎么考?学历不高也能拿!含金量不输本科 高级技能电工证怎么考?学历不高也能拿!含金量不输本科 你是不是也担心自己学历不高,考不了高级技能电工证?其实完全不用担心,我身边就有好几个初中毕业的电工朋友,现在拿着高级技能电工证在工地当技术员,工资都不比本科生低。今天我就从洛阳涧西区一个… · 2026/11/11 1:49:01
我需要一个电工证,2026河南最新政策怎么考? 我需要一个电工证,2026河南最新政策怎么考? 工地太忙根本没时间复习考试,这几乎是每个特种作业人员的心声。尤其像你这样在开封转岗的电工,可能正面临着从建筑工地转到电力安装岗位的挑战,而手里那本已经过期的电工证,成了你上新岗位的拦路虎。20… · 2026/11/11 1:49:01
工地太忙根本没时间复习考试?黔江电工证考试在哪里网上查询全攻略 工地太忙根本没时间复习考试?黔江电工证考试在哪里网上查询全攻略 工地太忙根本没时间复习考试?你不是一个人。很多人在工地一干就是几个月,白天干活晚上赶工期,根本抽不出时间复习电工证考试,但证件到期又不能上岗,这事儿谁懂?别急,今天我就把… · 2026/11/11 1:49:01
电工证有办法挂靠吗 含金量高到你想不到 电工证有办法挂靠吗 含金量高到你想不到 你是不是也遇到过这样的情况?不知道去哪报名,怕被中介坑,听说电工证有办法挂靠,但又不知道具体怎么操作,生怕踩坑?其实,电工证的含金量在很多行业都相当高,尤其是涉及电力、建筑、制造这些领域,但问题在于,… · 2026/11/11 1:49:01
证书过期了不知道怎么复审延期?办理无锡学习电工证千万别踩坑 证书过期了不知道怎么复审延期?办理无锡学习电工证千万别踩坑 你是不是也遇到过这种情况?电工证快到期了,但不知道该怎么复审,怕一不小心错过了,证书就失效了,到时候找工作、上岗都受影响。特别是像你这样在南阳卧龙区转岗的从业者,对证书的含金量和有… · 2026/11/11 1:49:01
高压电工作业教案官方回应:证书过期了不知道怎么复审延期 高压电工作业教案官方回应:证书过期了不知道怎么复审延期 证书过期了不知道怎么复审延期?很多人在高压电工作业证到期后才意识到问题,结果发现不是简单地找培训机构补个课就能解决。 官方回应… · 2026/11/11 1:48:00
电工证丢失还能补吗?千万别踩坑,丢了证等于失业 电工证丢失还能补吗?千万别踩坑,丢了证等于失业 你正在找工作,但手里的电工证突然丢了,这下怎么办?别急,先别慌, 电工证丢失还能补吗 ?答案是肯定的,但 千万别踩坑 ,补办流程如果不了解,可能会耽误你上岗、甚至影响到你的收入。… · 2026/11/10 2:16:10
电力电缆作业证直播报名入口与办理流程良心建议 电力电缆作业证直播报名入口与办理流程良心建议 你是不是也担心学历不高报不了名?别急,今天就给你讲讲 电力电缆作业证直播 的报名流程和那些你必须知道的干货, 良心建议 在这里,别错过。 电力电缆作业证直播报名需要什么条件?… · 2026/11/10 17:46:39
英德电工证报考费多少钱,怕考不过白交培训费?看完这篇别再踩坑 英德电工证报考费多少钱,怕考不过白交培训费?看完这篇别再踩坑 怕考不过白交培训费?英德电工证报考费多少钱,你真的了解吗?现在市面上培训机构五花八门,价格参差不齐,很多人一不小心就花了冤枉钱,结果还考不过。别急,今天就来给你说清楚英德电工证的… · 2026/11/10 17:11:43
_中职电工证最新政策解读 中职电工证在哪考?工地太忙根本没时间复习考试的救星来了 工地太忙根本没时间复习考试,这是很多正在备考 中职电工证 的建筑工友的真实写照。你不是不想考,而是真的没时间。但别担心,这期内容是 新乡初次报考人员… · 2026/11/11 0:00:52
昆明电工证培训价格值不值得考?老电工告诉你真相 昆明电工证培训价格值不值得考?老电工告诉你真相 之前考的证在外省能不能转过来?这个问题是很多在外地打工、换城市生活、或者是想在昆明发展的朋友最关心的。尤其是现在云南发展快,很多企业都在招持证上岗的电工,如果你手头有证但不在本地,那这个证是不… · 2026/11/11 0:00:52
特种电缆张工网上查询全攻略:外省证如何转过来 特种电缆张工网上查询全攻略:外省证如何转过来 之前考的证在外省能不能转过来?这是很多特种作业从业者最关心的问题,尤其是像特种电缆张工这类证书,直接关系到日常工作是否合规、能否顺利晋升。很多小伙伴因为证书归属地的问题,陷入职业发展的瓶颈,甚至… · 2026/11/11 0:00:52
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/10 20:19:29
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/10 19:22:45
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/10 17:00:47