简介:这份《全国计算机二级C++等级考试真题及答案》PDF面向备考全国计算机二级C++科目的考生,也可作为C++基础与进阶知识点的自测清单,帮助读者熟悉笔试选择题的命题方式与常见陷阱。文件包共1个PDF,大小约2.2MB,内容以真题题干、参考答案及知识点解析为主,便于打印或分屏对照复习。已有1809人学习,说明其在同类备考资料中具有一定参考热度。资料围绕变量存储类型、类与对象、继承、运算符重载、模板、输入输出流、关键字、程序控制结构和指针等考点展开,读者可通过20道真题检验薄弱环节,并借助答案解析理解默认存储类型、派生类访问权限、函数重载与this指针等易错细节,适合冲刺阶段查漏补缺。
1. 先看分值分布:二级 C++ 选择题和填空题的拿分顺序
计算机二级 C++ 考试里,选择题 70 分、填空题 30 分,很多人一上来就狂背语法点,真正拉开差距的却是几个固定套路:默认构造函数什么时候生成、派生类成员函数能不能碰基类私有成员、构造析构谁先谁后、运算符重载写成成员函数到底要几个参数。送分题不少,但答案标注里常有 OCR 错位,光背 ABCD 容易翻车。判断标准只有一条:先看“谁在访问”“谁先构造”“是不是成员函数”,再套规则。这份按年份成套的真题适合已经学过一遍语法、想用题目反查薄弱点的人;想拿计算机二级 C++ 证书,先把选择题里的类与对象、继承多态、运算符重载三块吃透,填空题的指针、循环、栈操作才不会临场发懵。
2. 高频考点:存储类型、关键字、数组和指针的送分题怎么拿稳
2.1 存储类型与默认 auto:2007 年第 11 题拆解
变量存储类型题看着像背关键字,实际考的是“默认值”和“生命周期”。2007 年第 11 题问:在函数中可以用 auto、extern、register、static 说明变量存储类型,如果不说明,默认存储类型是什么。按老教材的 C++ 语法,函数内局部变量默认是 auto,答案是 A。这里要提醒一句,现代 C++ 里 auto 已经变成类型推导关键字,但二级 C++ 的题库里大量老题仍按“存储类型说明符”考,答题时看题干语境,不要拿新标准去硬套老题。
四个关键字的分工要能一口说出来:auto 是自动变量,进入函数块时建立、离开时释放;extern 是声明外部变量,告诉编译器“这个变量在别处定义”;register 是建议把变量放进寄存器,注意只是建议,编译器可以不理;static 修饰局部变量时,变量只初始化一次,函数退出后值还在,下次调用接着用。考试最爱在 static 局部变量上设坑,因为它把“作用域”和“生命周期”拆开了:作用域仍在函数内,生命周期却延长到程序结束。
看一段静态局部变量的代码:
#include <iostream> using namespace std; int f() { static int s = 6; // 只初始化一次,后续调用不再执行这行 s -= 2; // 每次调用都执行 return s; } int main() { cout << f(); // 第一次:6 - 2 = 4 cout << f(); // 第二次:4 - 2 = 2 }这段代码的输出是 42。关键参数是 static int s = 6 里的 6,它只在第一次调用 f 时赋值;s -= 2 每次调用都会执行。如果题干问“第二次执行 f 的返回值”,答案是 2;如果问“第一次”,答案是 4。2007 年填空题里有一道类似题,OCR 把“=6”识别成“-6”,看到这种题干先还原成“static int s = 6”再算,别被错字带偏。答题时先问自己:这个变量是普通局部变量还是静态局部变量?普通局部变量每次重新初始化,静态局部变量只初始化一次。
2.2 关键字、标识符、类型等价与 const:容易背混的清单
关键字题属于“记住就得分,记混就丢分”。2007 年第 17 题问以下哪个不是 C++ 关键字,选项 friend、namespace、continue、byte,答案是 byte。friend 是友元,namespace 是命名空间,continue 是循环继续,byte 在当时的 C++ 标准里不是关键字。2021 年第 17 题问哪个不能作为标识符,9stars 以数字开头,不能做标识符;user、WHILE、_lvar 都可以。标识符规则就三条:不能数字开头,不能是关键字,只能由字母、数字、下划线组成。考试不会绕太深,看到数字开头直接排除。
类型等价题考“缩写”。2007 年第 18 题里,signed short int 和 short 含义一样,答案是 B。把常见等价关系列成表更稳:
| 写法 | 等价写法 | 注意 |
|---|---|---|
| signed short int | short | signed 可省略 |
| unsigned short int | unsigned short | int 可省略 |
| unsigned long int | unsigned long | 不等于 long |
| long int | long | 不等于 int |
| signed int | int | signed 可省略 |
const 题要看清 const 修饰谁。2021 年第 18 题问哪条语句错误,const int a; 没有初始化,错误;const int a = 10; 正确;const int* point = 0; 是指向 const int 的指针,可以指向空;const int* point = new int(10); 也合法。记住:const 变量定义时必须初始化;const 在 * 左边表示“指向的内容不能改”,const 在 * 右边表示“指针本身不能改”。字符串数组题还要算结尾的 '\0',比如 "Hello-World!" 有 12 个可见字符,数组至少要 13 个位置,写成 char myString[12] 就放不下结尾空字符。
2007 年第 20 题问 int *p = &k 的等价语句,答案是 int *p; p = &k;。这里容易错写成 int *p; *p = &k;,后者是把地址赋给 p 指向的变量,类型都不对。答题时把“定义指针”和“给指针赋值”分成两步看:int *p 定义指针变量,p = &k 让 p 保存 k 的地址;*p = &k 是拿地址去改 int 变量,编译就过不了。
2.3 真题演练:指针定义、三目运算和嵌套循环怎么推
先看三目运算符。2007 年填空题:int a = 3, b = 2, c = 1; 表达式 a < b ? a : b 的值是多少。a < b 是 3 < 2,假,取冒号后面的 b,结果是 2。三目运算符的套路是先算条件,再按真假选分支,不要被 c 干扰,c 根本没参与。
再看嵌套循环。2021 年第 19 题:
#include <iostream> using namespace std; int main() { int sum; for (int i = 0; i < 6; i += 3) { // i 取 0、3 sum = i; // 每次外层循环都重置 sum for (int j = i; j < 6; j++) // 内层把 i 到 5 累加 sum += j; } cout << sum << endl; return 0; }i = 0 时,sum = 0,内层 j 从 0 到 5,sum 变成 15;i = 3 时,sum 被重置为 3,内层 j 从 3 到 5,sum 变成 3 + 3 + 4 + 5 = 15。输出 15。这里最容易被“sum 没有初始化”吓到,但外层第一次就给 sum 赋值了,真正要看的是 sum = i 这行有没有重置。做循环题不要心算到底,画一张 i、j、sum 的小表,逐轮填,比盯着代码硬想稳。
数组移动填空也常考。2021 年填空题给数组 a、起始下标 x1、目标下标 x2、长度 k,要求把从 x1 开始的 k 个元素移到从 x2 开始的 k 个元素。因为题目给的是 0 <= x1 < x2 < n,目标区在源区后面,正向复制不会覆盖还没读的源元素,核心语句就是:
for (i = 0; i < k; i++) { a[x2 + i] = a[x1 + i]; // 从前往后复制,x1 < x2 时安全 }参数含义:i 是偏移量,x1 + i 是源下标,x2 + i 是目标下标,k 是要搬的元素个数。如果题目改成 x1 > x2,正向复制会覆盖源数据,就要从后往前复制。填空题看到“移动数组”,先判断源区和目标区谁在前,再决定循环方向。
3. 类与对象:构造、拷贝、析构、静态成员和 this 指针的答题模板
3.1 默认构造、拷贝构造、析构的生成规则与调用次数
类与对象是二级 C++ 选择题的重灾区。2007 年第 12 题问类定义中哪个表达错误,选项里说“如果没有定义缺省的构造函数,编译器将一定生成一个缺省的构造函数”,这句话错在“一定”。编译器生成默认构造是有条件的:类里没有用户声明的任何构造函数,它才生成一个无参的默认构造;只要用户自己写了带参构造,编译器就不再自动生成默认构造。2021 年第 12 题也考这个点,题干问自动生成缺省构造函数的条件,核心判断是“用户有没有声明构造函数”,不是“有没有有参构造”这么简单,因为用户写了无参构造同样会阻止编译器生成。
拷贝构造也一样。2007 年第 12 题的正确选项说:如果没有定义拷贝构造函数,编译器将生成一个拷贝构造函数。这个说法在通常情况下成立,但如果类里有指针成员,编译器生成的只是浅拷贝,考试常拿它引出“深拷贝”问题。析构函数每个类只有一个,不能重载,不能带参数;对象生命周期结束时自动调用。2021 年第 22 题定义 Sample temp[2], *pTemp[2]; 临时对象数组有两个元素,构造两次,析构两次,输出两个星号,答案是 2。指针数组 pTemp 只是两个指针,不构造 Sample 对象,别把它算进去。
2021 年第 27 题是拷贝构造和析构的输出题:
#include <iostream> using namespace std; class MyClass { public: MyClass(int i = 0) { cout << 1; } // 普通构造 MyClass(const MyClass& x) { cout << 2; } // 拷贝构造 MyClass& operator=(const MyClass& x) { // 赋值运算符 cout << 3; return *this; } ~MyClass() { cout << 4; } // 析构 }; int main() { MyClass obj1(1), obj2(2), obj3(obj1); // obj1、obj2 普通构造,obj3 拷贝构造 return 0; }obj1(1) 调用普通构造输出 1,obj2(2) 输出 1,obj3(obj1) 用同类型对象初始化,调用拷贝构造输出 2,所以构造阶段输出 112。main 结束时三个对象逆序析构,每个输出 4,共 444,总输出 112444。参数说明:obj3(obj1) 是“初始化”,一定调拷贝构造;如果写成 obj3 = obj1,那才调赋值运算符 operator=,输出 3。考试把拷贝构造和赋值放一起,就是看你分不分“初始化”和“赋值”。
3.2 静态成员、静态函数和 this 指针:计数题为什么总考
静态成员属于类,不属于某个对象。2007 年第 27 题的 MyClass 里有一个 static int count,构造函数 ++count,析构函数 --count,主函数先定义 obj,再 new 一个对象,再 delete。obj 构造后 count = 1,输出 1;new 后 count = 2,输出 2;delete 后 count = 1,输出 1,所以输出 121。这类题的关键是:每构造一个对象 count 加一,每析构一个对象 count 减一,new 出来的对象要 delete 才析构。
class MyClass { public: MyClass() { ++count; } // 每构造一个对象计数加一 ~MyClass() { --count; } // 每析构一个对象计数减一 static int getCount() { return count; } // 静态成员函数没有 this private: static int count; // 静态数据成员属于类 }; int MyClass::count = 0; // 类外定义并初始化静态成员函数不能直接访问非静态成员,因为它没有 this 指针;非静态成员函数有隐含的 this 指针。2007 年第 22 题问头文件中哪个函数具有隐含 this 指针,f3 是类 MA 的普通成员函数,有 this;f1、f2 是全局函数,没有;f4 是静态成员函数,也没有。答题时看两点:是不是类的成员函数,是不是 static。两个条件都满足“是成员、非静态”,才有 this。
3.3 真题演练:const 对象、访问控制和构造输出怎么答
2021 年第 28 题给了一个 const MyClass obj1(10); 主函数里 obj1.Print() 正确,obj1.Set(20) 错误。原因很直接:const 对象只能调用 const 成员函数,Set 没有 const 修饰,Print 有 const 修饰。题目问错误语句,选 obj1.Set(20)。这里不要和“const 成员函数内部不能修改成员”搞混:const 成员函数保证不修改对象状态,所以 const 对象敢调它;非 const 成员函数可能修改对象,const 对象不能调。
class MyClass { public: MyClass(int x) : val(x) {} void Set(int x) { val = x; } // 非 const 成员函数 void Print() const { cout << val; } // const 成员函数 private: int val; }; int main() { const MyClass obj1(10); obj1.Print(); // 对:const 对象调用 const 成员函数 // obj1.Set(20); // 错:const 对象不能调用非 const 成员函数 return 0; }访问控制题先分清“谁在访问”。2021 年第 29 题问 public: 后声明的成员访问权限,答案是公有。2007 年第 31 题里 obj 是类 D 的对象,obj.fun2() 能调用,因为 fun2 在 B 的 public 区,D 用 public 继承,fun2 在 D 中仍是 public;obj.fun1() 不行,fun1 在 B 中默认 private;obj.var1 不行,var1 是 protected;obj.fun3 不行,fun3 在 D 中是 protected。答这类题不要凭感觉,先画三列:成员在基类里的权限、继承方式、在派生类里的权限,再判断对象能不能访问。
4. 继承、访问权限与多态:public 继承表、构造析构顺序、虚函数输出题
4.1 继承方式与访问权限:三行表解决大部分选择题
继承访问权限题看似选项多,实际就一张表。public 继承时,基类 public 成员在派生类中仍是 public,protected 仍是 protected,private 成员在派生类中不可直接访问;protected 继承时,基类 public 和 protected 成员在派生类中都变成 protected;private 继承时,基类 public 和 protected 成员在派生类中都变成 private。基类 private 成员无论哪种继承方式,派生类成员函数都不能直接访问。
2007 年第 13 题问 public 继承时基类 protected 和 public 成员在派生类中的访问类型,按表应该是 protected 和 public。如果手头答案标注成 protected 和 protected,先回到表上核对,别把错答案背进去。2021 年第 30 题问公有继承的派生类成员函数可以直接访问基类某个成员,说明该成员权限是什么,答案是公有或保护,因为私有成员派生类成员函数碰不到。2021 年第 31 题问不写继承方式时默认是什么,答案是私有继承。这个点容易忘,class 默认 private,struct 默认 public,考试按 class 考。
2021 年第 13 题容易踩坑:派生类成员函数试图调用基类 void f() 无法编译,说明什么。选项里有“f() 是基类私有成员”和“派生类继承方式为私有”。正确答案应落在“f() 是基类私有成员”。私有继承只影响派生类对象和进一步派生,派生类成员函数仍然可以访问基类的 public 和 protected 成员;只有基类私有成员,派生类成员函数才不能直接访问。答题时把“派生类成员函数访问”和“派生类对象访问”分开,前者能看基类 public/protected,后者只能看 public。
| 基类成员权限 | public 继承后 | protected 继承后 | private 继承后 |
|---|---|---|---|
| public | public | protected | private |
| protected | protected | protected | private |
| private | 不可直接访问 | 不可直接访问 | 不可直接访问 |
4.2 构造与析构顺序:基类→成员对象→派生类,析构反过来
构造顺序是考试里的“送分送命题”。规则固定:先构造基类,再按成员声明顺序构造成员对象,最后执行派生类构造函数体;析构顺序完全反过来,先执行派生类析构函数体,再逆序析构成员对象,最后析构基类。2007 年第 30 题 Derived d(1); 基类 Base 的默认构造先输出 0,派生类构造体再输出 1,结果是 01。2021 年第 32 题问有成员对象的派生类对象构造顺序,答案是基类、成员对象类、派生类。
初始化列表的书写顺序不等于执行顺序。2007 年第 29 题 Whole(int x, int y, int z = 0) : p2(x), p1(y), val(z) 里,初始化列表先写 p2 再写 p1,但成员声明是 Part p1, p2;,所以先构造 p1,用 y;再构造 p2,用 x;最后 val。答题时不要顺着冒号后面读,要回到类定义的成员声明顺序。
class Part { public: Part(int x = 0) : val(x) { cout << val; } // 构造输出 val ~Part() { cout << val; } // 析构输出 val private: int val; }; class Whole { public: 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; };构造阶段先 p1 输出 y,再 p2 输出 x,再 Whole 构造体输出 z;析构阶段先 Whole 析构体输出 val,再逆序析构 p2 输出 x,再 p1 输出 y。参数说明:初始化列表里的 p2(x), p1(y) 只是把实参传进去,真正谁先谁后看 p1、p2 在类里的声明顺序。考试只要出现成员对象,先找声明顺序,再写输出。
4.3 真题演练:虚函数三个调用方式输出怎么拆
虚函数题考“看指针/引用指向的实际对象,还是看形参类型”。2007 年第 35 题给 fun1(B* ptr)、fun2(B& ref)、fun3(B b),主函数里 B b, *p = new D; D d; 然后 fun1(p)、fun2(b)、fun3(d)。fun1 传的是指向 D 的基类指针,show 是虚函数,动态绑定到 D,输出 D;fun2 传的是 B 对象 b 的引用,实际对象就是 B,输出 b;fun3 传值,D 对象被切片成 B,虚函数不生效,输出 b,总输出 Dbb。这里的关键是 fun3 的参数是 B b,不是 B& b,传值会发生对象切片。
class B { public: virtual void show() { cout << "b"; } // 虚函数 }; class D : public B { public: void show() { cout << "D"; } // 覆盖虚函数 }; void fun1(B* p) { p->show(); } // 指针调用:看实际对象 void fun2(B& r) { r.show(); } // 引用调用:看实际对象 void fun3(B b) { b.show(); } // 传值:对象切片,看形参类型如果主函数写成 B b; D d; fun1(&d); fun2(d); fun3(d);,输出是 DDb;如果写成 2007 年第 35 题那样 fun2(b),第二个输出就是 b。答题时先看参数形式:B*、B& 保留多态,B 传值切断多态。2007 年第 32 题里 output 不是虚函数,print 是虚函数,通过 Base* 调用 output 看指针类型输出 1,调用 print 看实际对象输出 D,结果应为 1D;如果答案标成 2D,是把非虚函数也当成动态绑定了,这个坑要记住。
2021 年第 23 题问哪个与运行时多态无关,答案是重载函数。重载是编译期根据参数选函数,虚函数、指针、引用才和运行时多态有关。2021 年第 35 题问抽象类一定包含什么,答案是纯虚函数。抽象类不能实例化,通常至少有一个纯虚函数;有虚函数不一定是抽象类,有纯虚函数才是。面试里问虚函数也常拿这种代码,答题口径一样:虚函数看对象,非虚函数看指针/引用类型,传值会切片。
5. 易错点:5 类失分题与纠正模板
5.1 易错点一:默认构造函数“一定生成”
错在哪:看到“没有定义缺省构造函数,编译器一定生成”就选对。2007 年第 12 题 B 选项、2021 年第 12 题都在这里设坑。
为什么错:编译器生成默认构造的前提是类里没有用户声明的任何构造函数。用户写了带参构造,编译器不再生成无参默认构造;用户写了无参构造,也不会再“自动生成”一个。拷贝构造同理,用户声明了拷贝构造,编译器不再生成默认拷贝构造。
怎么答对:先数类里有没有构造函数声明,再看题干问的是默认构造还是拷贝构造。答题模板:用户未声明任何构造函数 → 编译器生成默认构造;用户声明了构造函数 → 不再自动生成默认构造;需要无参构造就自己写。2021 年第 12 题如果题干写“没有定义任何有参构造函数”,还要补一句“有没有无参构造”,不能直接选。
5.2 易错点二:派生类成员函数能访问基类私有成员
错在哪:把“派生类对象不能访问基类 protected”和“派生类成员函数不能访问基类 private”混在一起,看到无法编译就选继承方式私有。2021 年第 13 题、2007 年第 23 题都考这个。
为什么错:访问权限看“谁在访问”。派生类成员函数在类内部,能访问基类 public 和 protected,不能访问 private;派生类对象在类外部,只能访问 public。继承方式改变的是基类成员在派生类中的权限,不会把基类 private 变成可访问。
怎么答对:先写三行——基类成员权限、继承方式、访问者身份。派生类成员函数访问失败,优先怀疑基类 private;派生类对象访问失败,再查继承方式和成员权限。2007 年第 23 题问派生类成员函数不能访问基类什么,答案是私有成员;2021 年第 13 题按这个口径应落到基类私有成员。
5.3 易错点三:运算符重载参数个数和返回值
错在哪:成员函数重载二元运算符时写两个参数,或者以为返回值类型不同也能重载。2007 年第 14、24、33 题,2021 年第 14、24、33 题都在考。
为什么错:成员函数有隐含 this 指针,左操作数就是当前对象,所以二元成员函数只写一个参数,一元成员函数不写参数;非成员函数没有 this,二元运算符写两个参数,一元写一个参数。返回值类型不参与重载区分,参数列表才参与。不能重载的运算符包括 .、::、?:、sizeof 等;重载运算符的参数不能设默认值。
怎么答对:先看函数是不是成员,再数操作数。成员二元:一个参数;成员一元:零个参数;非成员二元:两个参数;非成员一元:一个参数。2021 年第 24 题 Value operator-(Value, Value) 有两个参数,肯定不是类 Value 的成员函数;2021 年第 33 题 a >= b 用非成员重载,等价 operator>=(a, b),不是 a.operator>=(b)。表格记牢:
| 重载形式 | 一元运算符参数个数 | 二元运算符参数个数 |
|---|---|---|
| 成员函数 | 0 | 1 |
| 非成员函数 | 1 | 2 |
5.4 易错点四:构造/析构输出顺序
错在哪:按初始化列表书写顺序写输出,或者把拷贝构造和赋值运算符混了。2007 年第 29、30 题,2021 年第 27、32 题都是这个套路。
为什么错:成员对象构造顺序看类内声明顺序,不看初始化列表;析构顺序和构造相反;拷贝构造发生在“用同类型对象初始化新对象”,赋值发生在“已有对象之间赋值”。2021 年第 27 题 obj3(obj1) 是拷贝构造,输出 2;如果写成 obj3 = obj1,才输出 3。
怎么答对:画三层:基类、成员对象、派生类。构造从上到下,析构从下到上;成员对象按声明顺序排,不按冒号后排。遇到输出题先标每个对象什么时候构造、什么时候析构,再按顺序抄输出。血泪经验是:别一上来就看选项,先写自己的输出串,再对选项。
5.5 易错点五:const 对象、const 指针和文件流默认打开方式
错在哪:const 对象调用非 const 成员函数;const int a; 不初始化;分不清 const int* 和 int* const;ofstream 默认打开方式选错。2021 年第 18、28、34 题,
本文还有配套的精品资源,点击获取