
这段时间一直在做两件事刷C语言基础练习以及每天固定啃一点计算机英语。目前C语言练习做到第18期英语词汇也到了第12天两个进度叠在一起反而让我发现了一些单刷任何一个都体会不到的东西——写代码卡壳的地方回头看英文原版定义往往一下就通了而背单词时遇到的术语敲代码时又成了天然的“记忆锚点”。这篇就把这两条线放在一起聊一聊。C语言部分我会挑这期练习里最典型的几道题说说思路、对比写法、填上那些教科书里不讲但实际敲代码才会踩的坑计算机英语部分则分享一下这12天里我筛选出来的高频术语以及怎么把它们真正用起来而不是停在“眼熟”层面。适合正在系统学C语言、同时又想提升英文文档阅读能力的初学者参考也欢迎已经入行的朋友一起纠正补充。1. 内容整体设计与思路拆解1.1 为什么把编程练习和英语学习放在同一条线说白了编程语言和自然语言都是“语言”。写C语言代码是在跟编译器沟通读英文文档是在跟全世界的开发者沟通两者共用同一套底层能力语法识别、语义理解、模式匹配。我自己的体会是代码写多了以后再看英文文档不会先“翻译成中文再理解”而是直接以英文的逻辑去理解那个概念速度提升非常明显。具体到C语言它的正统教材、标准文档、开源项目注释基本都以英文为主。比如你查malloc中文资料总喜欢翻译成“动态内存分配”听起来高大上但英文原意是“memory allocation”memory是内存allocate是分配拆开一看就特别直白。再比如while这个词本身就是“当……时”的意思do-while就是“先做当……时继续”把英文含义搞清楚了两者的区别就再也忘不掉。所以这一阶段的规划是每天刷3到5道C语言基础题同时每天积累10到15个计算机英语词汇。做题时遇到的关键字、函数名、报错信息我会顺手抄进英语本里背单词时遇到的概念我会要求自己立刻回忆起对应的C语言用法。两条线互相喂效率比单线高很多。1.2 第18期练习的选题逻辑这一期的练习范围集中在几个方向while与do-while的对比使用、字符串处理、数组排序、简单文件读写、以及一些涉及日期运算的数学逻辑题。这些内容在C语言知识体系里属于“承上启下”的位置——前面是变量、运算符、分支结构后面是指针、结构体、动态内存。如果这一层的地基不稳后面的链表、树、文件系统接触起来会非常吃力。为什么要专门挑这些题因为它们考察的从来不是“会不会背语法”而是“能不能把语法用对场景”。举一个这期练习里的例子计算某年某月某日是当年的第几天。看起来只是switch-case的堆叠但真正优化起来可以用数组预处理每月天数、用函数封装闰年判断还牵扯到static数组和const修饰符的选择。说实话能把这道小题写出三种以上版本的人数组和函数这两个章节基本没问题了。另外这期练习里我也刻意加入了文件读写的内容因为很多初学者学到文件操作就卡住。原因倒不难理解——文件操作涉及FILE*指针、打开模式、缓冲区刷新等一堆新概念抽象程度比printf高一个台阶。再加上文本文件在Windows和Linux下的换行符不同\r\n和\n的问题光是把“读一行字符串”跑通就够研究半天。这些我都会在后文详细拆。2. 核心细节解析与实操要点2.1 while 与 do-while一个先判断一个先执行这个知识点几乎是每套C语言基础题里必出现的但真正能说明白的人不多。简单说while先判断条件条件为真才执行循环体所以循环体可能一次都不执行。do-while先执行一次循环体再判断条件所以循环体至少执行一次。听上去很简单但实际写代码时很多人习惯性地都用while结果在“至少执行一次”的场景里被迫在循环前重复写一遍逻辑。比如要把用户输入的数字累加直到输入0结束用while就得先读一次再进循环而do-while就自然得多#include stdio.h int main(void) { int num, sum 0; do { printf(请输入一个数字输入0结束: ); scanf(%d, num); sum num; } while (num ! 0); printf(总和 %d\n, sum); return 0; }这段代码天然契合“先做一次再判断”的业务逻辑。反之如果需要“当条件满足时才执行”比如遍历链表直到指针为空那while才是正确的选择。选哪种循环本质上不是在背语法而是在理解业务逻辑的执行时机。有一个细节很多人忽略了do-while的while(...)后面必须加分号。这个分号非常容易漏一漏就是编译错误。初学者经常花十分钟找bug结果发现就是少写一个分号。我自己的习惯是写完do-while立刻检查一遍分号——这个习惯救过我很多次。2.2 指针、取地址符与内存管理的基础认知这一节是C语言的“劝退点”也是“分水岭”。第18期练习里已经开始涉及简单的指针操作比如用指针遍历数组、用指针交换两个变量的值。很多初学者在这里最大的困惑是和*到底是啥关系换算成生活场景来理解假设内存是一条巨大的街道每个房间内存单元都有门牌号地址。变量的名字就是贴在房间上的便签而变量名相当于查这个房间的门牌号*指针变量则是拿着门牌号找到房间、打开门看看里面放了什么。所以int a 5;相当于在某个房间放了数字5int *p a;相当于记下了a房间的门牌号*p 10;相当于按门牌号找到房间把里面的5改成10指针的难点不在语法而在“间接”这个思维方式。平时我们直接操作变量指针则是在变量外面又包了一层“门牌号”。初学者最容易犯的错是混淆p、a、*p三者的含义p存的是门牌号a是取门牌号*p是门牌号对应房间里的值。这三者的类型也完全不一样用错就是编译警告甚至程序崩溃。关于内存管理这一期暂时只要求理解栈区和全局区的基本概念先不深入malloc和free。但我建议有这个意识全局变量和static局部变量放在静态存储区程序启动时就分配好了普通局部变量放在栈区函数调用结束就自动释放。后面学到动态内存分配时这个概念会反复用上。2.3 字符串函数与文件读写高频工具函数盘点C语言没有原生的“字符串”类型只有字符数组加\0结尾。这个设计导致字符串处理比高级语言麻烦但也因此更透明。这期练习里我重点用了几个函数strlen—— 返回字符串长度不含\0strcmp—— 比较两个字符串返回0表示相等strcpy—— 把一个字符串复制到另一个数组strcat—— 把一个字符串拼接到另一个后面这四个函数在使用时有个通病都不检查目标缓冲区大小。strcpy和strcat如果目标数组不够大就会越界写入轻则数据错乱重则程序崩溃。安全一点的写法是用strncpy和strncat指定最大复制长度但这两个函数又有“不自动补\0”的坑。C语言就是这么朴实无华——每个工具都有代价理解代价比记住函数名重要。文件读写部分这期练习我写了一个“统计文本文件行数”的小程序。核心就三步fopen打开文件、循环读字符数换行、fclose关闭文件。但中间有个细节让我踩了坑Windows下文本文件换行是两个字符\r\n直接按\n统计会把\r漏掉某些情况下统计结果会偏差。解决方法是把文件以文本模式打开默认就是C标准库会把\r\n转成\n处理但如果你用二进制模式rb打开就要自己处理换行符。这个问题在Linux下不存在但跨平台时一定要留意。3. 实操过程与核心环节实现3.1 字符串逆序从思路到代码这道题是这期练习里比较经典的一道给定一个字符串把它的字符顺序颠倒过来输出。有不少新手第一反应是“从后往前遍历打印”这确实是解法之一#include stdio.h #include string.h int main(void) { char str[100]; printf(请输入一个字符串: ); fgets(str, sizeof(str), stdin); str[strcspn(str, \n)] \0; // 去掉fgets带入的换行符 int len strlen(str); for (int i len - 1; i 0; i--) { putchar(str[i]); } putchar(\n); return 0; }如果要求“原地逆序”也就是不借助额外数组那就用双指针法一个指针指向开头一个指向结尾交换两个位置的字符然后向中间移动。这里要注意的是fgets会读取换行符直接strlen会把它也算进去所以用strcspn把换行符替换成\0是很实用的小技巧。这个细节在很多网上的题解里都不会讲但实际敲代码时一定会碰到。原地逆序的代码长这样#include stdio.h #include string.h void reverse(char *s) { int left 0; int right strlen(s) - 1; while (left right) { char tmp s[left]; s[left] s[right]; s[right] tmp; left; right--; } } int main(void) { char str[100]; printf(请输入一个字符串: ); fgets(str, sizeof(str), stdin); str[strcspn(str, \n)] \0; reverse(str); printf(逆序结果: %s\n, str); return 0; }这里就体现了一个英语词汇的重要性reverse本身就是“反转、颠倒”的意思看到函数名就懂函数功能。很多教材里的示例代码喜欢用f、g这类无意义命名实际开发中如果都用reverse、swap、merge这类“动词化”命名代码会好读很多。3.2 日期计算两种优化方法对比题目输入年、月、日输出这是当年的第几天。最“老实”的写法是switch-case累加每个月的天数再根据闰年调整二月。但既然学了数组和函数完全可以写得更漂亮。方法一用数组预处理每月天数。#include stdio.h int isLeap(int year) { return (year % 4 0 year % 100 ! 0) || (year % 400 0); } int main(void) { int year, month, day; printf(请输入年 月 日: ); scanf(%d %d %d, year, month, day); int days[] {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; if (isLeap(year)) { days[1] 29; } int total 0; for (int i 0; i month - 1; i) { total days[i]; } total day; printf(%d年%d月%d日是当年的第%d天\n, year, month, day, total); return 0; }方法二用static数组避免每次调用都重新初始化。如果这段逻辑封装成函数每次调用都重新定义并填充days数组虽然开销不大但写成static const int days[]可以明确告诉编译器这是只读数据、放在静态存储区既清晰又高效。日期题的核心难点不在语法而在“闰年判断”的边界条件。判断闰年的规则是“能被4整除但不能被100整除或者能被400整除”这个规则很容易记错。有个记忆技巧400年是一个大周期所以能被400整除的一定是闰年能被100整除但不能被400整除的不是闰年其余能被4整除的就是闰年。以后写任何与日期相关的工具这个判断务必抄对。3.3 冒泡排序交换次数与边界控制冒泡排序是算法入门第一课。这个排序之所以叫“冒泡”是因为每轮比较都会把当前最大的数“冒泡”到数组末尾像水里的气泡往上升一样。写下最基础的版本不难难的是理解每一轮循环的边界是怎么推出来的。#include stdio.h void bubbleSort(int arr[], int n) { for (int i 0; i n - 1; i) { for (int j 0; j n - 1 - i; j) { if (arr[j] arr[j 1]) { int tmp arr[j]; arr[j] arr[j 1]; arr[j 1] tmp; } } } } int main(void) { int arr[] {5, 2, 9, 1, 5, 6}; int n sizeof(arr) / sizeof(arr[0]); bubbleSort(arr, n); for (int i 0; i n; i) { printf(%d , arr[i]); } printf(\n); return 0; }很多初学者会疑惑为什么外层循环是n - 1为什么内层循环是n - 1 - i原因很简单n个数最多需要n - 1轮排序因为每一轮至少能确定一个数的最终位置排完n-1个剩下的自然就位而每轮结束后末尾的i个数已经是排好的无需再参与比较所以内层比较次数要减去i。进阶一点可以在每轮开始前加一个“是否发生交换”的标记如果一轮下来没有任何交换说明序列已经有序直接跳出循环。这个优化叫“提前终止”在处理已经接近有序的数据时能省不少时间。这类优化思想比背排序代码更具迁移价值——以后写任何循环都应该问一句“有没有可能提前结束能不能减少无效计算”4. 计算机英语Day12把术语变成肌肉记忆4.1 这12天里最有价值的几组术语每天记十几个计算机英语词汇坚持12天差不多积累了150个左右。这150个里我筛选出几组真正高价值的它们几乎每天写代码都会遇到declare/definition声明 / 定义。C语言里声明只是告诉编译器“有这么个东西”定义才是真正分配内存。这两个词搞不清理解头文件机制会一直吃力。allocate/release分配 / 释放。对应malloc/free也对应内存的两个状态allocate是拿到release是还回去。iterate/recursion迭代 / 递归。循环是迭代函数调用自己是递归。两个词在算法讨论里高频出现。buffer/overflow缓冲区 / 溢出。buffer是临时存放数据的区域overflow是数据放不下溢出来了。缓冲区溢出是C语言安全问题的头号来源。compile/link编译 / 链接。C语言从源码到可执行文件需要经过编译和链接两个阶段undefined reference这类错误就是链接阶段的经典报错。pointer/reference指针 / 引用。C语言只有指针C才有引用但如果英文资料里出现dereference解引用对应的就是*p这个操作。这些词有个共同特点它们描述的是动作和状态而不是名词本身。比如iterate是“迭代”动词强调的是“重复执行”的过程overflow是“溢出”名词/动词兼有强调的是“超过边界”的状态。把这类词当成动词记再去读代码里的注释和报错信息会顺畅很多。4.2 怎么把英语文档反哺到C语言理解背单词不是目的能读文档能排查报错才是目的。我给自己定了个规矩遇到不懂的C语言概念先查英文资料再对照中文资料理解。英文写得不复杂但很多细节表述得非常精确。比如说undefined behavior——中文经常翻译成“未定义行为”听起来很学术英文原文是“the behavior is not defined in the standard”意思是“标准没规定会发生什么”所以编译器想干嘛就干嘛。这个英文表述直接揭示了为什么不要依赖未定义行为——因为你根本不知道程序会怎样表现。另一个例子是sequence point序列点。C语言里i i是危险的就是因为中间没有序列点。中文资料讲这个知识点总是云里雾里但英文里 “sequence point is a point in the execution where all previous evaluations are complete and all side effects are done” 直接说明了“一个确保前面所有操作都完成、副作用都生效的时间点”一下就清楚了。所以我的建议是每天背的词汇不要孤立地背拼写和中文意思一定要跟C语言的语法点绑定。比如背到static这个词时同时回顾它在C语言里的三个作用修饰局部变量使其生命周期延长、修饰全局变量使其仅在本文件可见、修饰函数同理。这样每背一个词等于复习了C语言的一个知识点一天10个词就是10个知识点一个月下来积累量会很可观。5. 环境配置与常见问题排坑实录5.1 开发环境的选择VSCode搭配C/C插件的细节很多初学C语言的人第一个问题就是“用什么软件写代码”。目前主流的方案有Visual Studio、Dev-C、Code::Blocks、VSCode等。我个人更推荐VSCode原因有三个轻量、插件生态强、跨平台一致性好。但VSCode本身只是一个编辑器要让它能编译运行C语言还需要安装C/C扩展和一个编译器Windows下推荐MinGW-w64macOS用自带的clangLinux用gcc。我在配置过程中踩过的最大坑是装完插件后依然找不到编译器路径。原因通常是MinGW的bin目录没有加到系统环境变量PATH里或者VSCode没有重启识别新环境变量。解决方法是手动打开终端输入gcc --version如果能输出版本信息说明 PATH 没问题再重启VSCode即可。还有一个小坑是任务配置文件tasks.json的写法。如果直接从Run菜单选Run Without DebuggingVSCode会默认使用上一次的任务配置首次使用需要手动生成tasks.json指定args里至少要包含源文件名和-o输出文件名。很多教程会写固定的文件名比如main.c但实际项目里我经常同时打开多个源文件所以更推荐在args里用${file}和${fileDirname}/${fileBasenameNoExtension}.exe这样的变量自适应当前文件。5.2 新手最容易踩的5个编译/运行问题这期练习期间我在学习群里帮人排查了不少问题挑几个频率最高的整理成表格问题现象根本原因快速排查方法编译报错fatal error: xxx.h: No such file or directory头文件路径不对或者头文件本身不存在检查#include里的文件名和项目目录是否一致编译报错undefined reference to xxx编译和链接分离缺少对应函数实现确认是否把所有.c文件一起编译程序运行闪退什么也不输出可能是数组越界、指针未初始化导致段错误用printf在关键位置打印调试信息定位崩溃点用fgets读字符串时程序跳过了后续输入fgets把换行符也读进去了影响后续scanf在fgets后加str[strcspn(str, \n)] \0;double类型取余报错C语言的%只能用于整数浮点取余要用fmod使用math.h里的fmod编译时加-lm顺手展开说两点。double取余这个问题我在网上看到不少新手问“为什么5.2 % 2报错”其实是C语言规范里%运算符只接受整数操作数浮点数取余需要fmod函数而fmod的返回值和整数取余的符号规则还不太一样使用时需要额外留意。另一个是 C语言里符号的双重身份一个作为“取地址”运算符比如int *p a;另一个作为“按位与”运算符比如5 3结果是1。这两个用法初学者容易混口诀是“左边有类型就是取地址左右都是值就是按位与”。还有一个非常隐蔽的坑scanf的返回值。很多人写完scanf(%d, num);之后就默认读成功了但实际上如果用户输入了非数字内容scanf会返回0num的值保持不变。如果程序后续用这个num做运算结果就是错的。正确处理方式是检查返回值或者在循环里把输入清理掉再重新读。这一点在刷题网站的多组输入场景里尤其重要用例一多输入不规范是常态你不检查返回值就等着 “Wrong Answer” 吧。6. 关于持续练习的一点心得第18期练习和第12天英语听起来都只是漫长学习曲线里不起眼的一小段但真正走下来你会发现每天固定学习两小时比周末猛学十小时有效得多。C语言的知识点很多都是环环相扣的比如这期练习里“字符串逆序”用到的双指针思想后面学链表反转、快排划分时还会遇到而“日期计算”里对闰年的判断几乎是所有时间处理库的基础。我个人在实际操作中的体会是练习不止是把题做对还要留意“有没有更优的写法”、“有没有更安全的写法”、“这个函数名到底怎么读”。把这些习惯固化下来收获会比刷一百道题还大。至于计算机英语不用一开始就死磕原版大部头从每天的报错信息开始读起再慢慢扩展到man手册、官方文档一步步来等到某天你发现自己不再依赖翻译工具时就已经进入新的境界了。最后再分享一个小技巧每次做完一道题花30秒在代码旁边用英文注释写一句“这道题的关键点”。比如// This loop stops when left and right meet.这样既复习了英语写作又把代码逻辑重新梳理了一遍一举两得。这个习惯我还在坚持目前看来效果不错你可以试试看。