
秋招倒计时这种事只有真的开始数日子才知道慌。今天是菜鸟自学C语言的第4天进度谈不上快但脑子比头三天清楚了不少——至少现在我看代码不再是每个字都认识连起来不知道在说什么的状态了。秋招笔试里C语言是很多公司的必考项尤其是嵌入式、客户端、底层开发这些方向算法题允许你用C写但如果你连指针和内存都搞不明白写出来的代码自己心里都没底。Day4的定位很明确把基础语法拧紧把数组、字符串、指针这几个高频考点啃一遍再用调试工具解决自己写代码时遇到的报错。这篇文章会把今天学的东西、踩的坑、查的资料全部整理出来给同样在秋招路上死磕C语言的朋友做个参考。这篇东西适合两类人一是像我一样从零开始自学C语言、目标秋招的应届生二是已经在学校上过课但觉得自己基础不扎实、想系统梳理一遍的在校生。Day4不是从Hello World开始的菜鸟教学而是记录一个自学者在掌握基本输入输出和变量类型之后如何推进到数组、指针、字符串处理和基础调试的完整过程。我会把今天涉及的每个知识点、每道练习题、每个报错场景都讲清楚包括为什么这样做、错在哪、怎么排查尽量还原一个真实的自学现场。1. Day4学习规划与核心内容拆解1.1 自学路线第四天的合理定位C语言自学最怕两件事一是贪多嚼不烂二是东一榔头西一棒子。Day1装环境、跑通Hello WorldDay2搞定变量、数据类型和printf输出Day3开始接触scanf输入和运算符优先级当时差点被那个前置后置自增搞疯。到了Day4我觉得可以开始碰真正的程序逻辑了——循环结构、数组、字符串这些是后续学指针、结构体、链表的地基也是秋招笔试里出现频率最高的基础题考察范围。今天给自己定的任务量不大三块核心内容加一道真题练习。第一块是把运算符和表达式彻底搞清楚重点解决ab这类表达式的计算顺序问题第二块是循环结构把while、do-while、for三者的区别和使用场景弄明白第三块是数组和字符串包括字符串逆序、指针初探、数组遍历。晚上用一两道PAT或PTA的练习题检验成果再花时间把今天遇到的编译报错和调试方法记录下来。这样安排的理由很简单秋招笔试的C语言题本质上就是在考你会不会用数组“能不能写好循环”“知不知道字符串的坑”这几样搞定了基础题基本能拿分。1.2 今日知识版图从语法到调试的全链路Day4要覆盖的知识点看起来零散但串起来就是一条完整的学习链路表达式运算 - 流程控制 - 批量数据存储 - 字符串处理 - 程序调试。这也是为什么我不建议完全跟着视频一章一章看效率太低最好的方式是知识点 即时练习 报错排查三件套同步走。今天查资料的时候看到了不少热搜词像c语言 ab解释while和do-while区别字符串逆序c语言pta数组 指针 移动 指定位输出 字符这些基本就是我今天在学的东西。还有一些词是后面几天的内容比如冒泡排序完数c语言文件缓冲区fscanf和fprintfgdb调试——冒泡排序和完数可以作为今天的进阶练习做掉文件操作和调试工具则打算放到Day5以后。自学C语言最忌讳的就是看到什么学什么得有自己的节奏。今天这份知识版图就是给Day4画的边界超纲的东西先记下来不急着深入。2. 今日重难点突破运算符、输入输出与循环结构2.1 先搞懂 ab 这类前置后置自增表达式ab和ab这个坑我Day3就踩过今天彻底掰扯明白了。C语言的自增运算符和自减运算符--放变量前面叫前置放后面叫后置。区别在于先运算后赋值还是先赋值后运算。int b 5; int a; a b; // b先自增变成6再赋值给a所以a6b6 a b; // 先把b的当前值5赋给ab再自增变成6所以a5b6这个知识点看起来简单但笔试里喜欢往下挖一层int i 3; int j i i;这种组合到底怎么算我的建议是遇到这种题别硬算直接拆解成语句序列。表达式里同一个变量被多次修改在C语言中其实是未定义行为undefined behavior不同编译器算出来的结果可能都不一样。秋招笔试如果碰到这种题首先要知道它是未定义行为其次才是用自己的编译器验证。我在VS Code里用gcc试了一下i i确实不同优化级别结果不同这说明编译器在处理时没有统一规则。实操中真正应该记住的是前置自增减更适合用在循环条件和独立语句里比如while (i 10)因为你想先改变值再判断后置自增减更适合用在数组遍历和值传递场景比如arr[i]因为你想先用当前下标取值再让下标移动。搞懂这个逻辑比死记硬背放前面还是放后面有用得多。2.2 scanf与printf的高频坑格式控制、缓冲区和类型匹配有网友问c语言里scanf一定要输入abc吗——这显然是把scanf的格式控制串理解错了。scanf(%s, str)确实会读入一串字符但scanf(%d, n)就得输入整数scanf(%f, f)就得输入浮点数。格式占位符定义了你要输入的数据类型不是随便输入什么都能被接受。今天我在练习日期计算时写了这样一段代码int year, month, day; printf(请输入年、月、日); scanf(%d %d %d, year, month, day);这个%d %d %d中间的空格表示输入时可以用空格、换行或制表符分隔三个数字scanf会自动跳过空白字符。但要注意如果你写的是scanf(%d,%d, a, b)输入时就必须带逗号比如5,3否则输入会失败。这是很多新手莫名其妙第一个数读到了第二个数读不到的原因。还有一个高频坑是缓冲区问题。scanf读完数据后回车键产生的换行符会留在输入缓冲区里。如果你先用scanf读整数再用getchar()或gets()读字符会发现字符根本没读到——缓冲区里残留的换行符被读走了。处理方法是用getchar()把残留的换行符吃掉或者用while(getchar() ! \n);把缓冲区清空。我现在写代码凡是涉及连续读入字符和数字混用都会在中间加这一行血泪教训。2.3 while和do-while到底怎么选判断时机决定一切c语言while和do-while区别这个问题如果只说一个是先判断后执行一个是先执行后判断那等于没说因为面试官和笔试不会考你背定义而是给你场景让你选。我的理解是while条件可能一开始就不满足循环体一次都不执行。do-while循环体至少执行一次再判断条件是否继续。看代码更直观int i 10; while (i 10) { printf(while执行了\n); } // 不会输出因为i10不满足i10 int j 10; do { printf(do-while执行了\n); } while (j 10); // 会输出一次先执行循环体再判断j10不成立退出实际中什么时候用do-while最典型的场景是菜单程序你至少要显示一次菜单让用户选择后才判断是否退出。还有一个场景是输入校验你要先让用户输入再判断输入是否合法不合法就继续要求输入。秋招笔试如果考循环多半是让你手写某个逻辑你得根据至少执行一次这个特性来判断该选哪个。顺带说一句for循环本质上是while的变体把初始化、条件判断、循环增量都写在头部适合已知循环次数的情况比如遍历数组、打印九九乘法表。今天我做九九乘法表练习时就用的是嵌套for循环这个下面详细说。3. 数组、字符串与指针入门含经典题实操3.1 字符串逆序PTA题从数组到字符指针的必经之路字符串在C语言里其实就是字符数组以\0结尾。理解了这一点字符串逆序这道题就变成了数组元素交换问题。PTA上的题目是让你输入一个字符串把它逆序输出。我一开始写的是这样#include stdio.h #include string.h int main() { char str[100]; gets(str); // 太老有安全问题下面会讲 int len strlen(str); for (int i len - 1; i 0; i--) { printf(%c, str[i]); } printf(\n); return 0; }这段代码能跑通但是有两个问题。第一gets()这个函数在老版本里是能用的但现代编译器普遍警告它有缓冲区溢出风险因为没办法限制读取长度。PTA平台用的gcc版本一般还能编译但你要是用VS Code配的新版gcc会直接报warning甚至error。应该用fgets(str, sizeof(str), stdin)替代它多了一个长度参数更安全。第二个问题逆序输出用下标遍历没问题但如果题目要求逆序存储到原数组代码就得更具通用性。我重新写了一个双指针版本#include stdio.h #include string.h void reverseString(char *s) { int left 0; int right strlen(s) - 1; while (left right) { char temp s[left]; s[left] s[right]; s[right] temp; left; right--; } } int main() { char str[100]; fgets(str, sizeof(str), stdin); // fgets会读入换行符需要去掉 str[strcspn(str, \n)] \0; reverseString(str); printf(%s\n, str); return 0; }这里有个细节必须注意用fgets读入字符串时换行符\n会被保存到数组里所以要用strcspn(str, \n)定位换行符的位置并替换为\0。这个坑我踩过如果不处理换行符逆序结果末尾会多一个空行。3.2 数组与指针的关系指定位输出字符其实是地址运算数组 指针 移动 指定位输出 字符这个热搜词本质上是在问指针如何配合数组进行定位操作。C语言里数组名是个常量指针指向数组首元素。arr[i]等价于*(arri)。所以如果你想让指针移动到数组的第k个位置再输出字符直接加偏移量就完事了char arr[] hello world; char *p arr; p p 6; // 指针移动6个位置指向w printf(%c\n, *p); // 输出 w还有一种写法是p arr[6];效果一样。我个人的理解习惯是arr[i]是数组视角*(pi)是指针视角两者在绝大多数情况下可以互换。笔试里经常考的p1和p的区别也要搞清楚p1不改变指针本身的值相当于临时算出一个新地址p会修改指针变量本身让指针指向下一个元素。今天我自己用一个指针遍历二维数组写出printf(%c , *(p i * cols j));这种代码刚开始不太理解为什么二维数组的地址偏移是i*colsj而不是i*rowsj。后来想通了二维数组在内存里是连续存储的一行有cols个元素第i行的起始位置就是首地址加上i*cols。这就是把二维数组当一维数组用的核心思路也是很多笔试要考的东西。3.3 九九乘法表和冒泡排序基础代码的练手价值九九乘法表是嵌套循环的经典练习差不多每个C语言新手都写过。我今天的版本是这样的#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; }%-2d是什么意思-表示左对齐2表示占两个字符宽度。如果不加这个格式化控制输出的小数位不齐整体看起来歪歪扭扭。这个格式化输出的细节很值得记笔试手写代码不会考你排版但你自己练习时能写出整齐的输出说明你对printf的格式控制有掌握。冒泡排序属于今天顺手练掉的内容因为后面写题经常要排序虽然标准库有qsort但手写冒泡能帮你理解双重循环和数组元素交换。冒泡的思想一句话每一轮从头开始两两比较大的往后移经过n-1轮排序完成。可以加个标志位优化void bubbleSort(int arr[], int n) { for (int i 0; i n - 1; i) { int swapped 0; for (int j 0; j n - 1 - i; j) { if (arr[j] arr[j 1]) { int temp arr[j]; arr[j] arr[j 1]; arr[j 1] temp; swapped 1; } } if (swapped 0) break; // 没有交换说明已经有序 } }内部循环的n-1-i这个上界是冒泡排序最容易写错的地方。因为每一轮结束后最大的数已经沉到末尾下一轮就不用再比较到末尾了。这个优化不算难但能体现你有没有真正理解冒泡的原理而不是靠记忆硬背。4. 调试实战环境配置、常见报错与gdb初体验4.1 VS Code配置C语言环境从装编译器到跑通第一个程序C语言环境配置是自学路上第一道坎也是最劝退的环节。第一天我卡了快一个小时后来发现其实就是三个组件的事编译器MinGW-w64或gcc、代码编辑器VS Code、扩展插件C/C插件。我推荐用VS Code而不是Visual Studio主要原因是VS Code轻量启动快写小练习和刷题更顺手而且配好后调试体验也很不错。配置步骤下载并安装MinGW-w64安装时注意把安装目录下的bin文件夹路径加到系统环境变量Path里。在终端输入gcc --version能输出版本号说明编译器装好了。VS Code里安装C/C扩展这是微软出的官方插件支持语法高亮、代码补全、调试。写一个hello.c在集成终端里执行gcc hello.c -o hello编译再执行./hello运行。vscode怎么运行c语言代码这个热搜词说明不少人卡在这一步。说实话你不需要记住复杂的tasks.json和launch.json配置最简单的方式就是终端命令编译运行。等你写了更多代码再研究调试配置也不迟。我Day4才开始认真配置调试环境因为前期代码太简单用不上调试器但今天开始写字符串和指针代码后发现调试工具是真的需要了。4.2 无法打开源文件等报错的排查思路c语言无法打开源文件怎么解决——这个报错大概能排进新手常见问题前三名。它的报错信息往往是fatal error: xxx.h: No such file or directory。这里要分清两种情况一种是头文件不存在比如你写了#include stdio.h但编译器说不存在那多半是编译器安装有问题环境变量没配好另一种是你自己写了个#include myheader.h但源文件不在同一个目录下编译器找不到。还有一类更隐蔽的无法打开源文件是扩展名的锅——有些编辑器默认创建的是.txt文件你把它改名为.c格式其实没变编译器自然认不了。这时候需要在VS Code右下角语言模式里手动切到C或者重新创建xxx.c文件。今天我自己遇到的报错是undefined reference to main排查了半天发现是编译时把多个.c文件一起编译了某个文件里没有main函数但编译器认为它在找所有文件里的main。解决方法是只编译当前要运行的源文件或者搞清楚多个文件项目的构建方式。这些报错说起来都不是大问题但心理打击大容易让人怀疑自己是不是不适合写代码。我的心得是把报错信息复制到搜索引擎里搜一遍基本都能找到答案别自己瞎猜。4.3 gdb调试C语言程序从哪里断点、怎么看变量验08利用gdb工具调试c语言程序——gdb是Linux下最经典的C语言调试器秋招笔试不考但实际开发中经常用。我是在Windows的MinGW环境下用gdb调试的VS Code的调试功能底层就是调用gdb。第一次用gdb的人很容易被一堆命令吓到其实核心就这几条# 编译时加-g生成调试信息 gcc -g main.c -o main # 用gdb启动程序 gdb ./main # 在main函数入口打断点 break main # 在某一行打断点比如第20行 break 20 # 运行程序到断点 run # 单步执行进入函数内部用step跳过函数用next step next # 查看变量的值 print i今天我用gdb调试了一个字符串逆序的程序发现指针指向的内容和自己想的不一样。代码里我让指针p指向字符数组中间位置用print p和print *p分别看了地址和值终于理解了指针本身是地址解引用才是数据这句话。这个理解光看书很难建立但自己调试一次就有了。我个人的建议是调试技能不用刻意学当你开始写稍微复杂的代码时遇到程序没报错但结果不对的情况第一个想到用调试器这就够了。报错只能告诉你程序崩了但逻辑错误只能靠调试来定位这是自学过程中最应该早点培养的能力。5. 从题库到项目PAT/PTA题目与小型项目实战5.1 PAT乙级1037 在霍格沃茨找零钱进制转换的练习价值这道题是PAT乙级里非常典型的一道基础题名字听着很魔法核心内容是货币进制转换——在霍格沃茨的货币体系里1加隆等于17西可1西可等于29纳特。题目给你商品价格和实际支付的钱让你算找零要求用标准格式输出x金x银x铜这样的结果。我一开始想复杂了想着分别处理加隆、西可、纳特三个单位的借位写了半天把自己绕晕了。后来换个思路统一换算成最小单位纳特再计算最后再把结果除回去。这是进制转换题的标准解法——把复杂的多位进制运算转成整数运算能省很多事long long toKnut(int g, int s, int k) { return (long long)g * 17 * 29 s * 29 k; }注意这里g*17*29可能超出int范围所以类型要用long long。这是刷题中很容易忽略的细节——你以为数据不大实际乘起来就爆了。PAT乙级的数据范围一般不会太极限但养成用long long处理金额、计数的习惯总没错。拿这道题说它考察的就是进制转换 结构体思维虽然用不上结构体但你需要把三个单位看成一个整体。刷PAT好处在于题目循序渐进从乙级第一题开始你会发现每道题都在帮你巩固某个基础知识点。像霍格沃茨这道题就是一个防坑指南别让单位之间的进制把你绕晕统一到最小单位就傻瓜式解决了。5.2 完数问题从循环判断到代码风格养成完数c语言什么意思——完数这个名词听起来高大上其实就是一个数恰好等于它的所有真因子之和比如6的真因子是1、2、3加起来等于6所以6是完数。这个题很适合Day4练手因为要用到取模运算、循环嵌套和条件判断。我写的版本是#include stdio.h int main() { int n; printf(请输入一个正整数); scanf(%d, n); int sum 0; for (int i 1; i n; i) { if (n % i 0) { sum i; } } if (sum n) { printf(%d是完数\n, n); } else { printf(%d不是完数\n, n); } return 0; }从性能上看这个版本可以优化成只遍历到n/2甚至sqrt(n)但对新手练习来说线性遍历效果一样重点是理解流程。我建议在学习阶段先写能跑的简单版本理解逻辑后再考虑优化而不是一开始就用各种花哨技巧把自己绕晕。秋招笔试手写代码也是如此先把思路顺畅地写出来再谈优化。5.3 从弹球游戏到网吧计费管理小项目练手的正确姿势热搜里出现了c语言项目弹球游戏代码和c语言网吧计费管理小项目。很多新手看到这种项目标题就跃跃欲试但我要泼个冷水Day4不适合碰这种项目。弹球游戏涉及图形界面库graphics.h或SDL这在Windows下配库就是个大工程网吧计费管理涉及变量记录、函数模块、文件存储虽然逻辑不复杂但以Day4的知识储备写起来会比较吃力。我自己的经验是项目分两个阶段。学习初期第1-2周可以写控制台小工具比如刚才的完数判断、日期计算天数、九九乘法表、字符串逆序这些几十行代码就能跑通的东西。等到第3-4周学完函数、数组、指针、结构体、文件操作后再挑战计费管理这类综合小项目就顺理成章了——用户管理用结构体数组数据持久化用文件读写菜单循环用do-while每一个模块都是之前练过的知识点的组合。今天我还看了c语言带宽带计费管理小项目相关资料这类项目的核心模块包括用户信息的增删改查、按时长计费、账单输出。用到的主要技术就是结构体、数组、函数和文件操作。给Day4的自己的建议是先把基础题刷到不用查书也能写出再考虑项目不然容易打击信心。6. 常见问题与学习心得记录6.1 自学C语言最容易踩的坑环境、教材、心态三位一体Day4踩完今天的坑回头看了一眼四天的自学过程发现新手容易踩的坑基本是三类。环境坑最浪费时间。不少人卡在VS Code配好了但运行报错这一步一查发现是编译器根本没装或者环境变量没配。我用的是MinGW-w64安装时默认路径、安装完手动加环境变量、重启终端才生效这三步少一步都会出问题。强烈建议装完后先用gcc --version确认编译器可用再去折腾编辑器不然你根本不知道问题出在哪一环。教材坑是贪多嚼不烂。网上C语言视频一大堆翁恺老师的课口碑最好今天我还特意查了他的练习题讲解节奏确实适合新手。但问题是很多初学者同时看好几个平台的课每个都从Hello World开始讲一周过去了还停在第一集。我的做法是选定一套主教材翁恺的课只做题和参考讲解配C语言Primer Plus当工具书不懂的语法查一下而不是从头到尾读。条条大路通罗马但你不能同时走几条路。心态坑是用不了两天就怀疑自己。今天写指针相关代码时我看了三次书才敢动手写完还是有一行报错。这不是笨是正常的。C语言的指针概念本身就反直觉你要让它在脑海里具体化得靠大量代码练习形成肌肉记忆。我给自己定的规矩是每天至少手写30行代码至少调试一次报错不管多简单。量变引起质变在编程学习里是真的。6.2 给秋招冲刺的自学者几条刻意练习的建议第一把刷题平台用起来。PTA和PAT是程序员刷C语言题最常去的平台PTA的基础编程题目集非常适合新手起步题目短小精悍考点集中。今天做的字符串逆序、霍格沃茨找零钱都是平台上的真题。每天坚持刷1-2道比看十页书有用。刷题时注意看别人提交的代码尤其是运行时间和内存占用更优的解法能学到不少省代码的技巧。第二做完题之后做总结。每道题我都会在个人笔记里记录三样东西这题考了什么知识点、我的思路和标准思路的差异、下次遇到类似题怎么办。秋招笔试的特点是你不知道会碰到什么题但核心考点就那么几个——数组遍历、字符串处理、进制转换、排序、递归。把这些考点的通用解法整理成自己的武器库上考场就不慌了。第三学会利用调试工具定位问题。今天我第一次尝试在调试模式下看变量值发现之前很多想不通的代码行为在一步步执行后变得非常直观。调试器不是出问题了才用平时写代码时也可以用它来看代码的执行流程尤其是学指针和递归的时候效果比看书好太多了。第四记得维护自己的代码库。今天写过的九九乘法表、完数判断、字符串逆序、冒泡排序我都会保存到一个名为c-basics的文件夹里按日期和知识点命名。秋招前复习时直接翻这个文件夹就能快速回忆所有基础知识点不用再去网上到处找资料。这个习惯越早养成越好等积累到几百个文件的时候这就是你独一无二的复习资料库。6.3 一些关于学习节奏的碎碎念最后聊点实在的。自学C语言的第四天我并不觉得自己已经会C语言了——说实话我现在看到const char *p和char *const p还是得反应两秒看到链表还是会头疼。但和第一天比最大的变化是我不再害怕报错了。遇到不懂的先试着搜搜不到就调试调试不通就从头再捋一遍逻辑。这个过程重复多了你会发现自己解决问题的能力在悄悄变强。秋招确实让人焦虑尤其当你看到周围人都在刷LeetCode、做项目、投简历的时候容易觉得自己落后了。但我给自己的定位是长期主义者C语言是基础基础打不牢后面学数据结构、操作系统、计网都会很吃力。Day4只是第四天前面还有Day5、Day10、Day30。按部就班地学稳扎稳打地练等秋招真正开始的时候手里有东西心里才有底。