ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Dev-C++复制即用小游戏:猜数字、贪吃蛇、井字棋与爱心动画

Dev-C++复制即用小游戏:猜数字、贪吃蛇、井字棋与爱心动画 很多人第一次在 Dev-C 里跑小游戏卡住的点其实不是写不出代码而是从网上复制过来的小游戏代码往编辑器里一贴F11 一按满屏红字。更让人抓狂的是同样的代码别人机器上跑得好好的。我前后帮不少刚学 C 的朋友看过这类问题十次里有八次跟代码逻辑无关全是环境设置和编码的事。所以这篇不讲空泛的语法直接把几段在 Dev-C 里复制粘贴就能运行的小游戏代码放出来同时把每段代码背后涉及的 C 知识点、以及为什么这段能跑、那段跑不起来讲清楚。内容包括控制台猜数字、贪吃蛇、井字棋人机对战外加一个会跳动的爱心动画从环境对齐讲到数据结构和算法适合刚接触 C 编程入门的朋友也适合已经会写几行代码但一直没做出东西的读者。1. 复制代码之前先把 Dev-C 的这四处设置对齐我见过太多代码没问题但还是报错的情况本质上都是编译器和写入环境的问题。这一节先把地基打平后面所有代码才能保证你复制进去就是能跑的。1.1 编译器标准为什么网上抄来的代码在你机器上报一堆错Dev-C 的版本差异比大部分人想象的大。老一点的 Orwell Dev-C 5.11 内置的是 TDM-GCC 4.9.2而小熊猫 Dev-C 这类后续版本带的 GCC 要新一些。关键不在 GCC 本身而在于默认启用的 C 标准。GCC 4.9.2 在不加参数的情况下默认按 gnu98 来编译这意味着auto自动类型推断、范围 for 循环、nullptr、to_string这些 C11 才有的东西一律会报未声明或者auto changes meaning in C11。解决办法很直接打开工具 → 编译选项 → 编译器找到编译时加入以下命令这一栏把勾打上在里面填-stdc11 -Wall-stdc11告诉编译器按 C11 标准来-Wall是打开常用警告它会帮你提前发现变量没初始化比较时把赋值写成了等号这类问题。如果你想用更新的特性也可以写-stdc14或者-stdc17但 GCC 4.9.2 对 C17 的支持不完整不建议在这套环境里折腾太新的语法。这个设置是针对当前编译配置保存的改完点确定就行之后新建的源文件都会带着它。1.2 控制台中文乱码不是代码写错了是编码对不上Dev-C 在中文 Windows 上默认把源文件保存成 ANSI也就是本地代码页 936GBK。你从网页上复制代码的时候如果编辑器当前是 UTF-8 模式代码里的中文字符串就会以 UTF-8 字节写进文件而编译出来的程序输出的字节流又被控制台按 GBK 去解码结果就是一堆问号或者奇怪的方块。处理方式有两条路我更推荐第一条把源文件保存为 ANSI。在 Dev-C 里用另存为编码选 ANSI/GBK中文字符串就正常了。第二条路是保持 UTF-8 保存然后在main函数最开头加一行#include windows.h // ... SetConsoleOutputCP(65001);这行会把控制台输出代码页切到 UTF-8。注意它必须在你第一次输出中文之前执行放到main的第一行最稳。两条路选一条别混着用混用反而更容易乱。提示如果你在代码里只写英文提示就不存在这个问题。做练习阶段用英文输出其实是省事的选择等要交作业或者给同学看的时候再换中文。1.3 黑窗口一闪而过三种收尾方式的取舍小游戏跑完立刻关窗是新手最常见的抱怨。原因是main返回后程序结束控制台窗口随之销毁你根本没机会看结果。三种收尾方式各有代价system(pause)需要#include cstdlib会打印请按任意键继续. . .好处是绝对不会一闪而过。缺点是它会启动一个子系统进程在某些在线判题环境里是被禁用的。cin.get()看起来最标准但它读的是输入缓冲区里的第一个字符。如果你前面用过cin x缓冲区里还留着那个回车换行符cin.get()会立刻返回等于没停。cin.ignore()加cin.get()组合用法先把残留的换行符吃掉再等一个按键行为最可控。我自己写练习代码一般用system(pause)简单直接如果是打算提交到某个评测系统的代码一律换成cin.get()那套避免被判无效。1.4 复制代码后报错自查表下面这张表基本覆盖了我见过的九成新手报错遇到红字先对照着看别急着改代码逻辑。报错信息真实原因处理方式to_string was not declared标准太老默认按 C98 编译加-stdc11auto changes meaning in C11同上加-stdc11conio.h: No such file or directory不在 Windows 平台或者编译器不完整Windows 下检查是否装了完整 MinGW非 Windows 平台要换输入方案Sleep was not declared缺头文件或者写成了小写sleep加#include windows.h首字母必须大写参数单位是毫秒undefined reference to system缺头文件加#include cstdlib输出的中文是乱码源文件编码与控制台代码页不一致按 1.2 的做法统一编译通过但变量值明显不对局部变量没初始化打开-Wall看警告给变量显式赋初值2. 猜数字三十行代码里藏着随机数、输入校验和二分思路猜数字这个题目听着像幼儿编程但真把它写得没毛病得同时处理随机数种子、输入类型校验、缓冲区清理三件事。很多教程给的版本只能输入数字你随手敲个字母进去程序就进入死循环了。2.1 完整代码可直接复制#include iostream #include cstdlib #include ctime #include limits using namespace std; int main() { srand((unsigned)time(NULL)); // 只做一次别放进循环 const int LOW 1, HIGH 100; int answer rand() % (HIGH - LOW 1) LOW; int guess, tries 0; cout 我想了一个 LOW 到 HIGH 之间的整数你猜猜看。 endl; while (true) { cout 请输入你的猜测; if (!(cin guess)) { // 输入的不是整数 cout 这不是一个整数请重新输入。 endl; cin.clear(); // 清掉错误状态位 cin.ignore(numeric_limitsstreamsize::max(), \n); continue; } tries; if (guess answer) { cout 小了。 endl; } else if (guess answer) { cout 大了。 endl; } else { cout 猜对了你一共用了 tries 次。 endl; break; } } cout 按回车键退出...; cin.ignore(numeric_limitsstreamsize::max(), \n); cin.get(); return 0; }代码不长但每一行都有理由。const int LOW/HIGH用常量而不是直接写 1 和 100是为了后面改范围的时候只改一处rand() % (HIGH - LOW 1) LOW这个写法是把rand()的输出映射到闭区间[LOW, HIGH] 1少了会漏掉上界这个坑我见过好几次。2.2 rand() 每次生成同一个数的真正原因rand()是伪随机数它其实是在一个固定序列上往后取值。种子不设默认种子是 1所以每次运行拿到的随机数完全一样。解决办法是srand()播种最常用的种子是当前时间戳time(NULL)。两个细节必须注意。第一srand()整个程序只调用一次写在main开头。如果你把它塞进 while 循环里每次循环都重新播种而循环执行得极快time(NULL)返回的秒数根本没变于是你每次拿到的还是同一个数反而更像坏掉了。第二Windows 下 MinGW 的RAND_MAX是 32767rand() % 100在理论上存在极其轻微的分布不均做练习完全够用如果你要写抽奖类程序就该用 C11 的random里的mt19937加uniform_int_distribution那个才是均匀分布。2.3 输入一个字母就死循环cin 的状态位和缓冲区这是新手代码里最经典的死循环来源。cin guess发现输入的是一串字母无法转成int它会把流的failbit置位然后把这个字符留在缓冲区里不动。接下来每一次循环cin guess又看到同样的字母又失败又置位于是屏幕上疯狂刷请输入你的猜测程序再也读不进新东西。修法就是代码里的三件套cin.clear()把状态位归零cin.ignore(numeric_limitsstreamsize::max(), \n)把当前这一整行的垃圾数据全部丢弃直到遇到换行符为止然后continue回到循环开头重新读。这里numeric_limitsstreamsize::max()需要#include limits它表示一个足够大的数字比随手写cin.ignore(1000, \n)更稳妥——如果用户手抖按住了键盘缓冲区里可能真有上千个字符。这是我从实际调试里学到的经验随手写个 1000 早晚会翻车。2.4 用二分查找把猜测次数压到 7 次以内猜数字的最优策略就是二分查找。1 到 100 的范围每次猜中点最坏情况只需要 7 次log2(100) 约等于 6.64向上取整就是 7。你可以把代码改造成电脑猜你想的数让读者心里想一个数程序来猜这样能把二分查找的边界处理讲透int low 1, high 100, mid; char reply; while (low high) { mid low (high - low) / 2; // 这样写不会溢出 cout 你想的数是 mid 吗(大/小/对); cin reply; if (reply 对) break; if (reply 大) high mid - 1; else low mid 1; }mid low (high - low) / 2这种写法看着绕但它和(low high) / 2的区别在于不会整数溢出。在 1 到 100 这种小范围里两者没差别但把这个思路搬到数组二分查找上low high超过int上限就是真实存在的 bug。顺带说一句循环条件是low high而不是low high是因为区间是闭区间[low, high]当两者相等时那个位置还没被检查不能提前退出。这个边界问题我在写二分查找的时候错了好多次最后总结成一句话先把区间定义清楚是闭区间还是左闭右开循环条件和更新方式自然就推出来了。3. 贪吃蛇数组、结构体与键盘即时响应怎么拼到一起贪吃蛇是控制台小游戏里性价比最高的一个代码量不大但把数组、结构体、键盘输入、屏幕刷新全用上了。写完这一份你对游戏循环这个概念会有实感。3.1 蛇身数据到底该用什么存蛇身本质上是一个有序的点序列每次移动是头部新增一个点、尾部去掉一个点。可以选的数据结构有几种两个平行数组int sx[200], sy[200]最省事但对新手不友好容易把 x 和 y 弄混一旦越界两个数组的错位很难查。结构体数组Point snake[200]把坐标打包成一个整体整体赋值snake[i] snake[i-1]一行搞定可读性最好我推荐这个。链表理论上头插尾删是 O(1)但每个节点要new和delete对初学者来说内存管理的坑比收益大得多。std::dequepush_front加pop_back语义最贴合可惜初学者往往还没学到 STL。我在下面的代码里用结构体数组长度上限设 200够一局游戏用了。之所以不动态扩容是因为贪吃蛇的长度上限是可以预判的格子总数固定数组既快又不会漏内存。3.2 完整代码可直接复制运行#include iostream #include conio.h #include windows.h #include cstdlib #include ctime using namespace std; const int W 24; // 场地宽度 const int H 18; // 场地高度 const int MAXLEN 200; struct Point { int x, y; }; int main() { srand((unsigned)time(NULL)); Point snake[MAXLEN]; int len 3; for (int i 0; i len; i) { snake[i].x 6 - i; snake[i].y 8; } int dir 3; // 0 上 1 下 2 左 3 右 int fx, fy; bool bad; do { fx rand() % W; fy rand() % H; bad false; for (int i 0; i len; i) if (snake[i].x fx snake[i].y fy) bad true; } while (bad); int score 0; bool dead false; while (!dead) { // 1. 取出所有排队的按键 while (_kbhit()) { int c _getch(); if (c 0 || c 224) { // 方向键是两字节 c _getch(); if (c 72 dir ! 1) dir 0; else if (c 80 dir ! 0) dir 1; else if (c 75 dir ! 3) dir 2; else if (c 77 dir ! 2) dir 3; } else { if ((c w || c W) dir ! 1) dir 0; else if ((c s || c S) dir ! 0) dir 1; else if ((c a || c A) dir ! 3) dir 2; else if ((c d || c D) dir ! 2) dir 3; else if (c q || c Q) dead true; } } if (dead) break; // 2. 算出新头部坐标 int nx snake[0].x, ny snake[0].y; if (dir 0) --ny; else if (dir 1) ny; else if (dir 2) --nx; else nx; // 3. 撞墙判定 if (nx 0 || nx W || ny 0 || ny H) { dead true; break; } // 4. 撞自己判定尾巴那格马上会空出来不算 for (int i 0; i len - 1; i) if (snake[i].x nx snake[i].y ny) { dead true; break; } if (dead) break; // 5. 吃到食物就变长 bool eat (nx fx ny fy); if (eat) { if (len MAXLEN) len; score; } // 6. 整体后移插入新头 for (int i len - 1; i 0; --i) snake[i] snake[i - 1]; snake[0].x nx; snake[0].y ny; // 7. 重新放食物 if (eat) { do { fx rand() % W; fy rand() % H; bad false; for (int i 0; i len; i) if (snake[i].x fx snake[i].y fy) bad true; } while (bad); } // 8. 绘制 system(cls); for (int i 0; i W 2; i) cout #; cout \n; for (int y 0; y H; y) { cout #; for (int x 0; x W; x) { bool drawn false; for (int i 0; i len; i) { if (snake[i].x x snake[i].y y) { cout (i 0 ? O : o); drawn true; break; } } if (!drawn) { if (fx x fy y) cout *; else cout ; } } cout #\n; } for (int i 0; i W 2; i) cout #; cout \n得分 score 按 W A S D 或方向键移动Q 退出\n; Sleep(150); } cout \n游戏结束最终得分 score endl; system(pause); return 0; }整个结构就是一个标准的游戏循环读输入 → 更新状态 → 判定成败 → 重绘 → 等待一帧。把Sleep(150)改小蛇就变快改成 250 就变慢想加随分数提速也很简单把 150 换成一个随score递减的变量即可但记得给它设个下限比如最小 60 毫秒否则蛇会快到你根本反应不过来。3.3 _kbhit 和 _getch 为什么能让游戏不等回车普通的cin 是行缓冲的你必须敲回车程序才能拿到数据。而_kbhit()是直接问操作系统键盘缓冲区里有没有待处理的按键有就返回真值没有立刻返回假绝不阻塞_getch()则是从缓冲区取走一个字符并且不回显到屏幕上。这两个函数来自conio.h它是 Windows 控制台专有的不属于 C 标准库。所以你会在各种这份代码只能在 Windows 跑的说明里看到它。如果你换到别的平台上写对应的思路是用终端的原始模式raw mode读取但那套东西就不能直接复制粘贴了。还有一个特别容易被忽略的细节方向键会返回两个字节。第一次_getch()拿到的是 0 或者 224也就是所谓的前导字节必须再调一次_getch()拿到的才是 72上、80下、75左、77右。如果你只取一次方向键会失效而且更糟的是那个残留的字节会在队列里堆积导致按键越来越迟钝。3.4 三个必崩点和对应的修法第一个必崩点是反向掉头。蛇正在向右走你按左键新头部坐标正好落在原来脖子那一格判定成撞自己。所以代码里每个方向判断都带了dir ! 反向的条件这个不是可选项是必须的。第二个是食物刷在蛇身上。rand() % W和rand() % H是完全随机的可能正好落在蛇的身体中间那样食物一出现就被判定吃到或者干脆永远看不见。代码里用了do...while(bad)循环重新随机直到落点不在蛇身上为止。这个循环在蛇特别长的时候可能多转几次但只要还有空格子它就一定会终止。第三个是数组越界。蛇的长度上限是MAXLEN吃到食物时len必须先判断len MAXLEN。如果不判断蛇长到 200 以上就会写到数组外面去程序可能立刻崩溃也可能悄悄改掉别的变量出现无缘无故分数变成乱码这种灵异现象。顺带说一个体验问题system(cls)是整屏清空重画屏幕会明显闪烁。想解决的话可以改用 Windows API 的SetConsoleCursorPosition把光标移回左上角再覆盖输出闪烁会小很多。代价是代码要多写几行而且得配合上一次画的内容做差异比较。做练习阶段闪烁可以忍。4. 井字棋人机对战让电脑会下棋的最小化极大搜索猜数字和贪吃蛇都是靠人脑判断井字棋这个不一样我们让电脑真的算一下。这段代码是理解递归和博弈搜索最好的入门材料而且因为棋盘只有 9 格暴力算到底也花不了几毫秒。4.1 棋盘用一维数组更省事棋盘看起来是二维的但用一维数组char b[9]更能简化代码。落子位置 1 到 9 直接对应下标 0 到 8判定胜负只需要 8 条线三行、三列、两条对角线。写成常量表const int LINES[8][3] { {0,1,2}, {3,4,5}, {6,7,8}, {0,3,6}, {1,4,7}, {2,5,8}, {0,4,8}, {2,4,6} };用表驱动的方式判定胜负以后要改成四子棋也只需要改这张表比写八个if干净得多。这是我强烈推荐的一个习惯凡是固定的一组位置组合就用常量表加循环不要手写重复的判定。4.2 完整代码可直接复制#include iostream using namespace std; const int LINES[8][3] { {0,1,2}, {3,4,5}, {6,7,8}, {0,3,6}, {1,4,7}, {2,5,8}, {0,4,8}, {2,4,6} }; char winnerOf(const char b[9]) { for (int i 0; i 8; i) { int x LINES[i][0], y LINES[i][1], z LINES[i][2]; if (b[x] ! b[x] b[y] b[y] b[z]) return b[x]; } return ; } bool isFull(const char b[9]) { for (int i 0; i 9; i) if (b[i] ) return false; return true; } // me 是我的棋子turn 是当前该谁走 int minimax(char b[9], char me, char turn, int depth) { char w winnerOf(b); if (w me) return 10 - depth; // 越早赢分越高 if (w ! ) return depth - 10; // 越晚输分越高 if (isFull(b)) return 0; int best (turn me) ? -1000 : 1000; for (int i 0; i 9; i) { if (b[i] ! ) continue; b[i] turn; int v minimax(b, me, (turn X) ? O : X, depth 1); b[i] ; if (turn me) { if (v best) best v; } else { if (v best) best v; } } return best; } int bestMove(char b[9], char me) { int best -1000, pos -1; for (int i 0; i 9; i) { if (b[i] ! ) continue; b[i] me; int v minimax(b, me, (me X) ? O : X, 1); b[i] ; if (v best) { best v; pos i; } } return pos; } void show(const char b[9]) { cout \n; for (int i 0; i 9; i 3) { for (int j 0; j 3; j) { char c (b[i j] ) ? (char)(1 i j) : b[i j]; cout c ; if (j 2) cout |; } cout \n; if (i 6) cout ---------\n; } cout \n; } int main() { char b[9]; for (int i 0; i 9; i) b[i] ; cout 你是 X电脑是 O。输入 1-9 落子输入 0 认输。\n; while (true) { show(b); char w winnerOf(b); if (w ! ) { cout w 获胜。\n; break; } if (isFull(b)) { cout 平局。\n; break; } cout 你的落子; int pos; if (!(cin pos)) { cin.clear(); cin.ignore(1000, \n); continue; } if (pos 0) { cout 你认输了。\n; break; } if (pos 1 || pos 9 || b[pos - 1] ! ) { cout 这个格子不能下换一个。\n; continue; } b[pos - 1] X; char w2 winnerOf(b); if (w2 ! || isFull(b)) continue; int ai bestMove(b, O); if (ai 0) { b[ai] O; cout 电脑落在 (ai 1) 号位。\n; } } return 0; }4.3 为什么这个算法在 3×3 上够用在五子棋上就不行minimax的核心思想是假设双方都走出最优的一步那么从当前局面出发把所有可能的走法一直推演到终局谁的分高就选谁。轮到我走的时候取最大值轮到对手走的时候取最小值所以叫最小化—极大化。它的搜索节点数大致是 9 的阶乘量级也就是三十多万个叶子节点。对现在的电脑来说这个规模连零点一秒都用不到。代码里加了个depth参数赢棋时返回10 - depth意思是能三步赢就别拖到五步赢不加这个的话电脑虽然不会输但会出现明明能赢却一直拖着不落子的怪现象。这套暴力搜索一旦搬到五子棋上就彻底失效了。15×15 的棋盘光是候选点就有两百多个往下搜三四层就是几百亿个分支普通电脑根本算不完。这时候就需要靠启发式评估函数加剪枝alpha-beta 剪枝、置换表或者干脆上蒙特卡洛树搜索。所以井字棋是理解搜索的起点但千万别以为把棋盘换成 15×15 就自动变成五子棋 AI。5. 让人眼前一亮控制台动画与爱心代码的实现要点写完游戏很多人还想做点看着酷的东西。那段流传很广的 C 爱心代码其实每一行都在做一件很简单的事对屏幕上的每个点判断要不要打星号背后的分界线是一个数学不等式。5.1 那个爱心公式到底在算什么爱心的边界来自这个方程(x^2 y^2 - 1)^3 - x^2 * y^3 0它是一条隐函数曲线把平面分成了内外两部分落在曲线内部也就是不等式成立的区域就是爱心形状。代码要做的事就是遍历一个矩形网格对每个点代入公式成立就打印*不成立就打印空格。这个遍历网格、逐点判定、拼出图的套路和早期计算机画分形图是完全一样的思路。5.2 完整代码会跳动的版本#include iostream #include cmath #include windows.h int main() { // 用 system(cls) 清屏需要 cstdlib这里简单起见也加上 for (int frame 0; frame 80; frame) { double s 1.0 0.10 * sin(frame * 0.35); // 缩放系数制造跳动 system(cls); for (double y 1.5 * s; y -1.5 * s; y - 0.1 * s) { for (double x -2.0 * s; x 2.0 * s; x 0.05 * s) { double px x / s, py y / s; double t px * px py * py - 1.0; bool inside (t * t * t - px * px * py * py * py) 0.0; std::cout (inside ? * : ); } std::cout \n; } Sleep(110); } return 0; }需要补一句上面这份没有显式#include cstdlibsystem(cls)在多数 Dev-C 环境里能编过但严格来说是依赖间接包含。想写得规矩一点把cstdlib加上然后才有system(pause)可以配合使用。5.3 控制台字符宽高比带来的变形问题爱心代码复制过去跑最常见的抱怨是我的爱心怎么又瘦又长。原因在字符的形状控制台里一个字符的高度大约是宽度的两倍所以按等步长遍历 x 和 y画出来的圆会变成竖长的椭圆爱心自然也被拉长。解决办法是让纵向的步长是横向的两倍。你看代码里y - 0.1 * s、x 0.05 * s正好是 2:1这就是为了抵消字符比例。如果你把 x 步长改成 0.1 试试爱心立刻变胖。这个技巧在做任何控制台图形的时候都适用比如用*拼一个圆或者坐标轴不校正比例永远画不圆。还有一个细节是缩放系数s的用法。它必须同时作用在遍历范围和坐标还原上也就是外层按1.5 * s决定画到哪儿内层再用x / s还原成标准坐标代进公式。如果只改范围不改还原会出现图形位置在动但形状没变的怪事。6. 从玩到学把这几段代码拆成 C 基础知识清单小游戏代码最大的价值不是能玩而是你把它拆开之后会发现 C 里那些看起来抽象的语法全都在解决具体问题。这一节把几位老朋友挑出来对号入座。6.1 排行榜里的冒泡排序与冒泡排序的边界贪吃蛇和猜数字都可以加一个历史最好成绩排行榜。数据结构用一个结构体数组排序直接上冒泡struct Score { char name[16]; int point; }; void bubbleSort(Score a[], int n) { for (int i 0; i n - 1; i) { bool swapped false; for (int j 0; j n - 1 - i; j) { if (a[j].point a[j 1].point) { Score t a[j]; a[j] a[j 1]; a[j 1] t; swapped true; } } if (!swapped) break; // 一趟下来没换过说明已经有序 } }三个位置最容易出错。第一内层循环的两个上界写法j n - 1是防止a[j 1]越界j n - 1 - i是利用第 i 趟结束后末尾 i 个元素已经就位来减少比较次数两个都要有。第二swapped这个提前退出优化在数据本身接近有序时能省掉大量无效比较是从 O(n²) 退化到接近 O(n) 的关键。第三结构体整体赋值a[j] a[j1]是可以的编译器帮你拷贝了所有成员但如果结构体里有指针成员这个赋值就是浅拷贝两边会指向同一块内存那就是另一个话题了。6.2 指针和引用swap 函数为什么必须传地址写排行榜的时候你会很自然地想抽一个交换函数出来然后写出这样的代码void swapWrong(int a, int b) { int t a; a b; b t; } // 完全没用它之所以没用是因为 C 默认是按值传递函数里的a、b是外面那两个变量的副本函数一返回副本就销毁了。要真正改到外面的变量必须传地址或者传引用void swapByPointer(int *a, int *b) { int t *a; *a *b; *b t; } void swapByRef(int a, int b) { int t a; a b; b t; }调用的时候前者写swapByPointer(x, y)后者写swapByRef(x, y)。引用的写法调用起来和普通函数一样但内部改的是原变量这是 C 比 C 顺手的地方也是初学阶段最容易踩的认知坑——很多人看到swapByRef(x, y)会以为没传地址就没效果实际上效果是有的。顺带一个必须知道的陷阱数组作为参数传给函数时void f(int a[])和void f(int *a)是完全等价的数组名会退化成指向首元素的指针。结果是函数内部sizeof(a)得到的不是数组总字节数而是指针本身的 4 或 8 字节。所以排序函数必须额外传一个n表示长度别指望在函数里算出数组大小。这个坑我调了很久才彻底记住。6.3 从 Dev-C 换到 VSCode 时最常踩的三个坑第一编译器装哪、路径配错。VSCode 本身不带编译器得自己装 MinGW-w64然后在c_cpp_properties.json里把compilerPath指到bin\g.exe。如果你的includePath手动加了另一个版本 MinGW 的头文件目录就很可能出现代码能编译通过但编辑器一直画红波浪线或者反过来编辑器不报错但一编译就找不到头文件。顺序上IntelliSense 会优先按compilerPath对应的目录去找系统头文件手动加的includePath主要用于你自己的项目头文件。第二标准参数没同步。Dev-C 里你加过-stdc11换到 VSCode 之后这个设置不会自动跟过来。tasks.json的args数组里得手动补上-stdc11或者更细的-stdc17否则同样的代码在 Dev-C 能编、在 VSCode 报错。第三终端输出中文乱码。VSCode 集成终端的默认编码和系统控制台不完全一致最省事的做法是在settings.json里给 Windows 的终端配置加上chcp 65001的命令前缀或者干脆把源文件保存成 UTF-8 并在程序开头切控制台代码页。这两个方案二选一别同时上同时上容易出现部分中文正常、部分乱码这种最难查的状态。最后分享一个我自己用了很久的小习惯每拿到一段别人的小游戏代码先不看逻辑直接在main开头加一行输出当前编译标准比如用__cplusplus这个宏打印一下确认环境是不是你以为的那个标准。这一步花不了十秒钟但它能省掉你后面半小时对着红字发呆的时间。
返回列表