信息学奥赛C++入门指南:从零掌握核心语法与STL应用

发布时间:2026/7/22 4:54:14

信息学奥赛C++入门指南:从零掌握核心语法与STL应用 1. 项目概述为什么C依然是信息学奥赛的“硬通货”如果你正在为信息学奥赛OI做准备或者刚刚踏入编程世界的大门面对琳琅满目的编程语言心里可能犯嘀咕到底该学哪个Python看起来简单Java好像很流行但为什么几乎所有OI教练和资深选手都会不约而同地指向C这篇指南就是为你解开这个疑惑并带你从零开始一步步掌握这门在算法竞赛和系统开发领域屹立不倒的“神兵利器”。我接触C超过十年从大学ACM竞赛到后来的工业级项目开发深知它的魅力和“坑点”。2025年尽管新语言层出不穷但C在信息学奥赛中的地位不仅没有动摇反而因其无可替代的性能优势和对底层细节的掌控力变得更加重要。它就像赛车手手中的方向盘直接、精准、反馈迅速能让你在算法的赛道上将性能压榨到极致。简单来说这篇指南的目标是让一个完全零基础的小白能够理解C的核心思想搭建起可用的编程环境掌握竞赛所需的关键语法和数据结构并最终能够独立解决NOIP/NOI级别的算法问题。我们会避开那些庞杂的、在竞赛中极少用到的特性直击要害把有限的精力投入到最高效的学习路径上。你会发现C并没有传说中那么可怕一旦你跨过最初的门槛它的强大和优雅会让你着迷。2. 核心需求解析信息学奥赛对C的要求到底是什么在开始敲代码之前我们必须先搞清楚目标。信息学奥赛不是软件开发它考察的核心是算法设计能力和代码实现效率。因此它对C的要求是高度特化的与工业级C开发有显著区别。理解这一点能让你省去大量学习无用知识的时间。2.1 竞赛C vs. 工业C学习侧重点的差异工业级C开发需要考虑软件工程、设计模式、可维护性、跨平台兼容性、内存安全如智能指针等复杂问题。而竞赛编程场景极其纯粹在一个封闭的评测系统Online Judge, OJ中你的程序接收标准输入经过计算输出标准答案。评判标准只有两个正确性和效率时间和空间复杂度。因此竞赛C的学习重点非常明确语法基础变量、循环、分支、函数、数组。这是所有程序的基石。标准模板库STL这是竞赛C的“核武器”。vector,string,queue,stack,set,map这些容器以及sort,lower_bound这些算法能让你用极短的代码实现复杂功能极大提升编码速度和正确率。掌握STL就掌握了竞赛编程一半的战斗力。基础数据结构与算法链表、栈、队列、二叉树特别是二叉堆、并查集、哈希表等的手动实现或STL应用以及排序、查找、递归、分治、贪心、动态规划、图论DFS/BFS/最短路/最小生成树等经典算法。这些是解题的核心思想。输入输出优化面对百万级的数据量cin/cout可能成为性能瓶颈。必须掌握scanf/printf或如何关闭cin/cout的同步流来提升速度。这是竞赛特有的“骚操作”。调试与测试如何设计测试用例如何使用输出调试或简单的IDE调试功能快速定位逻辑错误。你会发现像类的继承与多态、异常处理、模板元编程等高级特性在竞赛中几乎用不到。我们的学习路径必须围绕上述核心需求展开切忌贪多求全。2.2 零基础学习者的核心痛点与应对策略对于零基础学习者最大的几个障碍通常是环境配置被Visual Studio、VSCode、编译器、调试器这些工具吓到还没开始写代码就放弃了。指针与内存这是C/C特有的概念抽象难懂但又是理解许多问题的关键。调试困难程序运行结果不对不知道从哪里开始查面对黑框框控制台一筹莫展。针对这些痛点本指南会提供最简化的环境配置方案推荐使用**小熊猫C或Dev-C**这类轻量级、专为竞赛设计的IDE并用大量生活化的类比来解释指针等抽象概念。同时我们会强调“分模块测试”和“打印调试”这两种在竞赛中最实用、最快速的调试方法。3. 环境搭建选择最适合新手的“起跑线”工欲善其事必先利其器。一个友好、稳定的编程环境能让你专注于学习逻辑而不是和工具搏斗。对于竞赛入门我强烈不建议一上来就使用Visual Studio或复杂配置的VSCode。3.1 IDE推荐小熊猫C原Dev-C的现代版对于Windows用户**小熊猫C**是目前最适合信息学奥赛新手的IDE没有之一。它是著名的Dev-C的继承和现代化版本优点非常突出一键安装集成了编译器MinGW、编辑器和调试器下载一个安装包点下一步就能用。界面简洁没有复杂的功能按钮专注于代码编写、编译和运行。竞赛友好默认支持单文件编译方便快速测试算法代码。中文支持好由国内团队维护文档和社区支持更贴近国内学生。注意很多学校机房或老教程可能还在用Dev-C其最后一个官方版本更新于2015年在Win10/Win11上可能存在兼容性问题。小熊猫C是更好的选择。安装步骤简述访问小熊猫C官网下载最新版本的安装程序。运行安装程序基本上一路“下一步”即可。建议安装路径不要有中文或空格。安装完成后打开软件新建一个源文件.cpp后缀就可以开始写你的第一个“Hello, World!”程序了。3.2 备选方案在线评测系统OJ的编辑器如果你不想在电脑上安装任何软件或者想在多台设备间同步学习直接使用各大在线评测系统的内置编辑器也是一个不错的起点。例如洛谷国内最大的OJ之一有强大的社区和题解功能。它的在线IDE功能完善支持代码高亮、运行和简单的调试。Codeforces国际知名竞赛平台其编辑器同样可以直接编写、运行代码需要简单的输入。这种方式的好处是“开箱即用”零配置。缺点是对网络有依赖且功能不如本地IDE强大。适合前期体验和做练习题。3.3 第一个程序从“Hello, World!”到理解编译过程让我们在小熊猫C中写下并运行第一个程序#include iostream // 包含输入输出流的头文件 using namespace std; // 使用标准命名空间避免写 std::cout int main() { // 每个C程序都必须有一个main函数它是程序入口 cout Hello, World! endl; // 输出字符串并换行 return 0; // 向操作系统返回0表示程序正常结束 }点击“编译运行”通常是F11键你会在下方看到一个黑色控制台窗口输出Hello, World!。这个过程背后发生了什么编写源代码你写的是人类可读的.cpp文本文件。编译编译器如g将你的源代码翻译成计算机能理解的机器码.exe等可执行文件。这个阶段会检查语法错误。运行操作系统加载这个可执行文件并执行它。理解这个过程很重要。后续当你遇到“编译错误”时就知道这是语法问题遇到“运行错误”或“逻辑错误”则是程序能运行但结果不对或中途崩溃。4. 核心语法精讲避开弯路直击竞赛要点掌握了环境我们就正式进入C语言的核心。我会按照竞赛中的使用频率和重要性来讲解确保你学的东西立刻就能用上。4.1 变量、数据类型与输入输出程序与世界的接口程序是用来处理数据的。数据有不同的类型就像仓库里存放不同种类的货物整数、小数、字符。#include iostream using namespace std; int main() { // 1. 基本数据类型声明与初始化 int age 18; // 整型存储整数 double score 95.5; // 双精度浮点型存储带小数点的数 char grade A; // 字符型存储单个字符用单引号 bool isPassed true; // 布尔型只有 true(1) 和 false(0) // 2. 输入与输出 int num; cout 请输入一个整数: ; // 输出提示信息 cin num; // 从控制台读取一个整数存入变量num cout 你输入的数是: num endl; // 3. 格式化输出printf——竞赛提速关键 double pi 3.1415926; printf(pi的值是%.2f\n, pi); // 输出两位小数3.14 // %d 整数 %f 浮点数 %c 字符 %s 字符串 // \n 是换行符比 endl 在大量输出时效率更高 return 0; }实操心得在竞赛中当需要输出固定格式如保留小数、对齐时printf比cout方便得多。对于大量输入输出可以在一开始加上ios::sync_with_stdio(false); cin.tie(0);来关闭cin/cout与scanf/printf的同步从而提升cin/cout的速度但之后就不能混用这两套IO了。新手前期可以先用cin/cout遇到性能瓶颈再学习这个优化。4.2 控制结构让程序“思考”和“重复劳动”程序不能只会顺序执行更需要根据条件做出判断分支以及重复执行某些操作循环。分支结构if-else, switchint score; cin score; if (score 90) { cout 优秀 endl; } else if (score 60) { cout 及格 endl; } else { cout 不及格 endl; } // switch适用于对单个整型或字符变量进行多路分支判断循环结构for, while, do-while// for循环明确知道循环次数时使用 for (int i 0; i 10; i) { // 初始化循环条件每次循环后执行 cout i ; } cout endl; // while循环条件满足时一直执行可能一次都不执行 int n, sum 0; cin n; while (n 0) { sum n; n--; } cout 累加和: sum endl; // do-while循环先执行一次再判断条件。至少执行一次。4.3 数组与字符串存储和处理数据集合数组是存储相同类型数据的连续集合通过下标索引访问下标从0开始。#include iostream #include cstring // 用于C风格字符串函数 using namespace std; int main() { // 1. 一维数组 int arr[5] {1, 2, 3, 4, 5}; // 声明并初始化一个长度为5的整型数组 for(int i 0; i 5; i) { cout arr[i] ; // 通过 arr[i] 访问第i个元素 } cout endl; // 2. 二维数组可想象为矩阵 int matrix[2][3] {{1,2,3}, {4,5,6}}; cout matrix[1][2] endl; // 输出第二行第三列6 // 3. C风格字符串字符数组 char str1[10] Hello; // 末尾会自动加 \0 表示结束 char str2[] World; strcat(str1, str2); // 拼接字符串str1变为 HelloWorld cout strlen(str1) endl; // 输出字符串长度10 // 4. C string 类推荐使用更安全方便 #include string // 需要包含头文件 string s1 Hello; string s2 C; string s3 s1 s2; // 轻松拼接 cout s3 endl; // Hello C cout s3.length() endl; // 获取长度 cout s3.substr(0, 5) endl; // 截取子串: Hello return 0; }注意事项数组下标越界是C新手最常见的错误之一会导致程序崩溃或产生不可预知的结果。访问arr[5]对于一个长度为5的数组是非法的。务必确保循环条件中的索引值在有效范围内[0, 数组长度-1]。4.4 函数模块化与代码复用的艺术函数是把一段完成特定功能的代码封装起来方便重复调用。这符合“分而治之”的思想是编写复杂程序的基础。#include iostream using namespace std; // 函数声明返回类型 函数名(参数列表) int add(int a, int b); // 告诉编译器有这个函数具体实现可以在后面 // 函数定义 int add(int a, int b) { int sum a b; return sum; // 使用return语句返回结果 } // 另一个例子计算阶乘递归函数 long long factorial(int n) { if (n 1) return 1; // 递归终止条件 return n * factorial(n - 1); // 函数调用自身 } int main() { int x 5, y 3; int result add(x, y); // 函数调用 cout x y result endl; int n 5; cout n ! factorial(n) endl; // 输出 120 return 0; }理解“形参”与“实参”函数定义时的a和b是形式参数形参是函数内部的局部变量。调用时传入的x和y是实际参数实参它们的值被拷贝给了形参。这意味着在函数内部修改形参不会影响外部的实参除非使用引用或指针这是后话。4.5 指针与引用理解内存的“地址簿”这是C的难点但也是精髓。你可以暂时不深入但必须建立基本概念。指针一个变量其值是另一个变量的内存地址。就像一张纸条上面写着“宝藏数据藏在哪个抽屉内存地址里”。引用一个变量的别名。就像给一个人起了个外号无论叫本名还是外号指的都是同一个人。#include iostream using namespace std; int main() { int num 42; int* ptr # // ptr是一个指针存储了num的地址。是取地址符。 int ref num; // ref是num的引用ref就是num的另一个名字。 cout num的值: num endl; // 42 cout 通过指针访问: *ptr endl; // *是解引用符获取指针指向地址的值 cout 通过引用访问: ref endl; // 42 // 修改 *ptr 100; // 通过指针修改num的值 cout 修改后num: num endl; // 100 ref 200; // 通过引用修改num的值 cout 再次修改后num: num endl; // 200 return 0; }为什么竞赛中要懂指针动态内存分配竞赛中有时需要根据输入数据的大小来创建数组而普通数组的大小必须在编译时确定。这时就需要new关键字来动态申请内存它返回的就是一个指针。int n; cin n; int* dynamicArray new int[n]; // 动态创建一个长度为n的数组 // ... 使用 dynamicArray[i] ... delete[] dynamicArray; // 使用完毕后必须手动释放内存防止内存泄漏理解STL底层很多STL容器内部使用了指针和动态内存。函数传参当需要在一个函数中修改另一个函数中的大型变量如数组、结构体时传递指针或引用可以避免整个数据的拷贝极大提升效率。这是竞赛编程中至关重要的技巧。避坑指南动态内存 (new) 一定要配对使用delete释放否则会导致“内存泄漏”。在竞赛中对于简单的题目通常可以避免使用动态内存直接用足够大的静态数组如int arr[100000]是更安全简单的做法。但对于链表、树等动态数据结构指针是绕不开的。5. 标准模板库STL竞赛编程的“瑞士军刀”如果说C基础语法是木棍和石头那么STL就是为你打造好的精良武器库。熟练掌握STL是区分竞赛新手和老手的关键标志。它提供了现成的、高度优化的数据结构和算法。5.1 容器Containers数据的百宝箱序列式容器vector动态数组最常用可以动态增长支持随机访问像数组一样用[ ]。#include vector #include iostream using namespace std; int main() { vectorint v; // 创建一个空的int向量 v.push_back(10); // 在末尾添加元素 v.push_back(20); v.push_back(30); cout v[1] endl; // 输出20 cout v.size() endl; // 输出3元素个数 // 遍历vector for(int i 0; i v.size(); i) cout v[i] ; cout endl; // 或者使用范围for循环C11 for(int num : v) cout num ; return 0; }string前面已介绍比C风格字符串安全方便太多。queue队列先进先出FIFO。push入队pop出队front访问队首。stack栈后进先出LIFO。push入栈pop出栈top访问栈顶。关联式容器set集合自动排序且元素唯一的容器。查找、插入、删除效率极高O(log n)。#include set setint s; s.insert(3); s.insert(1); s.insert(4); s.insert(1); // 重复的1不会被插入 for(auto it s.begin(); it ! s.end(); it) cout *it ; // 输出1 3 4 已排序 if(s.find(3) ! s.end()) cout 找到了3 endl;map映射存储键值对key-valuekey唯一且自动排序。可以把它想象成一个超级数组下标key可以是任意类型如字符串。#include map #include string mapstring, int scoreMap; scoreMap[Alice] 95; scoreMap[Bob] 88; cout scoreMap[Alice] endl; // 输出95 // 遍历map for(auto pair : scoreMap) { cout pair.first : pair.second endl; // first是key, second是value }5.2 算法Algorithms封装好的高效工具algorithm头文件提供了大量通用算法最常用的莫过于排序。#include algorithm #include vector #include iostream using namespace std; int main() { vectorint v {5, 2, 8, 1, 9}; // 1. 排序默认升序 sort(v.begin(), v.end()); // v变为 {1, 2, 5, 8, 9} // 降序排序 sort(v.begin(), v.end(), greaterint()); // v变为 {9, 8, 5, 2, 1} // 2. 查找要求容器已排序 if(binary_search(v.begin(), v.end(), 5)) { cout 找到了5 endl; } // 查找下界第一个不小于目标值的位置 auto it lower_bound(v.begin(), v.end(), 5); if(it ! v.end()) cout *it endl; // 3. 反转 reverse(v.begin(), v.end()); // 4. 最大/最小值 int maxVal *max_element(v.begin(), v.end()); int minVal *min_element(v.begin(), v.end()); return 0; }实操心得sort函数非常强大它使用的是IntroSort内省排序在绝大多数情况下效率远高于自己手写的排序。对于自定义结构体排序需要重载运算符或提供一个比较函数。STL算法通常以迭代器[begin, end)作为参数范围这是一个“左闭右开”的区间end指向的是最后一个元素的下一个位置这个设计需要习惯。6. 数据结构与算法入门从理论到解题实践掌握了语言和工具接下来就是修炼内功——数据结构和算法。这是信息学奥赛考察的核心。6.1 线性结构栈、队列与链表的应用栈的应用场景函数调用栈、表达式求值、括号匹配、深度优先搜索DFS的非递归实现。// 括号匹配问题经典栈应用 #include stack #include string #include iostream using namespace std; bool isValid(string s) { stackchar stk; for(char c : s) { if(c ( || c [ || c {) stk.push(c); else { if(stk.empty()) return false; char top stk.top(); if((c ) top () || (c ] top [) || (c } top {)) { stk.pop(); } else { return false; } } } return stk.empty(); }队列的应用场景广度优先搜索BFS、缓存实现、任务调度。链表在竞赛中除非题目明确要求或需要频繁插入删除中间元素否则通常用vector或静态数组模拟链表比手写指针链表更不容易出错。6.2 树状结构二叉树与优先队列二叉树很多高级数据结构如二叉搜索树、堆、线段树的基础。理解递归遍历前序、中序、后序是关键。优先队列priority_queue实际上是一个堆默认大顶堆。能够快速获取并移除最大或最小元素。#include queue #include iostream using namespace std; int main() { // 默认是大顶堆最大的元素在队首 priority_queueint pq; pq.push(3); pq.push(1); pq.push(4); pq.push(1); while(!pq.empty()) { cout pq.top() ; // 依次输出 4, 3, 1, 1 pq.pop(); } // 如何实现小顶堆 priority_queueint, vectorint, greaterint min_pq; // 小顶堆 return 0; }应用场景Dijkstra最短路径算法、哈夫曼编码、求动态集合的最大/最小值。6.3 算法思想初探枚举、贪心、递归与分治枚举/暴力最简单直接的思想尝试所有可能解。在数据范围很小时是可行的解题起点。优化枚举如剪枝是搜索算法的核心。贪心每一步都采取当前状态下最优的选择希望导致全局最优。它不像动态规划那样有固定的公式更多是一种解题思路需要证明其正确性或举出反例。典型问题区间调度、哈夫曼编码、部分背包问题。递归与分治递归是函数调用自身。分治是将大问题分解成结构相同的小问题分解决小问题治再合并结果合。归并排序和快速排序是分治法的经典体现。// 递归示例斐波那契数列效率低仅用于演示 int fib(int n) { if(n 1) return n; return fib(n-1) fib(n-2); } // 分治示例归并排序伪代码思路 void mergeSort(vectorint arr, int left, int right) { if(left right) return; int mid (left right) / 2; mergeSort(arr, left, mid); // 分 mergeSort(arr, mid1, right); // 分 merge(arr, left, mid, right); // 治合并两个有序数组 }6.4 搜索与动态规划DP竞赛的两大核心深度优先搜索DFS与广度优先搜索BFS这是解决图论和许多组合问题的通用框架。DFS一条路走到黑走不通再回溯。通常用递归实现适合求所有解、路径问题。BFS一层一层向外扩张用队列实现。适合求最短步数、最少转换次数。动态规划DP解决具有重叠子问题和最优子结构性质的问题。核心思想是“记住求过的解”避免重复计算。定义状态dp[i]或dp[i][j]代表什么状态转移方程如何用已知状态推导出未知状态这是DP最难也最核心的部分。初始条件最小子问题的解是什么计算顺序确保在计算一个状态时它所依赖的状态都已被计算过。经典入门例题斐波那契数列DP版int fib_dp(int n) { if(n 1) return n; vectorint dp(n1, 0); dp[0] 0; dp[1] 1; // 初始条件 for(int i 2; i n; i) { dp[i] dp[i-1] dp[i-2]; // 状态转移方程 } return dp[n]; }这个版本的时间复杂度是O(n)远优于递归版的O(2^n)。这就是DP“空间换时间”和“避免重复计算”的威力。7. 实战演练与调试技巧从“写得出”到“写得对”学了再多理论不动手都是空谈。这里提供一个完整的解题流程和调试方法论。7.1 典型竞赛题拆解以“AB Problem”为例别笑这不仅是入门题更是理解OJ系统工作方式的绝佳例子。题目输入两个整数A和B输出它们的和。输入格式一行两个整数空格分隔。输出格式一个整数表示AB的和。解题步骤理解题意与数据范围确认输入输出格式了解A和B的范围例如在int范围内。设计算法本题就是直接相加。编写代码#include iostream using namespace std; int main() { int a, b; cin a b; cout a b endl; return 0; }测试边界测试输入0 0-100 1002147483647 1注意int溢出如果题目说会超出int范围就要用long long。特殊测试输入带空格、换行符题目说“一行”我们的cin会自动处理空格和换行。提交在OJ上选择语言C提交代码等待评测结果Accepted, Wrong Answer, Time Limit Exceeded等。7.2 调试技巧当程序不按预期运行时程序出错是常态尤其是竞赛中时间紧迫。高效的调试能力至关重要。输出调试法Print Debugging最原始但最有效。在怀疑出问题的地方打印关键变量的值。// 假设一个排序函数结果不对 void mySort(vectorint arr) { // ... 一些操作 ... cout 调试点1数组当前状态; for(int num : arr) cout num ; cout endl; // ... 更多操作 ... }小数据测试自己构造一些小的、容易手算的测试用例。比如排序就用[3,1,2]这样的小数组。对比法用你的程序和一个已知正确的程序或者STL的sort跑同样的随机数据对比输出。使用IDE调试器小熊猫C等IDE支持设置断点、单步执行、查看变量值。对于复杂逻辑错误这比输出调试更直观。学习使用它是进阶必备技能。仔细阅读错误信息编译错误根据提示的行号和错误描述修改语法。常见错误缺少分号、括号不匹配、变量未声明。运行错误Runtime Error如“段错误”Segmentation Fault通常是数组越界、空指针解引用、栈溢出递归太深。答案错误Wrong Answer逻辑错误。重新审视算法用更多测试用例验证。时间超限Time Limit Exceeded算法时间复杂度太高需要优化。内存超限Memory Limit Exceeded使用了过多内存检查是否有不必要的巨大数组或内存泄漏。7.3 常见问题排查速查表问题现象可能原因排查方向程序编译通过但运行后立刻崩溃数组越界、指针操作错误、除零错误检查所有数组下标检查指针是否为空检查除法分母。输出结果部分正确部分错误边界条件处理不当、初始化错误检查循环的起止条件检查变量是否已正确初始化。在小数据上正确大数据上错误或超时算法复杂度高、使用了低效的数据结构、变量类型溢出分析算法时间复杂度考虑使用更高效的算法或数据结构如用map代替线性查找。检查int是否会溢出改用long long。在本地正确提交OJ错误未处理多组输入、输入输出格式不符、环境差异题目是否要求“处理到文件尾”输出是否要求行末空格或换行递归程序深度稍大就崩溃递归层数过深导致栈溢出尝试改为迭代循环实现或者调整系统栈大小竞赛中通常不可行。8. 学习路径与资源推荐从入门到精通最后分享一个我认为比较合理的C竞赛学习路径以及一些优质的资源。第一阶段语法基础与环境熟悉1-2周目标能编写顺序、分支、循环结构的程序使用数组和基本函数完成OJ上的简单模拟题。资源小熊猫C自带示例、《信息学奥赛一本通》第一部分。第二阶段STL与基础数据结构2-3周目标熟练掌握vector,string,queue,stack,set,map,sort的使用。能用它们解决线性表相关的问题。练习平台洛谷的“新手村”和“普及组”题目。第三阶段简单算法与搜索1-2个月目标理解枚举、贪心、递归、分治思想掌握DFS和BFS框架能解决经典的迷宫、八皇后等问题。资源《算法竞赛入门经典第2版》刘汝佳著前几章。第四阶段动态规划与图论入门2-3个月目标理解DP的基本思想能解决背包、线性DP等经典问题。掌握图的基本概念和存储方式邻接矩阵、邻接表会写DFS/BFS遍历图。关键大量练习总结模型。DP需要“悟”多看、多练、多总结状态转移方程。第五阶段进阶与专题训练持续目标学习更高级的数据结构并查集、线段树、树状数组等和算法最短路、最小生成树、网络流、字符串匹配等参加模拟赛进行专题突破。资源各大OJ的专题训练、历年NOIP/NOI真题、《算法竞赛进阶指南》李煜东著。我个人最深的体会是学习算法竞赛动手练习的重要性远远大于看书听课。看懂一个算法和能在时限内独立用代码实现它中间隔着巨大的鸿沟。最好的方法就是“刷题”从简单题开始逐步提升。遇到不会的先思考再看题解理解后自己再独立写一遍。建立一个自己的错题本或代码库记录经典题型和易错点定期回顾。这个过程没有捷径但每跨越一个台阶你解决问题的能力都会有质的飞跃。记住你写下的每一行代码调试的每一个错误都是通向“精通”的坚实一步。

相关新闻