
前言本篇博客包含了C语言的核心知识适用于想入门C语言的编程新手或者是想扩展对C语言的理解的学生本人是物联网专业的学生也属于计算机专业大类未来走嵌入式硬件方向所以在我的文章中你能读到一些我的学习习惯和对嵌入式的理解在C语言的核心基础内容更新完后我将进一步更新C语言进阶的内容其中还包括我在大学之路上的见闻以及我曾经阅读过的好书也会毫无保留的推荐给大家跟我同样想走嵌入式硬件方向的道友一定不可错过这本由北京理工大学“双一流”精品出版工程的《“智”造梦工厂典型机电系统综合创新实践教程》非常适合作为入门书籍打下前期的硬件基础对电子设计竞赛和创新创业竞赛的备赛也有一定帮助物联网虽然是偏向硬件的专业但AI时代人人都要学会C语言底层计算机语言我对C语言的学习有一些思考和总结虽然只是最基础的知识但程序的高楼大厦就是由这些基础代码堆砌而成我对C语言知识的学习更多是靠自学教材明确C语言这本书而来我坚信程序员的路是靠务实的积累循序渐进推进的更何况物联网这种杂乱的专业特点我从一开始就选好了C语言嵌入式学习方向这并不是因为我的目标有多明确而是选择太多时代变迁太快一切都太模糊只能选最靠谱的方向摸黑学下去我坚信靠专业技能取得的成功最具可复制性我不知道我能在嵌入式这条道路上我能坚持多久我只想在趁现在对编程还有热情的时候尽可能的多输出一些有用的内容下面是我近一段时间对C语言的总结希望自己的分享可以帮助到读者。很多物联网专业的同学在学完C语言后都会面临一个共同的困惑接下来该往哪个方向走如果你和我一样对硬件感兴趣那么嵌入式开发就是最自然、也最有前景的进阶方向。嵌入式开发本质上是“用软件控制硬件”而C语言正是连接软件与硬件之间最直接的桥梁。下面我从入门路径、必读书籍、学习方法与技巧、以及需要避开的坑四个方面给同样想走嵌入式方向的物联网学弟学妹们一些参考。一、入门路径从C语言到嵌入式开发嵌入式开发的学习路径可以概括为“三层递进”第一层是扎实的C语言功底包括指针、结构体、内存管理等核心知识这是所有嵌入式开发的基石第二层是硬件基础需要了解单片机如STM32、电路基础、GPIO、定时器、中断等概念知道芯片是怎么工作的第三层是软硬结合学会阅读芯片手册、操作寄存器、编写驱动程序最终能独立完成一个小型项目。建议按这个顺序循序渐进不要一上来就买一堆开发板却连指针都还没弄明白。二、必读书籍推荐除了前面提到的《“智”造梦工厂典型机电系统综合创新实践教程》适合打硬件基础外我再补充几本嵌入式方向的经典书籍。第一本是《嵌入式C语言自我修养》这本书从C语言与硬件的结合点切入非常适合刚学完C语言、想转向嵌入式的同学第二本是《STM32嵌入式系统开发实战指南》以实际项目为导向手把手教你配置寄存器、编写驱动第三本是《ARM嵌入式系统开发软件设计与优化》适合想深入理解ARM架构和底层原理的同学。另外正点原子、野火等开发板厂商的配套教程和视频也非常适合入门配合开发板边看边练效果远好于只看书。三、学习方法与技巧嵌入式学习最忌讳“只看不练”。我的建议是第一买一块入门级开发板如STM32F103跟着教程从点亮LED开始逐步学会按键、定时器、串口、中断等外设操作第二学会读芯片手册这是嵌入式开发的核心技能刚开始看不懂很正常先找关键章节反复看配合代码理解第三养成写调试日志的习惯用串口打印变量值来定位问题这比盲目猜测高效得多第四多逛正点原子、CSDN、电子工程世界等社区很多坑别人已经踩过搜一搜就能少走弯路。记住嵌入式开发是“做”出来的不是“看”出来的。四、需要避开的坑第一个坑是“贪多嚼不烂”买了一堆开发板和模块结果每个都只学了个皮毛不如先把一块板子的常用外设吃透。第二个坑是“忽视C语言基础”指针、结构体、内存管理没学扎实就急着上硬件结果代码一复杂就各种崩溃还找不到原因。第三个坑是“只看不练”视频看了很多代码一行没写过等到自己动手时完全无从下手。第四个坑是“不读手册只看教程”教程只能带你入门真正解决问题还得靠芯片手册和官方文档。最后一个坑是“遇到问题就放弃”嵌入式调试本来就比纯软件更繁琐一个bug查几天很正常坚持住跨过那道坎就是成长。一、C语言基础语法与核心概念本章围绕C语言的基础语法展开涵盖数据类型、运算符、流程控制、函数与数组等核心知识点帮助读者建立扎实的语法基础。1.1 数据类型与变量C语言中的数据类型决定了变量在内存中占用的空间大小以及可表示的取值范围。掌握数据类型是编写高效、可靠程序的基础。本节重点介绍整型、浮点型、字符型三大基本数据类型以及变量的声明、初始化和作用域规则。1.1.1 基本数据类型C语言的基本数据类型主要分为三类整型、浮点型和字符型。整型用于表示整数浮点型用于表示带小数的实数字符型用于表示单个字符。下面是一个数据类型参照图不需要记下来因为计算机语言与中文英语这类传统语言不同计算机语言的数据浩如烟海这些数据只是记录在电脑上用于随时调用查询不是像英语单词一样必须作为考察点这些关键数据多是在写代码的点点滴滴中潜移默化记得的在我先前的博客中提到考研电子科技大学的理想通过一些考研面试官的介绍可知考试基本不会将一些无关的偏离数据作为考题复试也是这样面试官多会问一些底层的知识因为底层知识考察角度最为客观也最能体现一个程序员是否掌握了系统的基本功类型关键字典型占用字节取值范围32位平台整型int4-2147483648 ~ 2147483647短整型short2-32768 ~ 32767长整型long4 或 8取决于平台无符号整型unsigned int40 ~ 4294967295单精度浮点float4约 ±3.4e38双精度浮点double8约 ±1.7e308字符型char1-128 ~ 127这个知识点是我编程的起点从main函数开始到printf打印信息都要面对许多小困难入门这些基础知识会让我们感到无比挫败因为中国大多数的家庭多是在孩子上大学的时候才买电脑我们对电脑的盲区较多期间一旦遇到困难便将其认为是难以克服的困难这是我们人类在远古时期就保留的本能我们将未知曲解成了困难觉得C语言比自己想象的还难但几乎所有人都是这样问题的根源或许并不在编程本身我们找错了问题问题主体或许就在我们身上。如果你跟我一样不知道问题所在你想找到真问题找到想要的答案不妨看看计算机先祖温伯格的巨著《你的灯亮着吗?》,其中的思维运用将助你找到问题根源我强烈推荐你用此书搭配AI使用但在当前AI在图片识别上还有局限性我经常问DeepSeek图片类的问题但都没有得到想要的结果要么是找不到问题所在要么是表述不清因此对于前期在编程上遇到的问题最好有个答疑老师前期的错误是最多的但编程的乐趣就在代码成功运 行的那一瞬间失败后的总结就是成长就这样在不断犯错中调试纠错你就会找到编程的乐趣所在。需要注意的是不同平台下各类型的字节数可能不同实际开发中可以使用sizeof运算符来获取类型在当前环境下的真实大小。起初我学这个知识点也是一样的感觉抱怨着怎么计算机也要记得这么多数据后来在老师的开导下了解了计算机的学科特点才克服了畏难情绪也希望每位在IT之路上前行的追梦者能笑对困难。1.1.2 变量的声明与初始化变量是程序中用于存储数据的命名内存单元。声明变量时需要指定其数据类型初始化则是在声明的同时赋予初始值。在C语言中未初始化的局部变量其值是随机的栈上的残留数据因此建议在声明变量时就进行初始化避免产生难以排查的bug。这里为理解变量的概念打下前提基础初始化可以减少程序后期的Debug次数同时方便后期的调试和维护下面通过一段经典代码示例演示变量的声明与初始化#include stdio.h int main() { // 声明变量指定数据类型和变量名 int age; float score; char grade; // 初始化变量在声明的同时赋予初始值 int count 10; float pi 3.14; char ch A; // 先声明后赋值也是允许的 age 20; score 95.5; grade B; printf(age %d\n, age); printf(score %.1f\n, score); printf(grade %c\n, grade); printf(count %d, pi %.2f, ch %c\n, count, pi, ch); return 0; }这段代码展示了两种常见的初始化方式一种是在声明变量时直接赋初值如int count 10;另一种是先声明、后赋值如age 20;。两种方式在C语言中都是合法的但推荐在声明时就完成初始化避免使用未初始化变量带来的随机值问题。1.1.3 变量的作用域作用域决定了变量在程序中的可见范围。C语言中常见的变量作用域包括局部变量、全局变量和静态变量当时我的老师只介绍了前面两个作用域静态变量少有介绍但前期理解局部和全局就足够了因为静态变量不常用新手较难理解可以后期C语言进阶后进一步了解局部变量定义在函数或代码块内部只在所在的花括号范围内有效函数调用结束后自动销毁。全局变量定义在所有函数之外从定义处到文件末尾均可见程序运行期间一直存在。静态变量使用static修饰局部静态变量在函数多次调用之间保持值不变全局静态变量则限制其作用域仅在当前源文件内。#include stdio.h int global_var 100; // 全局变量 void func() { static int count 0; // 静态局部变量只初始化一次 int local_var 5; // 局部变量 count; printf(count %d, local_var %d\n, count, local_var); } int main() { func(); func(); func(); printf(global_var %d\n, global_var); return 0; }理解作用域规则有助于合理组织代码避免变量名冲突并减少不必要的内存占用。在嵌入式开发中合理使用静态变量和全局变量还能有效控制资源开销老手可能会一眼定真假1.2 运算符与表达式运算符可以理解为C语言中的“计算符号”它告诉计算机要对数据做什么操作。比如小学就学过的加号、减号在C语言里就是算术运算符。而表达式就是由运算符和操作数参与运算的数据组合起来的一行代码比如a b就是一个表达式。这一节我们结合新手最容易上手的简单代码把常用的运算符讲清楚。1.2.1 算术运算符算术运算符就是我们最熟悉的加减乘除另外C语言还多了两个“取余”和“自增自减”的运算符。先看下面这段代码#include stdio.h int main() { int a 10; int b 3; printf(a b %d\n, a b); // 加法结果是 13 printf(a - b %d\n, a - b); // 减法结果是 7 printf(a * b %d\n, a * b); // 乘法结果是 30 printf(a / b %d\n, a / b); // 除法结果是 3注意 printf(a %% b %d\n, a % b); // 取余结果是 1 return 0; }这里新手最容易踩的坑就是除法。两个整数相除结果还是整数小数部分会被直接丢掉所以10 / 3的结果是3而不是3.33。如果你想要带小数的结果至少要让其中一个操作数是浮点数比如写成10.0 / 3。取余运算符%得到的是除法剩下的余数比如10 % 3的结果是1它常用于判断一个数是奇数还是偶数。再来看自增和自减运算符它们非常常用但也是新手容易搞混的地方#include stdio.h int main() { int x 5; printf(x 的值是 %d\n, x); // 先使用 x 的值再让 x 加 1 printf(现在 x 的值是 %d\n, x); // 此时 x 已经变成 6 int y 5; printf(y 的值是 %d\n, y); // 先让 y 加 1再使用 y 的值 printf(现在 y 的值是 %d\n, y); // 此时 y 已经是 6 return 0; }简单记一句话x是“先用后加”x是“先加后用”。如果只是单独写一行x;或者x;效果是一样的都是让 x 加 1。1.2.2 关系运算符关系运算符用来比较两个值的大小关系结果只有两种成立真用1表示或不成立假用0表示。它们经常用在 if 判断里。看下面的例子#include stdio.h int main() { int score 85; printf(score 60 的结果是 %d\n, score 60); // 大于结果是 1真 printf(score 60 的结果是 %d\n, score 60); // 小于结果是 0假 printf(score 85 的结果是 %d\n, score 85); // 等于结果是 1真 printf(score ! 85 的结果是 %d\n, score ! 85); // 不等于结果是 0假 return 0; }这里要特别提醒新手判断“相等”要用两个等号千万不要写成赋值用的单个等号。这是C语言初学者最容易犯的错误之一写错了程序不会报错但逻辑就完全不对了。1.2.3 逻辑运算符逻辑运算符用来把多个条件组合在一起常见的有三种与、或||、非!。它们的结果同样只有真1或假0。与两边都为真结果才为真。就像“并且”两个条件都要满足。或||只要有一边为真结果就为真。就像“或者”满足其中一个就行。非!把真假反过来。真的变假假的变真。#include stdio.h int main() { int age 20; int has_id 1; // 1 表示有身份证 // 与两个条件都满足才为真 printf(成年且有身份证%d\n, age 18 has_id 1); // 或满足其中一个就为真 printf(未成年或没带身份证%d\n, age 18 || has_id 0); // 非取反 printf(不是未成年人%d\n, !(age 18)); return 0; }新手在写逻辑运算符时容易把和||写成单个的和|。单个的和|是位运算符作用完全不同后面我们会讲到。写代码时一定要记得写两个。1.2.4 赋值运算符赋值运算符除了最基础的还有一组“复合赋值运算符”比如、-、*、/。它们的作用是“先运算再赋值”能让代码更简洁。看下面的例子#include stdio.h int main() { int money 100; money 50; // 等价于 money money 50现在 money 是 150 printf(加 50 后%d\n, money); money - 30; // 等价于 money money - 30现在 money 是 120 printf(减 30 后%d\n, money); money * 2; // 等价于 money money * 2现在 money 是 240 printf(乘 2 后%d\n, money); money / 4; // 等价于 money money / 4现在 money 是 60 printf(除 4 后%d\n, money); return 0; }复合赋值运算符写起来省事读起来也直观新手可以慢慢习惯使用它们。1.2.5 位运算符了解即可位运算符直接对二进制位进行操作在嵌入式开发中非常常用因为硬件寄存器往往需要按位设置。常见的有按位与、按位或|、按位异或^、按位取反~、左移和右移。这里先简单认识一下后面在嵌入式章节会深入使用。#include stdio.h int main() { unsigned char a 0b1100; // 二进制 1100也就是十进制的 12 unsigned char b 0b1010; // 二进制 1010也就是十进制的 10 printf(a b %d\n, a b); // 按位与1000结果是 8 printf(a | b %d\n, a | b); // 按位或1110结果是 14 printf(a ^ b %d\n, a ^ b); // 按位异或0110结果是 6 printf(a 1 %d\n, a 1); // 左移一位11000结果是 24 return 0; }新手阶段不需要死记位运算的结果先知道有这回事、能看懂大概意思就行。等学到寄存器操作时再回头理解就会轻松很多。1.2.6 运算符优先级与结合性当一个表达式里有多个运算符时谁先算谁后算就由“优先级”决定。比如2 3 * 4因为乘法的优先级比加法高所以先算3 * 4得到 12再加 2结果是 14而不是 20。这和数学里的规则是一样的。如果想让某个部分先算就用小括号()把它括起来。比如(2 3) * 4先算括号里的 5再乘 4结果是 20。给新手一个实用建议拿不准优先级的时候就加括号这样代码既不会出错别人读起来也清楚。下面这张表列出了常用运算符的优先级从上到下优先级从高到低优先级运算符说明高()括号↓!、、--逻辑非、自增、自减↓*、/、%乘、除、取余↓、-加、减↓、、、关系比较↓、!相等判断↓逻辑与↓||逻辑或低、、-赋值结合性则决定了当优先级相同时是从左往右算还是从右往左算。比如加减法优先级相同就按从左到右的顺序计算而赋值运算符是从右往左的所以a b 5会先把 5 赋给 b再把 b 的值赋给 a。最后给新手一个总结运算符是C语言里最基础也最常用的工具刚开始记不住优先级没关系多用括号、多写几遍代码慢慢就熟练了。遇到和写混、整数除法丢小数这类问题都是每个新手必经的过程踩过坑记住就好。1.3 流程控制语句流程控制语句是C语言中让程序“有选择、会循环”的核心工具。本节讲解条件判断if、switch与循环for、while、do-while的写法与适用场景并补充循环嵌套与 break/continue 跳转的实战示例。1.3.1 if-else 条件判断if-else 是最基础的条件判断语句根据条件的真假决定执行哪一段代码。下面是一个根据分数评定等级的小程序#include stdio.h int main() { int score 85; if (score 90) { printf(优秀\n); } else if (score 80) { printf(良好\n); } else if (score 60) { printf(及格\n); } else { printf(不及格\n); } return 0; } /* 运行结果 良好 */if-else 会从上到下依次判断一旦某个条件成立就执行对应的代码块并跳过其余分支。注意判断相等要用不要写成赋值用的。1.3.2 switch 多分支选择当需要根据某个整型变量的多个取值做不同处理时switch 比一长串 if-else 更清晰。下面是一个根据星期数字输出星期名称的小程序#include stdio.h int main() { int day 3; switch (day) { case 1: printf(星期一\n); break; case 2: printf(星期二\n); break; case 3: printf(星期三\n); break; case 4: printf(星期四\n); break; case 5: printf(星期五\n); break; default: printf(周末\n); break; } return 0; } /* 运行结果 星期三 */每个 case 分支末尾的break;非常重要它用来跳出 switch防止“穿透”到下一个分支继续执行。如果漏写 break程序会继续执行后面的 case这是新手常踩的坑。1.3.3 for 循环for 循环适合循环次数已知的场景由初始化、条件判断和步进三部分组成。下面是一个计算 1 到 100 累加和的小程序#include stdio.h int main() { int sum 0; for (int i 1; i 100; i) { sum i; } printf(1 2 ... 100 %d\n, sum); return 0; } /* 运行结果 1 2 ... 100 5050 */for 循环的三个部分都可以省略但分号不能少。比如for (;;)就是一个死循环需要配合 break 才能退出。1.3.4 while 循环while 循环先判断条件条件为真才执行循环体适合循环次数不确定的场景。下面是一个不断让用户输入正数、直到输入负数才停止的小程序#include stdio.h int main() { int num; int count 0; printf(请输入一个整数输入负数结束); scanf(%d, num); while (num 0) { count; printf(第 %d 次输入%d\n, count, num); printf(请再输入一个整数输入负数结束); scanf(%d, num); } printf(共输入了 %d 个非负数\n, count); return 0; } /* 运行示例假设依次输入 5、8、-1 请输入一个整数输入负数结束5 第 1 次输入5 请再输入一个整数输入负数结束8 第 2 次输入8 请再输入一个整数输入负数结束-1 共输入了 2 个非负数 */while 循环的特点是“先判断、后执行”如果条件一开始就不成立循环体一次都不会执行。1.3.5 do-while 循环do-while 循环与 while 相反是“先执行、后判断”所以循环体至少会执行一次。下面是一个猜数字的小程序至少会让用户猜一次#include stdio.h int main() { int answer 7; int guess; do { printf(请猜一个 1~10 的数字); scanf(%d, guess); if (guess answer) { printf(猜大了\n); } else if (guess answer) { printf(猜小了\n); } else { printf(恭喜猜对了\n); } } while (guess ! answer); return 0; } /* 运行示例假设依次猜 5、9、7 请猜一个 1~10 的数字5 猜小了 请猜一个 1~10 的数字9 猜大了 请猜一个 1~10 的数字7 恭喜猜对了 */do-while 常用于“至少要执行一次”的场景比如菜单选择、密码输入校验等。注意 do-while 的 while 后面必须加分号;。1.3.6 循环嵌套循环嵌套是指在一个循环体内再写一个循环常用于处理二维结构比如打印乘法表。下面是一个打印 9x9 乘法表的小程序#include stdio.h int main() { for (int i 1; i 9; i) { for (int j 1; j i; j) { printf(%d*%d%-2d , j, i, i * j); } printf(\n); } return 0; } /* 运行结果 1*11 1*22 2*24 1*33 2*36 3*39 1*44 2*48 3*412 4*416 1*55 2*510 3*515 4*520 5*525 1*66 2*612 3*618 4*624 5*630 6*636 1*77 2*714 3*721 4*728 5*735 6*742 7*749 1*88 2*816 3*824 4*832 5*840 6*848 7*856 8*864 1*99 2*918 3*927 4*936 5*945 6*954 7*963 8*972 9*981 */外层循环控制行数内层循环控制每行打印的列数。嵌套循环的总执行次数是各层循环次数的乘积写的时候要注意控制好边界条件。1.3.7 break 与 continue 跳转break用于立即跳出当前循环continue用于跳过本次循环的剩余语句、直接进入下一次循环。下面是一个综合示例演示两者的区别#include stdio.h int main() { // break找到第一个能被 7 整除的数就停止 for (int i 1; i 20; i) { if (i % 7 0) { printf(break找到 %d停止循环\n, i); break; } printf(break当前 i %d\n, i); } printf(----\n); // continue跳过所有偶数只打印奇数 for (int i 1; i 10; i) { if (i % 2 0) { continue; // 跳过偶数不执行下面的打印 } printf(continuei %d\n, i); } return 0; } /* 运行结果 break当前 i 1 break当前 i 2 break当前 i 3 break当前 i 4 break当前 i 5 break当前 i 6 break找到 7停止循环 ---- continuei 1 continuei 3 continuei 5 continuei 7 continuei 9 */简单总结break是“彻底退出”continue是“跳过本次”。在嵌套循环中break 只会跳出它所在的那一层循环如果需要跳出多层可以借助标志变量或 goto 语句goto 不推荐新手使用。1.4 函数与模块化设计说明函数的定义、参数传递、返回值与递归调用介绍如何通过函数拆分实现代码复用与模块化。函数是C语言实现代码复用的核心工具。把一段重复使用的逻辑封装成函数主程序调用时只需写一行既清晰又便于维护。下面先看一个最简单的函数定义与调用示例#include stdio.h // 函数定义计算两个整数的和 int add(int a, int b) { return a b; } int main() { int result add(3, 5); // 函数调用 printf(3 5 %d\n, result); return 0; } /* 运行结果 3 5 8 */这里int add(int a, int b)就是函数定义int是返回值类型add是函数名括号里的int a, int b是形参。主函数里add(3, 5)把实参 3 和 5 传给形参函数计算后通过return把结果返回给调用处。新手最容易忽略的一点是形参是值的拷贝在函数内部修改形参不会影响外面的实参。看下面这个例子#include stdio.h void change(int x) { x 100; // 只修改了形参 x 的拷贝 printf(函数内部 x %d\n, x); } int main() { int num 10; change(num); printf(函数外部 num %d\n, num); // 仍然是 10 return 0; } /* 运行结果 函数内部 x 100 函数外部 num 10 */如果想在函数里真正修改外部变量就要用到指针后面章节会详细讲。这里先记住结论普通参数是传值指针参数才能传地址。递归是函数调用自身的一种写法非常适合解决“规模不断缩小、规律相同”的问题。最经典的例子就是计算阶乘#include stdio.h // 递归计算 n 的阶乘 int factorial(int n) { if (n 1) { return 1; // 递归出口 } return n * factorial(n - 1); // 递归调用 } int main() { printf(5! %d\n, factorial(5)); return 0; } /* 运行结果 5! 120 */递归一定要有递归出口上面的if (n 1)否则会无限调用导致栈溢出。新手可以先照着抄一遍跑通再试着改成计算斐波那契数列体会递归的“分而治之”思想。最后看一个模块化设计的综合示例把“求最大值”和“求平均值”封装成两个函数主函数只负责调用代码结构一目了然#include stdio.h // 求数组最大值 int get_max(int arr[], int n) { int max arr[0]; for (int i 1; i n; i) { if (arr[i] max) { max arr[i]; } } return max; } // 求数组平均值 double get_avg(int arr[], int n) { int sum 0; for (int i 0; i n; i) { sum arr[i]; } return (double)sum / n; } int main() { int scores[] {88, 92, 76, 85, 90}; int n 5; printf(最高分 %d\n, get_max(scores, n)); printf(平均分 %.1f\n, get_avg(scores, n)); return 0; } /* 运行结果 最高分 92 平均分 86.2 */把功能拆成一个个小函数每个函数只做一件事这就是模块化设计的基本思想。新手写代码时可以先在 main 里把逻辑写通再逐步把重复或独立的部分抽成函数代码会越来越清晰。1.5 指针与地址预告指针是C语言中最核心也最难啃的难点之一很多初学者在这里栽了跟头。简单来说指针就是用来存放变量地址的变量通过它我们可以间接访问和修改内存中的数据。在后续章节中我们将详细讲解指针变量的声明、取地址符、解引用符*以及指针与数组之间的紧密关系。先看一个最简单的指针声明与使用示例建立初步印象#include stdio.h int main() { int num 10; // 普通变量 int *p num; // 指针变量 p 存放 num 的地址 printf(num 的值 %d\n, num); printf(num 的地址 %p\n, num); printf(p 指向的值 %d\n, *p); // 解引用取出 p 指向的值 return 0; } /* 运行结果地址为示例值实际每次运行可能不同 num 的值 10 num 的地址 0x7ffd5a3b2c14 p 指向的值 10 */这段代码中int *p num;声明了一个指向 int 的指针 p并用取地址符把 num 的地址赋给它*p则是解引用取出 p 所指向地址中存放的值。理解了指针后面的数组、链表和嵌入式寄存器操作都会顺畅很多我们下一节见。在进入下一节之前我们再补充一个指针与数组结合的实战示例。数组名在C语言中本质上就是一个指向首元素的指针常量因此我们可以用指针来遍历数组甚至通过指针直接修改数组元素。下面这段代码演示了这两种操作#include stdio.h int main() { int arr[5] {10, 20, 30, 40, 50}; int *p arr; // 数组名 arr 就是首元素的地址等价于 arr[0] // 用指针遍历数组 printf(用指针遍历数组\n); for (int i 0; i 5; i) { printf(arr[%d] %d地址 %p\n, i, *(p i), (void *)(p i)); } // 通过指针修改数组元素 *(p 2) 99; // 把第 3 个元素改成 99 p[4] 100; // p[4] 和 arr[4] 完全等价改成 100 printf(\n修改后的数组\n); for (int i 0; i 5; i) { printf(arr[%d] %d\n, i, arr[i]); } return 0; } /* 运行结果地址为示例值实际每次运行可能不同 用指针遍历数组 arr[0] 10地址 0x7ffd5a3b2c14 arr[1] 20地址 0x7ffd5a3b2c18 arr[2] 30地址 0x7ffd5a3b2c1c arr[3] 40地址 0x7ffd5a3b2c20 arr[4] 50地址 0x7ffd5a3b2c24 修改后的数组 arr[0] 10 arr[1] 20 arr[2] 99 arr[3] 40 arr[4] 100 */这段代码有两个关键点第一int *p arr;中数组名 arr 会隐式转换为指向首元素的指针所以 p 和 arr 指向同一个地址第二*(p i)与p[i]完全等价都是按下标访问元素而*(p 2) 99则演示了通过指针直接修改数组元素。理解了这一点你就已经摸到了指针与数组关系的门道下一节我们会在此基础上深入展开。1.5 数组与字符串处理数组是C语言中最常用的数据结构之一它把同类型的多个数据连续存放在内存中。很多新手觉得数组高级其实它的原理非常简单——就是一段连续的内存通过下标就能直接访问。下面先看一个最经典的一维数组示例#include stdio.h int main() { // 声明并初始化一个长度为 5 的整型数组 int scores[5] {88, 92, 76, 85, 90}; // 遍历数组并打印每个元素 for (int i 0; i 5; i) { printf(scores[%d] %d\n, i, scores[i]); } return 0; } /* 运行结果 scores[0] 88 scores[1] 92 scores[2] 76 scores[3] 85 scores[4] 90 */这段代码的核心就两件事int scores[5] {...}在内存中开辟了 5 个连续的 int 空间scores[i]按下标取值。数组下标从 0 开始所以最后一个元素是scores[4]而不是scores[5]这是新手最容易越界的地方。再来看二维数组它本质上是数组的数组常用于矩阵、棋盘这类二维结构#include stdio.h int main() { // 3 行 4 列的二维数组 int matrix[3][4] { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }; // 双重循环遍历 for (int i 0; i 3; i) { for (int j 0; j 4; j) { printf(%2d , matrix[i][j]); } printf(\n); } return 0; } /* 运行结果 1 2 3 4 5 6 7 8 9 10 11 12 */二维数组的遍历用双重循环外层循环控制行内层循环控制列。理解了这个规律再复杂的多维数组也只是循环层数增加而已。关于字符串C语言没有专门的字符串类型它就是用字符数组来存储的以\0结尾#include stdio.h #include string.h int main() { char name[20] C语言; // 字符数组存储字符串 char greeting[] Hello; printf(name %s\n, name); printf(长度 %d\n, (int)strlen(name)); // strlen 计算长度不含 \0 // 字符串拼接 strcat(greeting, , World!); printf(拼接后 %s\n, greeting); return 0; } /* 运行结果 name C语言 长度 6 拼接后 Hello, World! */这里要记住字符数组的长度要留出\0的位置比如char name[20]实际最多存 19 个字符。用strlen获取长度时它返回的是不含结尾\0的字符个数。最后分享一个学习技巧数组和指针其实是相通的scores[i]等价于*(scores i)。很多同学觉得指针难其实把它想成数组下标的另一种写法就豁然开朗了。学这类较难知识点时我的方法是先抄一遍经典代码跑通再试着改几个参数看结果变化最后合上书自己默写一遍。三步走下来比死记概念高效得多。数组看上去高级本质就是连续内存 下标访问这个简单的数学原理把基础打牢后面学指针、链表都会轻松很多。二、指针、内存管理与进阶应用本章深入C语言的核心难点——指针与内存管理并结合结构体、文件操作等进阶内容为嵌入式开发打下基础。2.1 指针基础与指针运算解释指针的概念、取地址与解引用操作以及指针与数组、函数之间的紧密关系。2.2 动态内存分配介绍malloc、calloc、realloc与free的用法分析内存泄漏与野指针的成因及规避方法。2.3 结构体与联合体讲解结构体、联合体的定义与使用说明它们在复杂数据组织和硬件寄存器操作中的价值。2.4 文件操作与输入输出介绍标准输入输出与文件读写接口演示如何通过C语言处理文本和二进制数据。2.5 预处理指令与宏定义梳理宏定义、条件编译与头文件包含等预处理机制帮助读者理解编译流程与代码组织方式。三、C语言在嵌入式开发中的实践本章将C语言知识与物联网嵌入式方向结合介绍寄存器操作、中断处理与项目实战思路体现C语言在硬件领域的实际价值。3.1 嵌入式开发环境搭建介绍常用的交叉编译工具链、烧录工具与调试方法说明如何搭建一套可运行的嵌入式开发环境。3.2 寄存器操作与硬件抽象讲解如何通过指针操作寄存器、配置GPIO与定时器并引入硬件抽象层HAL的设计思路。3.3 中断处理与实时性设计介绍中断向量、中断服务函数的编写要点以及如何在资源受限环境下保证程序的实时响应。3.4 小型嵌入式项目实战以一个简单的传感器数据采集与串口输出项目为例串联本章所学知识展示从需求分析到代码落地的完整流程。总结与下期预告本文围绕C语言基础语法展开系统梳理了数据类型与变量、运算符与表达式、流程控制语句、函数与模块化设计并对指针与地址做了初步预告。从整型、浮点型到字符型从算术、关系、逻辑到赋值运算符再到 if-else、switch 与 for、while、do-while 循环每一块都是后续深入学习的基石。函数部分则展示了如何通过模块化设计让代码更清晰、更易维护。下一期我们将深入讲解指针与内存管理包括指针的声明与运算、动态内存分配、内存泄漏与野指针的规避等核心内容这些正是通往嵌入式开发的关键台阶。如果你对硬件方向感兴趣建议在掌握基础语法后沿着「C语言基础 → 指针与内存 → 寄存器操作 → 小型项目实战」的路径循序渐进逐步建立起软硬结合的能力。总结回顾C语言学习的核心脉络强调基础语法、指针内存与嵌入式实践三者层层递进的关系并鼓励读者通过持续编码与项目积累逐步提升。参考资料《C程序设计语言》KR《C和指针》《嵌入式C语言自我修养》GNU C 手册与相关在线文档