
1. 先搞懂函数参数C语言初阶最容易踩坑的一关很多初学者学到函数这块第一个困惑往往不是函数怎么写而是“为什么我在函数里改了个变量回到main函数它还是原来的值”。这个困惑几乎人人都有根源就在函数参数的理解上。C语言的函数参数默认是值传递也就是调用函数时把实参的值复制一份交给形参函数内部操作的其实是副本跟原来的变量没有关系。举个例子就明白了。你写了一个交换函数void swap(int a, int b) { int temp a; a b; b temp; }在main里调用swap(x, y)你以为x和y会被交换但实测下来x、y纹丝不动。原因就是swap收到的是x和y的值的拷贝你在函数里交换的是两份拷贝原来那两个变量根本没被碰过。这是C语言初阶里最经典的“值传递陷阱”和后续要学的指针、地址传递、引用这些概念直接挂钩。那怎么才能真正修改外部变量答案是传地址。C语言里可以用指针作为函数参数把变量的地址传进去函数通过地址去改那个内存位置上的数据。比如void swap(int *a, int *b) { int temp *a; *a *b; *b temp; }调用时写swap(x, y)这才能做到真正的交换。初学者往往就在这里开始分叉有人觉得指针难干脆避开有人理解了“传值 vs 传址”的本质后面的链表、树、回调函数全都顺了。回过头看函数参数这节课能不能学透直接影响后续C语言学习的深度。1.1 形参、实参、返回值先理清三个最基本的概念函数参数这块形参和实参是绕不开的第一组术语。形参形式参数是函数定义时写在括号里的变量它没有实际的内存空间只在函数被调用的时候才由实参初始化。实参实际参数是调用函数时真正传入的值、变量或表达式它才是数据的源头。int isPrime(int n) { // n是形参 // 函数体 } int main() { int result isPrime(101); // 101是实参 return 0; }调用isPrime(101)时程序会为形参n开辟一块临时的内存把101这个值复制进去。整个函数执行期间n就是101的副本。函数执行结束这块临时内存就被回收了。这也是为什么你以为“把101传进去了函数就能改动main里的某个变量”——根本动不了因为函数手里只有副本。返回值则是函数的产出。它通过return语句送出来类型必须和函数声明时一致。比如判断素数的函数返回int用1表示“是素数”0表示“不是素数”或者返回boolC99起可以用stdbool.h。返回值这个设计其实就是在解决“函数算完结果怎么带出来”的问题。一个函数只能返回一个值但可以通过指针参数“带出”多个结果这也是C语言常见的技巧。有一个细节很多教材不讲如果函数不需要返回值一定要写void。有些初学者偷懒不写返回类型在老式C编译器中默认是int但现代编译器会报错或警告。规范写法就是要么明确写void要么明确写返回类型不要留模糊地带。1.2 main函数的参数那些被忽略的细节其实很重要很多人把main写成了int main()就完事了却不知道main其实可以带参数。C标准规定的完整形式是int main(int argc, char *argv[]) { // 函数体 }argcargument count命令行参数的个数至少为1因为程序名本身算一个。argvargument vector是一个字符串数组存放每个参数的内容argv[0]是程序名argv[1]起才是用户传入的参数。比如编译出一个prime程序在命令行里执行./prime 100 200那么argc就是3argv[0]是./primeargv[1]是100argv[2]是200。这样程序就能拿到用户给的上下边界了。这里有个很坑的细节argv里存的全是字符串如果你要做数学计算必须先调用atoi()或strtol()转成整数直接拿argv[1]去做加减是行不通的。在写“100-200之间素数练习”这类程序时我建议干脆把函数的设计思路延伸到main参数上让用户从命令行输入范围这样你的程序就不是写死的而是真正可复用的工具。这也是函数参数这节课的实践意义——参数不只是函数的“入口”更是程序与外界交互的通道。1.3 从值传递到地址传递为什么说这是C语言的“分水岭”值传递和地址传递的选择本质上取决于一个问题你希不希望函数改动外部变量只读数据不需要改动外部变量 → 用值传递安全、清晰。需要修改外部变量 → 传地址指针。要返回一个结果 → 用return。要返回多个结果 → 用指针参数“带出”。初学者最容易犯的错是在需要修改外部变量时仍然用值传递然后在函数里改得兴高采烈回到主函数发现一切如故。这个坑我见过太多次了也包括我自己当年写的时候。其实理解原理之后这个坑是可以完全避免的。每次写函数之前先在脑子里问一句“我这个函数要不要改外部的某个变量”如果要参数类型就必须是指针。想清楚了再动手代码质量会提升一大截。2. 素数判断先理解数学本质再谈代码怎么写说完了函数参数接下来进入这个练习的核心内容判断100到200之间的素数。很多初学者一看到这个题目第一反应是“循环从100到200逐个判断”这个方向是对的但“怎么判断一个数是不是素数”往往有人凭感觉写写出来的逻辑是错的。先回到数学定义素数质数是大于1的自然数中除了1和它本身以外不再有其他因数的数。按这个定义2是素数3是素数4不是因为2×249不是因为3×3911是。注意两个边界1不是素数0和负数更不用讨论。所以判断一个数是不是素数首先得排除这些边界情况。判断的朴素思想叫试除法拿这个数n去除以从2开始一直到n-1的所有整数如果中间有任何一次能整除说明它有别的因数那就不是素数如果全都除不尽那它就是素数。这个逻辑是铁律问题只在于怎么写得高效、怎么写得不犯错。2.1 试除法的基本写法与隐藏的数学陷阱第一版代码通常长这样逻辑上完全正确#include stdio.h int isPrime(int n) { if (n 2) { return 0; } for (int i 2; i n; i) { if (n % i 0) { return 0; } } return 1; }外层循环从100到200内层调用isPrime逐个判断。这个代码没毛病但它有一个明显的缺点效率低。判断一个数n最多要试除n-2次100个数加起来总共的试除次数约为100×100量级也就是上万次。对一个初学练习来说无所谓但如果你把这个逻辑扩展到判断1到1000000的素数它就会慢得让人难受。这里有一个隐藏的数学陷阱很多人会把循环条件写成for (int i 2; i n; i)然后发现所有数都“不是素数”——因为n % n 0任何数除以它自己都整除当然就返回0了。还有人会把条件写成i n/2这倒是对的因为一个大于n/2的整数不可能是n的因数除了n自己。不过既然是试除法还是有一个更优的边界值得讲。2.2 为什么要开平方sqrt优化背后的数学原理判断一个数是否是素数根本不需要试除到n-1只需要试除到√n根号n就可以了。这个结论很多教材直接给出来但不讲为什么导致初学者将信将疑。道理其实很简单。如果n有一个大于√n的因数a那么一定存在另一个因数b n / a而这个b必然小于√n。换句话说大于根号n的因数一定和某个小于根号n的因数成对出现。所以你只要在[2, √n]范围内找不到任何因数那后半段也不可能有因数因为那部分如果有因数它们的小搭档早就被抓住了。举个例子判断97是不是素数√97约等于9.8。你只需要试2、3、4、5、6、7、8、9也就是最多8次试除。如果都不整除那97肯定是素数。不用一路除到96。这个优化把单个数判断的复杂度从O(n)降到了O(√n)。对100到200这个范围来说提升不算巨大但理解这个优化思路对整个算法学习很有价值。于是第二版代码就变成这样#include stdio.h #include math.h int isPrime(int n) { if (n 2) { return 0; } for (int i 2; i sqrt(n); i) { if (n % i 0) { return 0; } } return 1; }注意这里的循环条件用了因为如果n恰巧是一个完全平方数比如25根号正好是5你漏掉5这个因数就会误判成素数。所以条件是i sqrt(n)不是。这个细节很容易踩坑。2.3 进阶优化跳过偶数循环步长定为2还有一种很实用的优化大于2的偶数统统不是素数所以判断的时候可以先把偶数挡掉然后只试奇数因子。这样循环步长从1变成2试除次数又减半。int isPrime(int n) { if (n 2) { return 0; } if (n 2) { return 1; } if (n % 2 0) { return 0; } for (int i 3; i sqrt(n); i 2) { if (n % i 0) { return 0; } } return 1; }这个版本写起来也不复杂却把效率又翻了一倍。有人还会继续优化成“只试6k±1形式的数”但那是更高级的玩法初阶阶段能把sqrt和步长2这两个优化理解透已经超过大部分同学了。3. 100-200之间的素数练习完整代码的逐步演进前面把理论铺垫得差不多了现在真正动手写整体代码。完整的练习场景是输出100到200之间的所有素数并且把个数统计出来。这是一个非常典型的初阶综合练习它把循环、分支、函数封装、计数变量全部串在一起。我先给出最直观的版本然后一步步演进。第一版完全用主函数裸写不拆函数适合那些还不太习惯函数封装的同学先看逻辑#include stdio.h int main() { int count 0; for (int n 100; n 200; n) { int isPrime 1; if (n 2) { isPrime 0; } for (int i 2; i n; i) { if (n % i 0) { isPrime 0; break; } } if (isPrime) { printf(%d , n); count; } } printf(\ncount %d\n, count); return 0; }这个版本可以运行也能得到正确结果101、103、107、109、113、127、131、137、139、149、151、157、163、167、173、179、181、191、193、197、199一共21个。但代码的缺点很明显判断素数的逻辑和大循环耦合在一起读起来费力以后想复用这个判断逻辑还得重写。3.1 用函数封装素数判断体现函数参数这节课的价值既然这节课的主题是函数参数正确的做法自然是用函数把“判断素数”抽出来。封装之后主函数只负责循环和打印逻辑一目了然#include stdio.h #include math.h int isPrime(int n) { if (n 2) { return 0; } if (n 2) { return 1; } if (n % 2 0) { return 0; } int limit (int)sqrt(n); for (int i 3; i limit; i 2) { if (n % i 0) { return 0; } } return 1; } int main() { int count 0; for (int n 100; n 200; n) { if (isPrime(n)) { printf(%d , n); count; } } printf(\n共%d个素数\n, count); return 0; }我特别推荐把这个版本当作模板。它把“是什么”和“怎么做”分开了isPrime负责回答“一个数是不是素数”main负责“从100遍历到200并输出结果”。这就是函数封装带来的模块化思维。这里有一个值得注意的细节在isPrime里我把sqrt(n)的结果先存进limit变量而不是直接在循环条件里调用sqrt(n)。这是因为sqrt是浮点运算如果每次都调用一方面效率略低另一方面浮点比较还涉及精度问题。先转成整数存起来循环条件就变成整数比较既快又稳。这个写法是我在实际教学中反复强调的。3.2 程序输出验证跑一遍看看结果对不对写完代码不是终点验证结果才是关键。编译运行这个程序预期的输出也就是这21个素数101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199 共21个素数这时候我建议你做一件事人工抽查几个数。比如103你会不会认为它是素数试着用暴力思维验证103开根约为10测试2、3、5、7都不用试到10只要你确认2、3、5、7都不能整除它那它就是素数。再比如121很多人会直觉以为是素数但它是11×11不是素数。你的程序能不能抓住121能因为循环条件i sqrt(121)也就是i 11试到11的时候121 % 11 0直接返回0。如果之前把条件误写成i sqrt(n)那这里就是漏网之鱼。这就是我前面强调的重要性。通过这种抽查你不光验证了输出数量还验证了程序边界的正确性。程序员的习惯不是“跑起来就万事大吉”而是跑起来之后主动去测几个边界用例。3.3 从写死范围到命令行参数一次很自然的升级代码写好了但有一个问题这个程序只能算100到200之间的素数。如果下次我想算1到50的素数呢改代码重新编译能行但不够优雅。既然前面讲了main函数的参数这里正好用上——把上下限变成命令行参数。#include stdio.h #include stdlib.h #include math.h int isPrime(int n) { if (n 2) { return 0; } if (n 2) { return 1; } if (n % 2 0) { return 0; } int limit (int)sqrt(n); for (int i 3; i limit; i 2) { if (n % i 0) { return 0; } } return 1; } int main(int argc, char *argv[]) { int start, end; if (argc 3) { start atoi(argv[1]); end atoi(argv[2]); } else { printf(用法: %s 起始值 结束值\n, argv[0]); printf(示例: %s 100 200\n, argv[0]); return 1; } if (start end) { printf(起始值必须小于等于结束值\n); return 1; } int count 0; for (int n start; n end; n) { if (isPrime(n)) { printf(%d , n); count; } } printf(\n共%d个素数\n, count); return 0; }这样从终端里执行./prime 100 200和之前的效果一样执行./prime 1 50就能得到1到50之间的素数。程序的通用性一下子就上来了。这段代码里用到的atoi正是前面提到的字符串转整数函数它包含在stdlib.h头文件里。4. 从函数参数到素数判断两者是怎么串起来的一盘棋有些同学学函数参数是一节课学素数是另一节课两节课完全割裂考试的时候分开考还行一结合就懵。这个“100-200素数练习”其实特别适合把两件事打通因为素数判断天然需要函数封装而函数封装天然涉及参数传递。你不可能把判断逻辑裸写在main里还叫“函数参数练习”那这个练习就没有意义了。4.1 为什么判断素数必须写成函数模块化与可维护性从软件工程的角度看判断素数是一个高度独立的逻辑单元给它一个整数它返回一个“真假”结果。这种独立单元最适合抽成函数。抽出来之后主函数变薄了逻辑变清晰了后续维护也容易了。设想一下如果判断逻辑写成isPrime函数你想把试除法换成筛选法、想优化步长、想加上记忆化只需要改isPrime内部main完全不用动。但如果你把判断逻辑写在主循环里改起来就得在一大段代码里找改完还容易误伤其他逻辑。这就是模块化的价值。另外C语言里模块化还有一个好处测试方便。你可以单独写一个测试函数专门验证isPrime的边界行为。比如传0、传1、传2、传4、传9、传25、传97看返回是否符合预期。把函数测清楚了主循环的可靠性就有保障。这也是真实项目里的常见做法单元测试先行再拼装大逻辑。4.2 传值如何保证主循环不受污染isPrime(int n)用的是值传递。这意味着传入的n在函数内部怎么折腾都不影响main里循环变量n的值。这一点极其重要。我们写主函数时用的是for (int n 100; n 200; n)也就是说n是循环控制变量。假设你不把素数判断写成函数而是在main里直接写判断逻辑你非常容易不小心修改了n导致循环失控。但是用函数传值之后isPrime里用的n只是main里n的一份拷贝怎么改都不会影响循环本身。这就是值传递带来的隔离性。不过这里有个陷阱要提醒如果你在isPrime里写了n n / 2;之类的操作虽然不影响main但会影响函数自己的判断逻辑导致返回错误结果。函数内部对形参的修改不会影响外部但会影响函数自身的行为所以形参也不是可以随意乱改的。比较好的习惯是清晰的函数内部形参只读引入新的局部变量去存中间结果。4.3 设计返回值时要想清楚0和1到底代表什么isPrime返回0或1这是一个很朴素的设计但命名上要跟语义对齐。有人喜欢写成return 1;表示“是素数”return 0;表示“不是素数”这没问题。但关键要让调用者一眼看懂。我在实践中更推荐用bool类型需要包含stdbool.h代码变成#include stdbool.h bool isPrime(int n) { if (n 2) return false; if (n 2) return true; if (n % 2 0) return false; int limit (int)sqrt(n); for (int i 3; i limit; i 2) { if (n % i 0) { return false; } } return true; }这样在主函数里写if (isPrime(n))语义非常自然不需要费劲思考“返回1是啥意思”。这个建议适用于所有“结果只有真/假”的函数。很多真实项目也这么做因为它让代码自我说明。4.4 素数判断的另一种思路筛选法其实也在用函数参数如果你想扩展视野素数练习还有另一个经典方案——埃拉托斯特尼筛选法。它一次性生成一个范围内所有素数的“名单”不是逐个判断而是“筛掉”合数。基本步骤是先假设2到最大值所有数都是素数然后从2开始把2的倍数标记为合数接着找下一个未标记的数是素数再筛掉它的倍数反复操作。筛选法写出来通常需要两个参数上限max和存放结果的数组。这里又会用到函数和数组参数的概念void sieve(int max, int primes[], int *count) { int isPrime[max 1]; for (int i 0; i max; i) { isPrime[i] 1; } isPrime[0] isPrime[1] 0; for (int i 2; i * i max; i) { if (isPrime[i]) { for (int j i * i; j max; j i) { isPrime[j] 0; } } } *count 0; for (int i 2; i max; i) { if (isPrime[i]) { primes[(*count)] i; } } }这个函数的设计妙处在于它通过int primes[]数组参数和int *count指针参数一次性“带出”了所有结果。如果只靠一个return最多返回一个数字而筛选法要返回一堆素数就必须靠指针参数。所以我说函数参数这节课学透了你看筛选法代码就像看一个老朋友。5. 常见问题与调试实录初学者在这道题上踩过的坑这道题作为C语言初阶的经典练习学生踩过的坑我都见过无数遍。大部分错误不是不会写而是边界条件没想清楚。我把最常见的问题整理成一张速查表再逐个讲清原因和解决方案。现象原因解决方案4被输出为素数循环条件写成i n且初始值从2起但漏掉了4 % 2的判断逻辑确保循环包含2且条件为i sqrt(n)所有数都不是素数循环条件写成i n每次都能整除自己条件改为i n或i sqrt(n)121被误判为素数循环条件写成i sqrt(n)漏掉了因数11必须写成i sqrt(n)等号不能丢25被误判为素数同上漏掉了因数5同上1被输出为素数没有处理n 2的边界函数开头加if (n 2) return 00和负数被误判没有处理小于2的输入同上程序运行结果为空外层循环范围写错如for (n100; n200; n)漏了200确认边界用代码编译报错sqrt未定义忘了包含math.h或者没链接数学库加头文件并在gcc后用-lm链接5.1 编译时遇到的经典报错sqrt未定义、隐式声明在Linux或Mac环境用gcc编译时只要用了sqrt就非常容易出现这样的报错warning: implicit declaration of function sqrt [-Wimplicit-function-declaration]或者undefined reference to sqrt第一个警告的原因是没有包含math.h头文件。有人觉得“数学函数不是自带吗”其实C语言里每个库函数都必须先声明才能用sqrt的声明就在math.h里。第二个报错则是链接问题数学函数在单独的libm库里gcc默认不会自动链接需要手动加-lm。完整的编译命令是gcc prime.c -o prime -lm很多人知道要写#include math.h却忘了-lm编译出来报错就懵了。这个坑在Windows的IDE里可能不明显IDE往往自动链接了但在命令行编译环境下几乎人人都会遇到一次。提前记住这两个要点能省不少时间。5.2 逻辑正确但结果不对优先检查边界值和循环条件程序能编译但输出的素数不对这种问题靠瞪眼看代码往往很难发现最好的办法是缩小范围测试。比如你算100到200不对那你先算2到10对不对2、3、5、7这4个应该是素数4、6、8、9、10这5个不应该是素数。用这个极小的测试集去跑isPrime函数很快就能定位问题出在哪儿。如果2到10都对那问题可能出在外层循环如果2到10都不对那问题基本就在isPrime内部。这种“分而治之”的调试思路是实际开发里的基本功。另外一个实用技巧在isPrime函数里临时加一个printf打印传入的n和每次试除的i观察执行过程。比如printf(n%d, i%d, n%%i%d\n, n, i, n % i);跑一遍你就会清楚看到121被判别为合数的完整过程i走到11取模为0立即退出。调试完记得把printf删掉不然输出会被刷屏。5.3 那些教材没告诉你的优化细节取模的代价比你想的大%运算取模在C语言里不是免费午餐它在汇编层面通常对应一次除法指令而除法是CPU里最慢的整数运算之一比加法、乘法慢一个数量级。在100到200这种小范围下无所谓但如果将来你要处理大范围素数判断%运算的代价就需要考虑了。一个经典优化是用“查表法”代替部分取模判断一个数能否被3整除不需要取模看各位数字之和是否整除3判断被5整除看最后一位是不是0或5。这类技巧都属于数论里的整除性判则用在某些场景能省不少算力。但初阶阶段不必过度设计先用最清晰的取模写法等真遇到性能瓶颈再优化。过早优化是万恶之源这句话在C语言初学者身上尤其适用。还有一个小细节sqrt(n)返回的是double如果你把它赋给int limit会发生隐式类型转换。对于完全平方数比如121sqrt(121)在某些编译环境下可能用浮点数算出10.999999转成int就成了10这样循环就会漏掉11这个因数。虽然实际中sqrt(121)往往精确等于11但用浮点数结果控制整数循环条件本身就是一种风险。更稳的写法是for (int i 2; i * i n; i) { // 用 i*i 代替 sqrt(n) }i * i n避免了浮点精度问题也不依赖math.h代码更简洁。唯一的隐患是i * i在n非常大时会溢出但对初阶练习的范围来说完全够用。这是我在实际写代码时最推荐的方式。5.4 对输出格式的强迫症空格、换行与计数的设计输出格式也是这道练习里常被忽略的考点。如果直接写printf(%d , n);每个素数后面都有一个空格输出的末尾也会有一个多余的空格看起来“脏”。吹毛求疵一点的话可以用一个标志变量来控制格式int first 1; for (int n 100; n 200; n) { if (isPrime(n)) { if (!first) { printf( ); } printf(%d, n); first 0; } } printf(\n);这样输出的就是101 103 107 ... 199末尾没有多余空格干净利落。这个细节虽然不影响正确性但好的输出格式是程序质量的一部分。真实项目里日志、报文、CSV导出等都对格式有严格要求从小练习养成好习惯后面受益。计数方面也没什么玄机就是在循环里每找到一个素数就count。但要注意count必须定义在循环外面初始化为0。有人手滑写在循环里面每次循环都重置结果永远只输出1。这个低级错误看似可笑实际发生频率不低。5.5 调试实录一次真实的121误判排查过程我拿一个真实的调试过程做例子。一个学生写了如下版本的isPrimeint isPrime(int n) { for (int i 2; i n; i) { if (n % i 0) { return 0; } } return 1; }运行100到200的程序发现121被输出为素数他觉得奇怪121明明能除以11为什么程序看不出来其实原因很简单121除以2到10都不能整除循环走到i11时条件判断11 121成立但是121 % 11 0应该返回0才对怎么会是素数后来一检查他写的循环条件根本不是i n而是i n/2——121除以2到10不能整除121的一半是60所以循环在i11时就会被截断不对i 60到60才停11在范围内应该能抓到。再继续查真正的问题在于他把sqrt(n)写错了地方用了i sqrt(n)sqrt(121)是11i 11意味着i最大只能到10永远碰不到11这个因数。这就是经典的 “漏掉等于号” 问题。看到这里你应该明白了这类错误的根源几乎都指向循环边界少了一个等号。解决的核心方法也简单测试边界用例121、25、97一把就能抓住问题。6. 从这道练习延伸出去接下来还能做什么完成了100到200素数的输出函数参数和基本循环这两个核心点就算过关了。但练习的真正价值在于“延展”。我建议你可以按下面的方向继续深化每个方向都只需要在这道题的基础上改一小步统计素数的间隔输出素数的同时计算相邻素数之间的差值看看最大间隔是多少、出现在哪两个素数之间。这需要用到“记住上一个素数”的技巧还是函数和变量的应用。判断用户输入的数把程序改成交互式用户输入一个数程序立刻告诉它是不是素数。这个方向用到scanf也是C语言很常见的学习路径。扩展到其他范围把100-200改成任意范围用main参数实现前面已经演示过了。用筛选法重写先写试除法版本再写筛选法版本对比两个版本的输出和效率。筛选法会涉及数组作为函数参数的知识那是函数参数学习的下一站。探索更大的边界比如判断1到100000之间的素数有多少个。你可能会感受到试除法和筛选法的性能差距这也是一种对算法复杂度最直观的感知。我自己带过很多人学C语言有一个体会非常深初阶阶段的练习重点不是“会做”而是“会想”。函数参数这节课学的不是语法而是一种思维——数据从哪里进、逻辑怎么独立、结果怎么出来。素数这道题刚好把这几个问题全部覆盖了。6.1 把函数参数的值传递理解透再看递归和指针就会轻松很多C语言里“函数参数”这个知识点的后续影响面非常大。你现在理解了“值传递是复制”后面学递归时就不会问“为什么递归每次调用的参数是新的”你现在理解了“想改外部变量就传地址”后面学链表、二叉树时那些Node **head的写法就不会那么吓人。函数参数可以说是C语言的一座桥桥这边是变量、printf、分支循环桥那边是指针、数组、结构体、动态内存。有很多人卡在指针上回过头来问“为什么函数参数课里没讲清楚地址传递”其实那节课讲了只是当时没意识到“地址也是值”——指针变量本身也是一个值只不过它存的不是普通数据而是内存地址。理解了这一点指针的神秘感就消了一半。6.2 对初学者的几条建议慢慢写反复验证最后给初学的朋友三条实操建议。第一每个函数先单独测试再组装。不要一上来就写完整的100到200程序先把isPrime函数写好用一个简单的main测它输入几个已知的数看输出对不对确认函数没问题后再写外层循环。这样定位问题会快得多。第二打印中间结果。不确定循环边界的时候就临时加打印语句把n、i、n%i都打出来。虽然看起来原始但它比任何调试器都直观尤其对初学者来说能看见程序在一步步做什么比猜测强一百倍。第三用纸笔演算一遍小数据。比如手工算一下2到20的素数流程然后对照程序的输出。这个习惯看起来笨但它能帮你把“程序的逻辑”和“问题本身的逻辑”严格对应起来。我自己当年学C语言时就是用这个笨办法一步一步跟过来之后很多概念反而不觉得难了。函数参数这道坎跨过去素数的练习也完成之后你在C语言初阶阶段就打下了很扎实的底子。后面无论走算法方向、嵌入式方向还是上层应用方向这些基本思维都绕不开。希望这篇记录能帮你在同样的路上少走几个弯路。