尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

97天C++训练复盘:从环境配置到高频算法模板

发布时间:2026/9/10 19:02:06

资讯中心
01
ARTICLE

97天C++训练复盘:从环境配置到高频算法模板

97天C++训练复盘:从环境配置到高频算法模板
做了97天的C课后习题训练今天正好做个阶段复盘。说实话这个节点挺微妙的——既不像刚开始那样连VSCode都配不明白也不像真正的大神那样谈笑间手撕红黑树。但正是这种“卡在中间”的状态最适合把之前的积累沉淀成可以复用、可以给别人讲清楚的东西。这篇文章主要聊两件事一是这97天里我踩过的坑和总结出的训练方法二是几类高频习题字符串处理、排序与搜索、链表模板类、栈空间相关的解题模板与思考方式。如果你正在学C、准备面试或者刚被“VSCode配置C/C环境”折磨过这篇文章应该能帮你少走很多弯路。先说一个很多人忽略的事实C学到第97天真正的分水岭不是语法记得多熟而是能不能把“课后习题”当“工程问题”来解。比如冒泡排序算法书上的写法你能背但面试官问“怎么优化到最好情况O(n)”你就得懂标志位再比如字符串数组初始化报错了一下午最后发现是char*和string[]的存储区问题。这些细节都是习题练出来的不是看书看出来的。1. 训练整体设计与思路拆解1.1 为什么是“第97天”而不是“三个月”我刻意不用“三个月”来称呼这段训练周期因为实际天数比90天多了一周。这多出来的7天全是补课时间——哪次环境出问题、哪次某个数据结构死活弄不明白、哪次被一道看似简单的题耗掉了大半天都算在里面。所以如果你也在做类似的长期训练心态上要先接受一件事计划会被打乱但进度条不会因为被打乱而消失。97天的时间线我大致分成了三段第1天到第30天语法基础和STL常用容器刷题量不大但每个知识点都要写出能跑通的示例。第31天到第70天数据结构与算法强化开始接触链表、栈、队列、二叉树的模板类实现习题量提升到每天3到5道。第71天到第97天工程化与面试导向训练关注C特性和系统底层栈空间、IO缓冲、覆盖与隐藏、多线程同步整理八股文。这么切的原因很简单C这门语言语法和STL用起来是两回事STL用熟练之后看底层实现又是另一回事。如果一上来就对着《深入浅出C》去啃模板元编程大概率第10天就放弃了。先解决“能不能写出来”再解决“为什么这样写”最后解决“怎么写才更好”这是我认为对大部分自学者的合理路径。1.2 资料与工具选型别在小红书式“速成清单”里打转关于资料我自己试过不少最终长期稳定使用的只有三样教材首推《C Primer》中文版配合《深入浅出C》做补充。前者胜在系统后者胜在易读。在线工具一本在线C参考手册查函数签名和重载特别方便一个支持C17的在线编译器做快速验证用。本地环境VSCode MinGW-w64 C/C扩展这套组合自由度最高调试能力足够覆盖课后习题需求。工具选型上最大的坑是看到别人推荐Visual C 6.0就想去装一个怀旧。那个年代的IDE放到现在的Windows上兼容性问题很多代码里稍微用点现代C语法比如auto、nullptr就全挂完全没有必要折腾。1.3 每日训练节奏刻意练习不是熬夜硬刚我每天的固定安排是下午抽1.5小时晚上抽1小时。下午主要用来做“新课内容例题模仿”晚上用来“独立刷题整理错题”。中间穿插碎片时间比如等饭的时候看一下某个c小游戏的开源代码结构或者翻一篇别人写的c学习笔记。一个很重要的原则是不要连续超过1.5小时做同一道题。卡住了就标记好放到第二天或者先去查类似知识点比如你卡在“判断质数c优化”那就去看“素数筛法”第二天再回来重新分析。97天里我大概有12道题是“隔夜再做”才找到思路的。这种“间隔效应”对编程学习的帮助远比想象中大。2. 环境配置与核心语法细节深挖2.1 VSCode配置C/C环境从零到可调试这个环节是新手流失率最高的地方没有之一。我见过太多人卡在“已检测到匹配的Visual C Redistributable跳过安装”这步就放弃了。其实这里的关键是RedistributableVC运行库和你写代码用的编译器是两回事。如果你只需要运行别人编译好的C程序装Microsoft Visual C Redistributable就够。但如果你要自己写代码、编译、调试就得装编译器。MinGW-w64自带GCC是最常见的方案。具体配置分三步下载MinGW-w64解压或安装到一个无空格的路径比如D:\mingw64。把D:\mingw64\bin加到系统环境变量PATH里。在VSCode里装C/C扩展然后新建tasks.json和launch.json。第3步的tasks.json是让VSCode知道“如何编译你的代码”launch.json是让VSCode知道“如何启动调试器”。我用的最小可运行配置大概长这样{ version: 2.0.0, tasks: [ { label: C Build, type: shell, command: g, args: [ -g, ${file}, -o, ${fileDirname}\\${fileBasenameNoExtension}.exe ], group: build } ] }这段配置的作用是把当前打开的.cpp文件编译成同目录下的.exe可执行文件。-g参数是为了生成调试信息没有它VSCode的打点调试大概率不生效。2.2 cin提速与流式IO课后习题里最值钱的“小优化”有一个场景你一定遇到过循环里用cin读超大数据程序半天不出结果。这不是你的算法错了而是C的cin默认要和C标准IOscanf保持同步导致效率很低。解决办法是加一行ios::sync_with_stdio(false); cin.tie(nullptr);第一行关闭与C标准IO的同步第二行取消cin和cout的绑定。有个朋友问我加了这两行之后是不是就能随便用cin刷题了我的看法是刷题场景完全够了但如果你在做c流i/o相关的工程代码还是要搞清楚streambuf和ostream的关系别只在竞赛里会用。2.3 C字符串数组初始化几个藏得很深的坑热搜词里有“c字符串数组初始化”说明被这玩意坑过的人不少。常见写法有这几种char str1[] hello; // 栈上分配内容可改 const char* str2 hello; // 常量区内容不可改 string arr[] {a, bb}; // STL string 数组第2种特别容易出问题。str2指向的是一个字符串字面量可能存放在只读区如果你试图修改str2[0]在某些编译器上会直接崩溃。用指针声明的字符指针它的目标区域是谁管、能不能写取决于声明方式这在面试里也常被拿来问。2.4 C覆盖、隐藏与重载概念题的本质“c 覆盖 隐藏”是一个特别容易混淆的知识点。很多人把覆盖override和隐藏hide当一回事其实不是重载同一个作用域内同名函数参数列表不同。覆盖基类函数是虚函数派生类重写它调用时根据动态类型决定。隐藏派生类定义了与基类同名的函数基类的同名函数在派生类作用域内被“藏”起来了即使参数列表不同也会发生隐藏。它们的判断标准我总结成一句话先看作用域再看虚不虚。作用域不同基类 vs 派生类就可能隐藏虚函数派生类重写才叫覆盖。面试题里经常给一个基类指针指向派生类对象然后调用一个同名函数问你输出哪个版本。如果函数不是虚函数那结果就是基类版本因为编译器看的是静态类型。这个点课后题里不练个三五遍是记不牢的。2.5 栈空间为什么大数组直接爆掉“c 栈空间”是另一个高频搜索词。简单说默认情况下Windows的栈空间大概是1MBLinux是8MB左右。你如果写int a[300000];在函数内部这相当于直接在栈上申请了约1.2MB假设int是4字节很可能直接报栈溢出程序启动就崩。解决办法有几种用vector或new分配堆内存。把大数组声明成全局变量静态存储区不占用栈空间。用std::array但同样要注意大小。在我第97天做的一道题里需要存一个二维的图结构直接写int g[2000][2000]在函数里面就崩了挪到全局区就没事。这种“明明逻辑没错程序就是跑不起来”的情况十有八九都是存储区问题。3. 算法与数据结构习题里的高频“局”3.1 冒泡排序算法C从实现到优化冒泡排序是排序题里最基础的但也是最能考细节的。基础版就不贴了直接说优化版void bubbleSort(int 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] a[j 1]) { std::swap(a[j], a[j 1]); swapped true; } } if (!swapped) break; } }关键就在swapped标志位。如果某一轮循环里没有任何交换说明数组已经有序直接跳出。这样最好情况本来就有序的时间复杂度能降到O(n)这也是面试里常问的“冒泡排序能不能优化到O(n)”。课后习題里冒泡排序经常和结构体排序混在一起出比如按分数从高到低排序。可以用std::sort但如果你用冒泡得知道怎么在比较时只判断结构体的某个字段然后整体交换。这种题对理解“比较器”概念很有帮助。3.2 快速幂算法C从递归到迭代“快速幂算法c”也是热搜词里靠前的。它的核心思想是计算a的b次方时不需要连乘b次而是把指数拆成二进制利用a^(2^k)逐步相乘。递归版比较直观long long quickPow(long long a, long long b) { if (b 0) return 1; long long half quickPow(a, b / 2); if (b % 2 0) { return half * half; } else { return half * half * a; } }迭代版更常用而且面试更爱考long long quickPow(long long a, long long b, long long mod) { long long res 1; while (b 0) { if (b 1) res res * a % mod; a a * a % mod; b 1; } return res; }这题我在第80天左右才真正熟练。之前总是记不住b 1的目的后来自己想通了二进制的最低位是1说明指数这一位对应的权重要乘进结果里。这个视角一旦建立快速幂就再也不会忘。3.3 归并排序与单调栈两个看起来高大上其实都是模板的算法归并排序是分治思想的代表也是求逆序对的基础。它的核心操作是把两个有序数组合并成一个有序数组。代码量比快速排序长但稳定性好而且时间复杂度稳定O(n log n)。单调栈是另一个高频考点尤其在“下一个更大元素”这类问题里。它的典型模板是维护一个栈内元素单调递增或递减的栈每次入栈时把破坏单调性的元素弹出去。这个模板看起来简单真正用起来需要理解“什么时候该出栈”“出栈时能得出什么结论”。我建议你把这几个算法分别写成可以复用的模板函数放进自己的代码仓库。c排序方式这类搜索词背后其实覆盖了大量需要理解不同排序适用场景的诉求。后面遇到题直接调自己的模板效率会高很多。3.4 判断质数C优化从O(n)到O(sqrt(n))再到筛法“判断质数c优化”看起来简单但展开讲可以讲很深。最朴素写法是循环到n-1复杂度O(n)。优化第一步是循环到sqrt(n)bool isPrime(int n) { if (n 2) return false; for (int i 2; i * i n; i) { if (n % i 0) return false; } return true; }再进一步判断大量数字是不是质数用埃拉托斯特尼筛法一次性生成质数表。这是课后习题里经常出现的“从单个判断到批量判断”的思路跨越。i * i n这里有个小坑如果n很大的话i * i可能会溢出。更稳妥的方式是写i n / i。4. 实操过程与核心环节实现4.1 今日训练现场一道链表模板类的完整推理过程第97天的训练里我做了一道综合题实现一个模板类链表支持插入、删除、反转、查找并且用C template让它可以存任意类型数据。我先把题目要求拆成三类基本操作增删查改。进阶操作反转链表、找中间节点。模板化要求不写死int用templatetypename T。核心实现大概是这样的结构template typename T class MyList { private: struct Node { T val; Node* next; Node(const T v, Node* n nullptr) : val(v), next(n) {} }; Node* head; public: MyList() : head(nullptr) {} void insertFront(const T v) { head new Node(v, head); } void reverse() { Node* prev nullptr; Node* cur head; while (cur) { Node* nxt cur-next; cur-next prev; prev cur; cur nxt; } head prev; } };里面有几个点值得注意Node的构造函数用初始化列表而不是在函数体里赋值这样更高效。insertFront先创建新节点再让新节点的next指向当前头节点最后更新头节点。顺序不能乱。reverse需要三个指针prev、cur、nxt。做题的时候每次都先画图再写循环。4.2 实现细节析构与深拷贝模板链表还有一个特别容易被忽略的点析构函数。因为我用了new动态分配内存所以必须释放否则会内存泄漏。~MyList() { Node* cur head; while (cur) { Node* nxt cur-next; delete cur; cur nxt; } }如果你要做深拷贝还得自定义拷贝构造函数和赋值运算符。这里用到了“三/五法则”的思想——如果你自己管理资源就必须同时考虑析构、拷贝构造和赋值。很多初学者只写析构不写拷贝构造然后程序一复制就崩就是因为默认拷贝构造是浅拷贝两个对象的指针指向同一块内存析构时被释放两次。4.3 用C写小游戏把课后题变成“看得见”的东西在第60天左右我照着SFML的教程试着做c小游戏做了一个非常简单的控制台版的扫雷雏形。它的核心是一个二维数组的棋盘加上递归地展开空白格子。这个逻辑本质是什么是“深搜DFS”。这个例子让我彻底搞明白了深搜在真实问题里是怎么用的——你有一个当前格子坐标向上下左右四个方向递归调用并把空白格子展开直到到达边界或碰到数字格子。如果你还在学C我特别推荐做一个简单的控制台小游戏不需要图形库纯cout输出都行。比如从“猜数字”开始到“井字棋”再到“扫雷”。这个过程中你会把循环、数组、函数、递归都串起来效果远好于直接刷题。4.4 C多线程的入门实践从std::thread到std::async第90天左右我开始看c多线程一开始被各种术语吓住了。后来找到一个比较平易近人的切入点用std::async创建异步任务比直接操作std::thread简单很多。#include iostream #include future #include thread int compute(int x) { std::this_thread::sleep_for(std::chrono::milliseconds(500)); return x * x; } int main() { auto fut std::async(std::launch::async, compute, 10); std::cout doing something else... std::endl; int result fut.get(); std::cout result std::endl; return 0; }多线程的核心难点不在API而在“数据竞争”。如果你两个线程同时写同一个变量就需要加锁或使用原子操作。这里提一个课后题里很少考但工程中很重要的概念ABA问题。它本质上是在并发环境下一个值是“从A变成B再变回A”导致基于“值没变过”的判断出错。这个问题在无锁编程里是一个经典难点。4.5 从C习题到工程应用UG二次开发视角的延伸可能有人会问刷了97天的课后题和实际的工业软件开发有什么关系在第75天的时候我临时看了一点UGNX二次开发的资料发现它用C作为主要开发语言。开发中常常要操作Block UI对话框并通过代码去打开、关闭或读取用户输入。这个场景用到的东西恰恰是C课后题里训练过的回调函数、类封装、字符串处理、事件驱动。这个观察对我触动挺大。很多人觉得课后习题就是“题”可一旦你进入某个专业领域比如CAD二次开发、游戏引擎、音视频处理C的习题内容会换一个名字出现在你的工作流里。学习的时候多想想“这个语法在真实项目里用来干嘛”会比单纯背八股文更有后劲。4.6 回调函数课后题里最容易被误解的概念“c回调函数例子”也是搜索热词之一。我试着用最简单的方式解释你把一个函数A的地址作为参数传给另一个函数BB在合适的时候调用A这就是回调。最简单的例子是用std::functionvoid process(int x, std::functionint(int) cb) { int y cb(x); std::cout result: y std::endl; } int main() { process(5, [](int v) { return v * 2; }); return 0; }这里的lambda表达式其实就是一个回调函数。C里回调还有函数指针、仿函数等多种形式。面试的时候通常不会直接问“什么是回调”而是让你在一个排序函数里传入自定义比较器或者在一个事件系统里注册事件处理函数。理解了回调你就理解了“把行为作为参数传递”的核心思想。5. 常见问题与排查技巧实录5.1 环境与编译器相关的排查报错“g不是内部或外部命令”说明MinGW的bin目录没加到Path里或者加的路径不对。检查环境变量后重新打开终端再试。VSCode的调试按钮是灰色的大概率是launch.json里program路径和tasks.json里编译输出路径对不上。建议统一用${fileBasenameNoExtension}.exe这种写法避免手打出错。遇到“已检测到匹配的Visual C Redistributable跳过安装”这是安装软件时的提示不是报错说明你的系统已经有合适的运行库了。但要注意运行库只是运行时依赖不是编译器。5.2 编译与链接错误排查“undefined reference to xxx”如果你是分文件编译比如模板类拆成.h和.cpp很可能是模板的定义和声明分开了。模板的实例化发生在编译阶段所以定义必须在头文件里否则链接不到。“multiple definition of xxx”多次包含同一个头文件导致重定义。用头文件保护宏#pragma once或#ifndef解决。“expected primary-expression before ; token”多半是因为中英文分号混用或者漏写了一个括号。这种问题看着低级但最容易磨心态我现在遇到类似问题会先快速扫一遍分号和花括号。5.3 运行时崩溃与性能问题排查程序启动就崩溃/卡死优先怀疑大数组在栈上分配把大数组挪到全局区或用vector试试。输入数据特别多的场景卡死检查是不是没用ios::sync_with_stdio(false)或者算法复杂度过高比如不应该用O(n^2)却写了双重循环。链表相关操作崩溃优先看空指针。链表题里的崩溃十有八九是访问了nullptr的next。我做了一个简单的速查表方便大家对照问题现象可能原因优先排查动作编译不过分号、括号、引号缺失检查语法错误提示所在行链接报错模板声明与定义分离把模板实现放回头文件运行崩溃空指针/野指针打印指针地址检查new后的赋值大数组崩溃栈空间不足改为全局数组或vector输入慢未关闭流同步加上ios::sync_with_stdio(false)逻辑正确但输出不对循环边界错误用一两个小样例人工模拟跑一遍5.4 面试与八股文的备考技巧不管你是为了课后作业、考研复试还是就业面试c八股文都是绕不开的。但背着背着你就会发现很多题其实是环环相扣的问多态会牵扯到虚函数表和动态绑定。问智能指针会牵扯到引用计数和析构函数。问栈空间会牵扯到函数调用栈和局部变量生命周期。问内存模型会牵扯到左值右值和移动语义。我的建议是不要直接背八股而是先在IDE里跑一下相关代码。比如“覆盖与隐藏”你分别写一个普通函数版和虚函数版用基类指针去调用看输出结果一下子就明白了。6. 后续扩展与个人经验6.1 面向第100天该不该碰“小游戏”和“图形库”我现在正规划第100天的时候用c sfml做一个小项目目标很简单在一张长宽可调的二维网格上实现一个棋类游戏的基础逻辑。之所以选SFML是因为它比OpenGL入门门槛低又比纯控制台有意思。它的下载与配置方式很直接解压后在VSCode的c_cpp_properties.json里配置好include路径和lib路径就行。如果对游戏开发有兴趣建议从“逻辑先行、渲染后置”的思路开始——先实现一个没有界面的核心算法比如判断落子胜负再用SFML把棋盘画出来。这个过程中你会同时接触到设计模式、事件循环、资源管理这些工程概念比单纯刷题收获更大。6.2 个人经验代码量不是关键复盘才是97天训练下来我最大的一笔经验是不要追求每天写很多代码而要追求每天至少彻底搞明白一个“为什么”。比如为什么cin.tie(nullptr)要放在主函数开头因为cin和cout默认绑定每当你用cin读取前会先刷新cout的输出缓冲区这算C IO同步机制里的一个设计选择关闭绑定可以减少不必要的刷新开销。再比如为什么std::sort比手写快排序快因为std::sort在数据量小的时候会切换成插入排序还会做三数取中之类优化防止最坏情况的递归深度过深。这些问题看起来是“小题大做”但它们正是面试官区分“刷过题”和“真的懂”的关键。只记API的人换一个场景就不知所措搞懂了机制的人可以举一反三。6.3 留给正在学C的你的几条建议老老实实配好环境再开始做题。工欲善其事必先利其器这句话放在这里比任何地方都合适。别怕报错报错是编译器在给你讲课。对照着英文提示用搜索引擎查一次比纯看书记忆深刻十倍。一个习题做三遍第一遍不看任何参考独立完成第二遍对照题解优化思路第三遍在几天后重新写一遍考察自己是否真的掌握了。尽早接触模板、多线程、内存管理这些硬核内容。它们才是C区别于其他语言的价值所在也是企业面试的重点。写到这里我又想起自己第37天时对着c字符串转数组头疼的下午。当时觉得一个简单的split怎么都写不对后来发现C标准库本来就没有直接的split你得用std::getline、std::stringstream或者手写循环去处理。想通了这一点之后我反而对C的“直白”有了好感——它不惯着你但一旦你懂它一切尽在掌握。这就是C的魅力也是97天训练给我最大的回馈。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。