简介面向准备全国计算机等级考试二级C的考生这份PDF整理了20道笔试真题及配套答案适合考前自测、查漏补缺与知识点串联。内容围绕变量存储类型、类与对象、继承、运算符重载、函数模板与类模板、输入输出流、关键字、程序控制结构及指针等高频考点展开每道题保留题干与选项并给出参考答案便于对照理解命题思路与常见陷阱。答案标注清晰可配合错题整理建立个人知识清单对存储类型默认值、public继承后的访问权限、运算符重载参数形式、this指针及流对象使用等易混细节也能通过反复练习加深记忆。资源包共1个PDF文件大小约2.2MB轻量易存手机与电脑均可直接查阅。目前已有1809人学习下载可作为冲刺阶段的刷题材料也适合课堂教学或复习提纲使用。1. 计算机二级C到底在考什么从一道变量存储类型的选择题说起有人翻到一份计算机二级 C 的真题第一道选择题就问函数里说明变量的存储类型可以写 auto、extern、register、static如果不写默认是哪一个。四个词单个都眼熟放到一起却答不上来——这种卡壳很常见不是没学过 C而是从没搞清这场考试究竟在考语言的哪些角落。这份真题及答案资料整理的正是这些角落变量存储类型、类和对象、继承、运算符重载、模板、输入输出流、指针与程序控制结构选择题 70 分、填空题 30 分题干短、选项刁、答案就印在后面。它更像一张 C 基础概念的体检表而不是一本教材。下面把这张表一栏一栏拆开。2. 这门考试为什么偏爱存储类型和对象模型C 知识地图的第一层计算机二级 C 的题目有个很明显的口味不考你能不能从零写出一个完整系统专挑那些边界清晰、答案唯一、在纸面上就能判对错的知识点。变量存储类型和类与对象正好完美符合这个标准——四个关键字对应四种行为几个编译器自动生成的函数对应几条确定规则全都可以用一道选择题问死。这也解释了为什么这两块在真题里反复出现。它们并不是偏题怪题恰恰是 C 里最容易被「想当然」带过去的地方写代码时靠着编辑器的提示和运行结果能蒙混过关一旦让你不看运行结果、只凭纸面判断短板立刻现形。换句话说这部分题目考的不是记忆力而是你有没有真正建立一个「变量活多久、对象怎么生、怎么死」的心智模型。2.1 四个关键字四种「寿命」auto、extern、register、static 到底在管什么存储类型这个说法听起来抽象其实它一次性回答了三个很具体的问题这个变量活多久生命周期、谁能看见它作用域、它大致待在哪儿存储位置。打个比方一间办公室里坐着四种身份的人。auto 是临时工。进函数报到出函数走人每次来都是全新的一个人上次干过什么完全不记得。它是局部变量的默认身份所以老教材那句「不说明存储类型默认就是 auto」说的就是这个意思。static 是正式员工。工位一直留着人走了桌子还在。局部变量前面加 static初始化只在第一次进函数时执行一次之后每次进来都接着上次的值往下算。这个特性在填空题里出现的频率相当高——题目给你一个函数让你算第二次、第三次调用时的返回值考的就是「初始化只做一次」。extern 是外派人员。它不在这份文件里上班只是留一张名片告诉你「外面有这么个人你可以用」。它通常用来声明在别处定义的全局变量或函数本身不分配新的内存。register 是贴身助理。它只是向编译器提一个建议这个变量用得频繁能不能放到寄存器里快一点。这是口头建议编译器完全可以不理现代编译器基本都不理。所以它在真题里出现的意义主要是让你知道「它是一个建议不是命令」。这里有一个特别容易翻车的时代差。C11 之后auto 被赋予了全新的含义——类型推导写auto x 10;时它不再表示存储类型而是让编译器自己猜类型。但考试沿用的是老语境选项里出现的 auto 依然指「自动变量」。同一个词在两个时代指两件事这是很多人明明写过不少代码、却在真题上答错的原因。用一小段代码把「临时工」和「正式工」的差别摆出来#include iostream using namespace std; void counter() { int autoVar 0; // 普通局部变量每次进函数都重新初始化为 0 static int staticVar 0; // 静态局部变量只在第一次进函数时初始化一次 autoVar; staticVar; cout autoVar autoVar staticVar staticVar endl; } int main() { counter(); // autoVar1 staticVar1 counter(); // autoVar1 staticVar2 counter(); // autoVar1 staticVar3 return 0; }这段代码的逻辑很直白两个变量都写了 0但执行时机不同。autoVar 的初始化语句每次调用都执行所以永远从 0 开始加staticVar 的初始化语句在整个程序运行期间只执行一次之后那行代码等于被跳过变量保留上一轮的值。参数上唯一要记住的是static这个修饰符的位置——写在类型前面作用在变量上而不是作用在函数上。至于函数返回static值的情况填空题里常见的形式是函数内部声明一个静态变量并每次自增第二次调用时返回的就不是初始值而是累加后的结果。提示静态局部变量的初始化只发生一次但这个「一次」是线程无关的老规则。真写多线程程序时它反而会带来初始化竞争问题考试不考但心里要有个数。2.2 类与对象编译器什么时候替你写函数什么时候不写真题里有一道很经典的判断题大意是如果没有定义缺省构造函数编译器将「一定」生成一个缺省的构造函数。很多人第一反应是「对」因为教科书上说编译器会自动生成默认构造函数。但这道题的正确答案恰恰是「错」关键就在「一定」两个字上。编译器更像一个只在没人干活时才出手的助手只要你写了任何一个构造函数哪怕是有参数的它就不再给你补一个无参的默认构造函数。这时候你写Point p;就会直接编译报错。类比一下开发商交付精装房时确实配了基础家电可你只要自己动过其中一件那套「默认套餐」就不再整套提供了。class Point { public: // 自己写了一个带参数的构造函数 Point(int x, int y) : x_(x), y_(y) {} // 编译器不会再补 Point()此时写 Point p; 会编译报错 private: int x_, y_; };这段代码想说明的是「写了就没了」这个规则。Point(int, int)一旦存在编译器就认为你已经接管了对象的创建过程不再插手。解决办法也很简单要么再补一个Point() default;要么给现有构造函数的所有参数都设上默认值。拷贝构造函数遵循同样的逻辑你没写编译器生成一个逐成员拷贝的版本你写了它就不生成。这两条规则加起来就是真题里那几个选项对错的全部依据。2.3 从构造到析构一个对象的出生顺序和离场顺序构造函数和析构函数的执行顺序是填空题的高频考点因为它有一条非常确定、又非常反直觉的规则构造时先基类、再成员对象、最后才是派生类自己的函数体析构时完全倒过来。反直觉的地方在于成员对象的构造顺序由它们在类里的声明顺序决定跟初始化列表里写的先后顺序毫无关系。很多人写初始化列表时习惯按调用顺序排一遇到「成员对象构造顺序」的题目就懵了。#include iostream using namespace std; class Part { public: Part(int x 0) : val(x) { cout val; } // 构造时输出自己的值 ~Part() { cout val; } // 析构时也输出自己的值 private: int val; }; class Whole { public: // 初始化列表里 p2 写在前面但真正的构造顺序看下面的声明顺序 Whole(int x, int y, int z 0) : p2(x), p1(y), val(z) { cout val; } ~Whole() { cout val; } private: Part p1, p2; // 先 p1 后 p2这才是实际顺序 int val; }; int main() { Whole obj(1, 2, 3); // 输出 2133 return 0; }参数含义拆一下obj(1, 2, 3)里1 传给 x2 传给 y3 传给 z。构造阶段先按声明顺序构造 p1p1 拿到的初值是 y 即 2于是输出 2再构造 p2拿到 x 即 1输出 1最后执行 Whole 的函数体输出 val 即 3。三个字符连起来是 213。析构时倒序先 Whole 的函数体输出 3再析构 p2 输出 1再析构 p1 输出 2所以整个程序跑完看到的是 213312。只要记住「声明的次序就是出生的次序出生的次序反过来就是离场的次序」这类题基本不会错。3. 继承、多态、运算符重载真题为什么总在这三处设卡如果把这门考试的知识点按「题目数量」排个序继承、多态、运算符重载一定排在最前面。原因不复杂这三块每一块都有明确的语法规则但又都有一个「看起来差不多、实际完全不同」的陷阱区命题人几乎不需要费心设计随手一写就是一道能刷掉一半人的选择题。对备考的人来说这一章的目标是建立一张「规则—例外」的对照表。规则是主线例外是陷阱考试真正想筛的就是你有没有把例外也记住。3.1 public 继承像一层滤镜public 和 protected 被降到了哪继承方式常常被误解成「用什么方式继承就继承到什么」其实它更像一层滤镜作用是把基类成员的访问权限往下压一档。public 继承是不降级protected 继承是把 public 压成 protectedprivate 继承则把所有能继承的成员都压成 private。基类中的访问权限public 继承后protected 继承后private 继承后publicpublicprotectedprivateprotectedprotectedprotectedprivateprivate不可访问不可访问不可访问注意最后一行private 成员无论用哪种方式继承派生类都访问不到它只对基类自己的成员函数和友元开放。所以真题里「派生类成员函数不能访问基类的什么」这种问法答案往往就是私有成员。还有一个容易忽略的默认值如果不写继承方式用 class 定义派生类时默认是 private 继承用 struct 定义时默认是 public 继承。这两个默认值不一样是纯粹的语法规定没有任何逻辑可推只能记住。真题喜欢在这里设一个选项让人凭感觉选 public然后翻车。3.2 虚函数与对象切片同一个调用为什么走了两条路多态这部分最有代表性的一类题是给你一个基类指针指向派生类对象然后连续调用两个成员函数一个加了 virtual一个没加问你输出什么。#include iostream using namespace std; class Base { public: void output() { cout 1; } // 非虚函数按指针的静态类型绑定 virtual void print() { cout B; } // 虚函数按对象的实际类型绑定 virtual ~Base() {} // 虚析构保证 delete 时能调到派生类析构 }; class Derived : public Base { public: void output() { cout 2; } void print() override { cout D; } }; int main() { Base* ptr new Derived; ptr-output(); // 输出 1 ptr-print(); // 输出 D delete ptr; return 0; }关键在绑定时机。ptr的静态类型是Base*编译器看到ptr-output()时发现 output 不是虚函数就直接按指针类型定死调用 Base 的版本输出 1这叫静态绑定。而 print 是虚函数编译器不急着定而是留到运行时去查对象的虚函数表发现真实对象是 Derived于是调 Derived 的版本输出 D。参数和写法上有两个细节值得单独记。一是派生类里重写虚函数时写override是好习惯写错了编译器会直接报错而不是悄悄变成「隐藏」二是基类的析构函数一定要声明为 virtual否则delete ptr时只会执行基类的析构派生类里申请的资源就漏了这类问题是实际写代码时最容易踩坑的地方。3.3 运算符重载成员还是非成员一步决定参数个数运算符重载的题目看着花哨判断依据其实只有一条这个重载函数是成员函数还是非成员函数直接决定了参数表里应该写几个参数。原因是成员函数自带一个隐含的 this 指针指向左操作数。所以二元运算符重载成成员函数时参数表里只写一个参数右操作数重载成一元运算符时参数表干脆是空的。反过来重载成非成员函数时所有操作数都得显式列出来二元两个参数一元一个参数。class Money { public: Money(double v 0) : val(v) {} // 成员版二元运算符参数表只有一个参数左操作数是 *this Money operator(const Money other) const { return Money(val other.val); } // 成员版一元运算符参数表为空 Money operator-() const { return Money(-val); } private: double val; }; // 非成员版二元运算符两个操作数都要写出来 Money operator-(const Money a, const Money b);把这条规则套进真题很多选项一眼就能排除参数表里有两个参数却声称是成员函数的错参数表里参数个数和运算符目数对不上的错。还有一条硬性限制也常考重载的运算符函数参数里至少要有一个是自定义类型或枚举类型不能两个参数都是内置类型。所以operator*(double, double)这种写法是无效的——内置类型的乘法早就定好了不允许你再改。顺带说一句不是所有运算符都能重载.、::、?:、sizeof这几个就不行强行记一下即可。3.4 函数模板与类模板两个「泛型入口」的分工模板这块考试的范围其实很窄知道模板分函数模板和类模板两种知道模板参数可以拿来做数据成员的类型、成员函数的返回类型、成员函数的参数类型知道class和typename在模板参数列表里基本可以互换。// 类模板T 同时用作数据成员类型和成员函数返回类型 template typename T class Box { public: Box(T v) : value(v) {} T get() const { return value; } private: T value; }; // 函数模板调用时编译器可以根据实参推导出 T因此可以省略模板实参 template typename T T maxOf(T a, T b) { return a b ? a : b; } int main() { Boxint b(3); // 类模板必须显式写出类型 int m maxOf(1, 5); // 函数模板可以靠推导省掉 int return 0; }两者的差别可以这么理解函数模板是「一次定义、按需生成一堆函数」编译器看到调用时能从未写明的实参反推出类型所以模板实参可以省类模板是「一次定义、按需生成一堆类」编译器没有实参可看所以用的时候必须把类型写全。这个区别在选择题里经常以「以下说法错误的是」的形式出现选项往往就藏在「能不能省略模板实参」这句话上。4. 输入输出流、指针与 const那些容易被当成「送分题」的细节真题里总有那么几道题看题干觉得简单得不像话一对答案才发现错了。流、指针、const 这三块就是重灾区。它们的基础概念人人都知道但考试问的往往不是概念本身而是概念边上那条很细的边界线。这一章的目的不是把三块讲全而是把真题反复触碰的那几条边界线标出来。4.1 cout 是对象不是关键字流式输入输出的三个层次有个题目问语句cout A;里的 cout 是什么。选项给了类名、对象名、函数名、关键字四个。答案是对象名。这件事值得多说两句因为它把 C 的流体系串起来了iostream是一个头文件里面定义了ostream、istream这些类而cout是ostream类的一个已经创建好的对象cin是istream类的对象。它们不是语言内置的关键字是标准库提供的东西所以你甚至可以自己定义同名的变量把它遮住当然没人会这么干。流的另一个考点是格式化控制尤其是setw和setfill的组合。真题里出现过cout setw(10) setfill(*) 1234这样的写法问输出长什么样。#include iostream #include iomanip // setw、setfill 都在这个头文件里 using namespace std; int main() { cout setw(10) setfill(*) 1234 endl; // ******1234 cout 5678 endl; // 5678 return 0; }setw(10)的意思是「下一个输出至少占 10 个字符宽」默认右对齐不够的部分用setfill指定的字符补默认补空格这里改成补星号。1234 占 4 位前面补 6 个星号总共 10 位。参数上要特别注意setw是一次性的只对紧随其后的那一个输出生效第二行输出 5678 时就恢复默认前面不会再有星号。setfill则是一直有效直到你再次修改它。至于打开文件要包含哪个头文件答案是fstream这个属于死记硬背的送分点。4.2 int *p k一行声明里其实发生了两件事指针的声明和初始化真题喜欢用「以下哪个语句序列和它等价」的方式问。看着绕其实就是把一行拆成两步。int k 10; int* p k; // 一步到位定义指针 p并让 p 指向 k 的地址 int* q; // 等价写法拆成两步 q k; // 第一步定义指针第二步把 k 的地址赋给 q关键要分清p、*p、k三个东西各自是什么。p是一个变量里面存的是一个地址k取的是 k 的地址*p则是「顺着 p 里存的地址找过去把那个位置上的值取出来」。所以int* p k;相当于把k这个地址塞进 p 这个变量里而*p k;完全不是一回事——它的意思是「把 k 这个地址值写到 p 当前指向的那块内存里去」类型都对不上逻辑上也讲不通。用类比说p 是一张写着门牌号的纸条k是去查 k 家的门牌号*p是照着纸条上的门牌号找过去、敲门看里面是谁。声明指针并初始化本质就是「准备一张纸条并把某个门牌号写上去」两步合成一步而已。4.3 const 的位置决定它修饰谁const 相关的题判断依据只有一条const 修饰的是它左边的东西如果左边没有东西就修饰右边的东西。这条规则听起来像口诀但确实好用。const int a 10;里 const 左边没东西修饰右边的 int所以 a 是常量值不能改而且定义时必须初始化——const int a;这种写法在真题里是错误选项。const int* p;里 const 修饰的是 int意思是「p 指向的那个 int 不能通过 p 修改」但 p 本身可以改指向别处这叫指向常量的指针。int* const p k;里 const 修饰的是 p意思是「p 这个指针本身不能改指向」但它指向的值可以改这叫常量指针。两者一字之差用法完全相反。还有一类题考的是 const 成员函数和 const 对象的关系const 对象只能调用 const 成员函数非 const 对象两者都能调。原因是 const 对象承诺自己不会被修改而普通成员函数在编译器看来是有可能修改成员变量的所以不许调。真题里出现过「主函数中错误的语句是」这种问法答案往往就是对 const 对象调用了非 const 的成员函数。这条规则在实际写代码时也常遇到尤其是把对象作为 const 引用传参进函数时一调用非 const 成员就报错很多人第一次遇到都会愣一下。5. 五个最容易搞混的概念真题的选项就埋在这些地方前面的章节把知识按「块」讲了一遍这一章换个角度按「对」来讲。因为考试失分的大头从来不是完全不会而是把两个长得很像的东西张冠李戴。下面这五组是我认为最值得单独拿出来捋一遍的。5.1 重载、重写、隐藏长得像绑定时机完全不同易混点在于这三个词在中文里都很像「重新弄一个」但它们在 C 里对应三种完全不同的机制。之所以会混是因为形式上确实相似函数名都差不多参数表也常常一样光看代码很难一眼分辨。区分的关键在两个维度——发生在什么范围以及什么时候决定调用哪个。函数重载发生在同一个作用域里同名但参数个数或类型不同编译期就根据实参定死了调哪个。虚函数重写发生在基类和派生类之间函数签名完全相同且基类那个标了 virtual运行期才根据对象的真实类型决定。隐藏则是派生类定义了和基类同名的函数不管参数一不一样把基类那一堆同名函数全遮住了想调用基类版本必须写Base::func()。一句话记法重载看参数、重写看 virtual、隐藏看名字被挡住。5.2 拷贝构造函数与赋值运算符为什么输出里有一串 112444易混点在于两者都是「拿一个对象去影响另一个对象」但触发的时机完全不同。之所以会混是因为很多场景下你根本分不清是「创建」还是「赋值」。区分方法很简单看左边那个对象是不是刚刚诞生的。MyClass obj3(obj1);是拿 obj1 初始化一个全新的 obj3走拷贝构造函数obj3 obj1;是 obj3 已经存在了把 obj1 的内容灌进去走赋值运算符。真题里有一道问输出结果的题类里给构造函数、拷贝构造函数、赋值运算符、析构函数各加了一句输出主函数里写了MyClass obj1(1), obj2(2), obj3(obj1);。obj1 和 obj2 各调一次普通构造输出两个 1obj3 由 obj1 拷贝而来调拷贝构造输出 2函数结束时三个对象依次析构输出三个 4合起来就是 112444。这题不需要算任何数值只要数清楚每个对象走了哪条路径。5.3 静态成员、静态局部变量、静态成员函数static 的三副面孔易混点在于同一个 static 关键字放在三个地方含义完全不同。之所以会混是因为它们共享「静态」这个模糊印象。区分要抓各自的作用对象静态局部变量作用在函数内部的一个变量上特点是初始化只做一次、函数退出后依然保留静态数据成员作用在类上特点是所有对象共享同一份必须在类外定义一次静态成员函数作用在函数上特点是没有隐含的 this 指针因此不能直接访问非静态成员调用时可以直接用「类名::函数名」。第三点最容易考。真题给一堆函数让你判断哪个带 this 指针答案永远是那个普通的非静态成员函数——静态成员函数、全局函数、带 static 的普通函数都没有 this。5.4 传值、传引用、传指针对象切片是怎么发生的易混点在于三种传参方式在函数体里看起来都能用但背后发生的事情差别巨大。之所以会混是因为写代码时它们常常「看起来都对」程序也能跑只是结果不对。区分的关键在有没有发生拷贝。传值时形参是实参的一份拷贝如果形参类型是基类而实参是派生类对象拷贝时只拷基类那部分派生类独有的成员被裁掉了这就是对象切片切片之后虚函数也失效因为形参是实打实的基类对象不再是「指向派生类的基类指针」。传引用和传指针都不拷贝传进去的还是原来那个对象多态才能正常工作。真题里有一组函数一个收基类指针、一个收基类引用、一个收基类对象三个都传派生类实例进去输出结果各不相同——收对象那个永远是基类的版本。这个坑实际写代码时也很常见尤其是把对象塞进容器容器会拷贝时多态悄无声息地就没了属于典型的血泪经验。5.5 抽象类、空类、纯虚函数抽象不等于空易混点在于抽象类听起来像「什么都没有的类」其实它恰恰是「规定了什么必须有、但自己没实现」的类。之所以会混是因为「抽象」这个词在日常语境里接近「模糊、不具体」容易联想到空。区分方法很直接抽象类的判定标准只有一个——类中至少有一个纯虚函数形如virtual void f() 0;带有纯虚函数的类不能实例化对象只能作为基类被继承派生类必须把所有纯虚函数都实现掉才能创建对象。空类则是什么成员都没有的类编译器照样会为它生成默认构造函数、拷贝构造函数、析构函数和赋值运算符它是可以正常实例化的。顺带一个常错点抽象类里不一定只有纯虚函数它可以有普通成员函数、有数据成员、甚至有已经实现了的虚函数只是不能有「没实现的非纯虚函数」而已。6. 什么时候该刷真题、什么时候刷了也白刷一份按阶段的判断真题这份东西用对了是地图用错了就是一堆过期的路标。我的判断标准很简单它擅长告诉你「这门考试怎么问」不擅长告诉你「这门语言怎么用」。具体来说它最适合三个场景。一是你已经学过一遍语法但知识点是散的需要有人帮你把它们串成一张表——真题按知识点排一遍哪些是高频、哪些只是偶尔露脸一目了然。二是你的选择题正确率卡在某个区间上不去错的总是那几类这时候把错题按知识点归类比再刷十套新题有用得多。三是你需要在短时间内熟悉出题口径比如填空题的答案通常填的是关键字还是表达式、代码题的空白处一般挖在哪个位置这些只能靠看真题摸。它不适合的场景也很明确。完全没学过 C 的人拿着真题背答案等于背字典学说话遇到换个说法就废。想靠它提升工程能力的人更该换条路——真题里没有真实的项目结构、没有构建流程、没有调试过程它考的是「你会不会判断」不是「你会不会做」。你现在的处境真题的用处建议怎么用完全没接触过 C很小先过一遍语法别急着上手学过一遍做题错一半很大按知识点分类做做完归类错因已经能稳定过线中等只做错题和不确定的题别重复劳动想提升实际开发能力没有去写能跑起来的小项目还有一点这些卷子的年份跨度不小早期的题和近年的题在风格上有变化早年更偏语法细节的死记近年稍微往「理解规则」上靠了一点。同一份资料里混着不同年代的题目看的时候要留个心眼别把老语境下的结论硬套到新标准上比如前面提到的 auto 的含义变化就是最典型的例子。我自己的教训是最早拿到这类资料时图快从头到尾当小说一样读了一遍看完觉得「都懂」一做题才发现只是「都眼熟」。从那以后我每次拿到一份真题都先按知识点把它拆成几堆一堆一堆地过过完立刻合上答案自己复述一遍规则说不清楚的再翻回去看。这份资料把多年真题和答案攒在一起正好适合这么拆着用需要的话拿去对着知识点过一遍就好。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?