简介这份《全国计算机二级C等级考试真题及答案》PDF面向准备全国计算机二级C科目笔试的考生也可供C基础学习者自测语法与概念掌握情况。内容围绕20道真题展开覆盖变量存储类型与auto、extern、register、static的区别类与对象、构造与拷贝构造、public继承下成员访问权限以及运算符重载、函数模板与类模板、输入输出流、friend与namespace等关键字、switch与if控制结构、指针定义与取地址等高频考点。包内共1个PDF文件压缩包约2.2MB篇幅紧凑便于在电脑或移动设备上直接翻阅。已有1809人学习适合按题对照答案梳理知识盲区也可用于考前快速回顾二级C选择题的常见设问方式与判断依据。1. 从 70 分选择题倒推计算机二级 C 笔试的六条主线计算机二级 C 笔试的真题卷表面是几十道互不相干的选择题加五道填空题拆开之后只有六条主线类与对象、继承与多态、运算符重载、模板、流式输入输出、语言基础与关键字。两千零七年的九月卷和二零二一年的九月卷相隔十四年考点重合度高得惊人——构造函数什么时候由编译器补、public 继承之后成员权限怎么变、虚函数在什么条件下才真正生效换个问法又考一遍。这张地图适合两类人正在系统学 C、想把零散语法串成体系的人以及刷真题总在同一类题上丢分、需要定位薄弱模块的人。真题的价值从来不在答案字母而在它把每个知识点的边界问得很刁。2. 类与对象构造函数补位、this 指针与 const 成员函数类与对象这一块真题考的不是「会不会写类」而是「编译器在你看不见的地方做了什么」。构造函数、拷贝构造、析构函数谁是自动生成的、谁被用户定义抑制、this 指针长在谁身上、const 对象能调哪些函数这几个问题在选择题里反复出现答错通常不是因为不会写代码而是没把「隐式生成」的规则记准。2.1 编译器什么时候替你补构造函数先把规则收敛成一张表。编译器隐式生成默认构造函数的条件只有一个类里没有任何用户声明的构造函数。注意是「任何」不是「无参的」。只要写了一个带参数的构造函数默认构造就不再生成MyClass obj;这种写法直接编译失败。拷贝构造的抑制条件独立用户声明了拷贝构造才不生成拷贝构造但声明普通构造不会抑制拷贝构造。用户已声明的构造函数默认构造拷贝构造什么都没写隐式生成隐式生成只写带参构造不生成隐式生成只写拷贝构造不生成用户定义带参构造加拷贝构造不生成用户定义真题里那道「如果没有定义缺省的构造函数编译器将一定生成一个缺省的构造函数」就是拿「没有定义缺省构造」偷换了「没有定义任何构造函数」一字之差选项就错了。另一道问「编译器自动生成缺省构造函数的条件」正确表述是「该类没有定义任何构造函数」。这两题放在一起看考的是同一个边界。提示带默认参数的构造MyClass(int i 0)能用MyClass obj;编译但它是用户写的构造不是编译器生成的默认构造。这个区别在考场上经常被混淆。2.2 拷贝构造与赋值运算符初始化不是赋值拷贝构造和operator是两套机制触发时机完全不同。用已有对象初始化新对象走拷贝构造两个已经存在的对象之间才走赋值运算符。真题里给三个对象obj1(1), obj2(2), obj3(obj1)输出里出现一次2就说明拷贝构造被调用而operator里的输出一次都不该出现。#include iostream using namespace std; class MyClass { public: MyClass(int i 0) { cout 1; } // 普通构造带默认参数 MyClass(const MyClass x) { cout 2; } // 拷贝构造参数是 const 引用 MyClass operator(const MyClass x) { // 拷贝赋值只用于已存在对象 cout 3; return *this; } ~MyClass() { cout 4; } // 析构 }; int main() { MyClass obj1(1), obj2(2), obj3(obj1); // 1 1 2obj3 走拷贝构造 return 0; // 三个对象析构输出 4 4 4 }这段代码的完整输出是112444。参数说明MyClass(int i 0)的默认值让无参写法也能编译拷贝构造参数必须是引用传值会无限递归operator返回MyClass是为了支持连续赋值。易错点在最后一行——三个栈上对象在main返回时依次析构顺序与构造相反所以是三个4而不是两个。2.3 this 指针只属于非静态成员函数this 指针是编译器给非静态成员函数偷偷加的隐藏参数指向调用该函数的对象。由此可以推出一串结论全局函数没有 this静态成员函数没有 this普通成员函数有 this。真题把四个函数摆在一起——普通全局函数、静态全局函数、类的普通成员函数、类的静态成员函数——问谁有隐含 this答案只有第三个。没有 this 带来的连锁限制更值得记静态成员函数不能声明为 constconst 修饰的就是 this 指向的对象不能直接访问非静态成员也不能调用非静态成员函数。反过来静态成员函数只能碰静态成员。这条规则在改错题和填空题里都可能出现写代码时编译器会直接报错但选择题只给你文字描述就得靠推理。class Counter { public: static int getTotal() { return total; } // 可以只访问静态成员 // static int getValue() { return value; } // 错误静态函数没有 this private: int value; static int total; }; int Counter::total 0; // 静态成员必须在类外定义静态数据成员在类内只是声明定义和初始化要放到类外且不带static关键字。漏掉类外定义链接阶段会报未定义符号。2.4 常对象只能调用 const 成员函数const 对象的所有成员在逻辑上都是只读的因此只能调用带 const 修饰的成员函数。真题里const MyClass obj1(10)调用非 const 的Set函数语句直接编译失败同一个对象调用Print() const则没有问题。普通对象两种函数都能调。对象类型普通成员函数const 成员函数普通对象可调用可调用const 对象不可调用可调用const 成员函数内部也不能修改非 mutable 成员这一点和「const 对象只能调 const 函数」是同一枚硬币的两面。判断这类题有个快办法先看对象是不是 const再看函数名后面有没有 const两者不匹配就是错。2.5 静态数据成员与构造析构计数题静态成员属于类而不属于对象所有对象共享一份。真题用构造函数count、析构函数--count来统计存活对象数问三次输出的结果。分析路径很固定定义一个栈对象count 变 1new一个堆对象count 变 2delete掉堆对象count 回 1输出就是121。class Tracked { public: Tracked() { count; } ~Tracked() { --count; } static int getCount() { return count; } private: static int count; }; int Tracked::count 0; int main() { Tracked a; // count 1 cout Tracked::getCount(); // 1 Tracked* p new Tracked; // count 2 cout Tracked::getCount(); // 2 delete p; // count 1 cout Tracked::getCount(); // 1 }这类题的坑有两个一是new出来的对象不析构就不会减计数忘了delete就会多算二是静态成员函数可以直接用类名调用不需要对象。把这两点记住计数题基本不会翻车。3. 继承与多态3×3 权限矩阵、构造顺序与虚函数生效条件继承部分的题量不大但很集中几乎都落在三个点上不同继承方式下成员权限怎么变、构造析构按什么顺序执行、虚函数在什么条件下才真正绑定到派生类。前两个是背了就能得分的规则题第三个是理解题也是丢分重灾区。3.1 三种继承方式下的访问权限矩阵基类成员在派生类里的访问级别由「基类中的原权限」和「继承方式」共同决定取两者中更严格的那个。private 成员在任何继承方式下都不可直接访问但空间确实被继承了。基类成员原权限public 继承后protected 继承后private 继承后publicpublicprotectedprivateprotectedprotectedprotectedprivateprivate不可访问不可访问不可访问把这张表背下来选择题就是查表。public 继承下 protected 仍是 protected、public 仍是 public这条最常考也最容易被网上流传的错误答案带偏——同一个知识点在不同版本的解析里给出过互相矛盾的选项字母复习时以矩阵为准不要盲信答案页。派生类成员函数能访问基类的 public 和 protected不能访问 private。反过来通过派生类对象在类外只能访问 public 成员。真题里给一个 public 继承的派生类对象逐个试obj.fun1()、obj.var1、obj.fun2()、obj.fun3()只有基类里的 public 函数成立其余要么是 private要么是 protected。3.2 构造与析构的执行顺序建立一个有成员对象的派生类对象构造顺序固定为基类构造 → 成员对象构造按声明顺序→ 派生类自身构造。析构顺序完全相反。这里最容易错的是「按初始化列表的书写顺序构造」——初始化列表只决定传什么参数不决定顺序顺序只看成员在类里的声明位置。class Part { public: Part(int x 0) : val(x) { cout val; } ~Part() { cout 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; }; // Whole obj(1, 2, 3); // 构造p1(2) 输出 2p2(1) 输出 1Whole 输出 3 → 213 // 析构Whole 输出 3p2 输出 1p1 输出 2 → 312 // 合计 213312初始化列表写的是p2(x), p1(y)但 p1 先声明就先构造输出2在前。参数x1, y2, z3分别传给 p2、p1、val看代码时别把实参顺序和成员顺序搞混。另一个常考变体是对象数组与指针数组的区别Sample temp[2]构造两个对象、析构两次Sample* pTemp[2]只是两个指针不构造任何对象。基类构造先于派生类这点在无参构造里也能考。派生类对象创建时没写初始化列表基类就走默认构造先输出基类里的内容再输出派生类构造里的内容。3.3 虚函数运行时多态的三个前提虚函数要真正生效三个条件缺一不可通过基类指针或引用调用、函数在基类中声明为 virtual、派生类中的同名函数签名一致。少一个调用的就是基类版本。class B { public: virtual void show() { cout b; } }; class D : public B { public: void show() override { cout D; } // override 让签名错误在编译期暴露 }; void fun1(B* ptr) { ptr-show(); } // 指针调用动态绑定 void fun2(B ref) { ref.show(); } // 引用调用动态绑定 void fun3(B b) { b.show(); } // 传值对象切片静态绑定fun3是这组题的核心陷阱。派生类对象按值传给基类形参只拷贝基类那部分派生类新增的成员和虚函数表指针都丢了函数里再怎么调都只能调基类版本。所以「多态失效」不一定是因为没写 virtual传值同样会让它失效。还有一类题把「多态」和「重载」放在一起问与运行时多态无关的是什么答案是重载函数。重载在编译期就按参数表定死了调用哪个版本虚函数才拖到运行期。指针和引用都是运行时多态的必要载体两者不能选。实参本身是谁也要看清楚。fun2(b)里的b是基类对象而不是派生类对象即使形参是引用传进去的仍然是基类版本输出b而不是D。这类题考的不是多态机制是读代码的仔细程度。3.4 抽象类与纯虚函数含纯虚函数的类是抽象类不能实例化。纯虚函数的语法是声明末尾加 0不是 0扫描版真题里这个等号经常被识别成两个看到时按语法常识还原。class Shape { public: virtual void draw() const 0; // 纯虚函数Shape 成为抽象类 virtual ~Shape() {} // 基类析构应为虚避免delete时只析构基类 }; class Circle : public Shape { public: void draw() const override { /* 具体实现 */ } };派生类必须实现全部纯虚函数才能实例化实现一个漏一个仍然是抽象类。抽象类可以定义普通成员函数和数据成员也可以有构造函数供派生类调用它只是不能直接创建对象。3.5 默认继承方式与访问边界class 定义的派生类不写继承方式默认是 private 继承struct 默认是 public 继承。这是 C 里 class 和 struct 少数几个实质区别之一选择题考过原题。private 继承会把基类的 public 和 protected 成员都降为 private外部完全看不到所以「默认继承」这个空不填对后面的权限判断全错。友元关系不被继承基类的友元不能访问派生类的私有成员。静态成员可以被继承但所有派生类共享同一份。这几条零散规则在综合题里偶尔出现单独背一遍比考场上现推稳。4. 运算符重载与模板参数个数、可重载边界与泛型声明运算符重载和模板是两份「规则密集型」内容题目不难但要靠记忆准确。运算符这块考参数个数和不能重载的清单模板这块考函数模板与类模板的声明差异两边都有固定的判分套路。4.1 成员函数重载时参数少一个重载为成员函数时隐含的 this 指针充当了第一个操作数所以参数表里的个数比操作数少一个。重载为非成员函数时参数个数和操作数个数一致。重载形式一元运算符参数个数二元运算符参数个数成员函数01非成员函数12以a b为例成员版本写作a.operator(b)参数表只有一个非成员版本写作operator(a, b)参数表两个。「成员函数参数表为空重载的是一元运算符」这句话是对的因为二元运算符的成员版本至少需要一个参数。反过来判断「肯定不是类成员函数」的题看参数个数二元运算符写成两个参数就不可能是成员函数。真题给Value operator-(Value, Value)两个参数只能是全局函数所以肯定不属于类成员。4.2 不能重载的运算符与四条硬限制C 里不能重载的运算符只有五个.、::、?:、sizeof、.*。其余大多数可以重载但有限制条件。不能改变操作数个数、优先级和结合性。至少有一个操作数是类类型或枚举类型不能给两个内置类型重载。真题里operator*(double, double)就是错的因为标准库已经有内置的double * double语义不能被改写。重载运算符的参数不能设默认值否则调用时的操作数个数就变了。不是所有运算符都能重载所以「所有运算符都可以通过重载赋予新含义」这种绝对化表述一定是错的。注意、[]、()、-只能作为成员函数重载和作为流运算符时通常写成非成员函数因为左操作数是流对象而不是自定义类。4.3 函数模板与类模板的声明差异模板分函数模板和类模板两类这是固定考点。函数模板的模板参数可以由实参推导调用时通常省略类模板的模板参数一般必须显式写出因为编译器没有实参可推。// 函数模板T 由调用时的实参推导 template typename T T maxOf(T a, T b) { return a b ? a : b; } // 类模板T 必须在使用时显式指定 template class T class Box { public: void set(T v) { value v; } T get() const { return value; } private: T value; }; int main() { int m maxOf(3, 5); // 省略 int推导为 int Boxint b; // 不能省 int b.set(10); }typename和class在模板参数表里完全等价可以互换。模板参数可以作为数据成员的类型、成员函数的返回类型、成员函数的参数类型这三条都是对的说「模板函数不能作为成员函数局部变量的类型」是错的。声明类模板时语法是template class T class 类名 { ... };两个关键字都不能省真题里把class和类名顺序写反的选项就是语法错误。4.4 流式 I/O头文件、对象与格式控制cout是ostream类的对象不是关键字也不是函数名。这条单独考过。流操作必须借助流对象完成不能凭空调用。头文件用途iostream标准输入输出cin、cout、cerrfstream文件流ifstream、ofstream、fstreamstrstream字符串流旧式iomanip格式控制setw、setfill、setprecision格式控制里setw只对紧接着的一次输出有效setfill会持续生效。setw(10) setfill(*) 12341234 占 4 位前面补 6 个星号结果是******1234不是补到 10 个星号。文件流这边ofstream默认打开方式是ios_base::out会截断同名文件要追加得显式加ios_base::app。#include fstream #include iomanip #include iostream using namespace std; int main() { // 追加模式打开避免覆盖已有内容 ofstream out(data.txt, ios_base::out | ios_base::app); out setw(10) setfill(*) 1234 endl; // ******1234 out.close(); return 0; }ios_base::out | ios_base::app用按位或组合多个打开模式这是流类里常见的写法。参数里的app表示每次写入都定位到文件末尾和trunc互斥。5. 五类高频易错点存储类型、指针、字符串与权限矩阵真题刷到第三遍会发现错的总是同一批地方。下面五条是按出错频率排的每条都按「易漏点是什么 → 为什么容易漏 → 怎么补」写最后一条几乎每套卷子都有一道。5.1 存储类型的默认值老教材与新标准混着记易漏点函数内局部变量不写存储类型时默认是 auto全局变量默认是 extern。register只是建议把变量放寄存器编译器可以忽略static修饰局部变量时延长生存期到程序结束但作用域不变。为什么漏C11 之后auto变成了类型推导占位符和新标准一起学的人看到「默认存储类型是 auto」会觉得是错的。二级笔试的题库偏老考的是存储期和链接性这套旧语义。怎么补按「存储期 链接性 作用域」三个维度列表记别只背关键字。局部 static 变量只初始化一次函数多次调用共享同一份真题里让求第二次调用f()的返回值靠的就是这个特性——第一次调用把静态变量加到某个值第二次在此基础上继续加。int f() { static int s 6; // 只初始化一次 s 2; return s; } // 第一次调用返回 8第二次返回 10参数说明static局部变量的初始化发生在第一次执行到声明处之后跳过所以两次调用的起点不同。这类题只要抓住「只初始化一次」就不会错。5.2 指针声明与初始化的拆解易漏点int* p k;等价于int* p; p k;不是int* p; *p k;。前者把 k 的地址存进指针后者试图把地址赋给 int 变量类型不匹配。为什么漏声明语句里的*是类型修饰符表达式里的*是解引用两个符号长得一样、含义完全不同。读题时一眼扫过去很容易把*p k当成合法写法。怎么补养成「声明和使用分开看」的习惯。声明语句先把int*整体当成类型再看变量名赋值语句先看左边是变量还是解引用左边是*p就说明在改 p 指向的内容右边必须是 int 而不是地址。5.3 字符串字面量的长度与字符数组易漏点char myString[12] Hello-World!;是对的char myString[11]就越界。字符串字面量末尾有一个隐藏的\0算数组大小时必须加上。为什么漏数数的时候只数看得见的字符忘了结尾符。Hello-World!是 11 个可见字符加上\0正好 12。怎么补遇到字符数组初始化先在草稿上把每个字符写出来编号最后一个位置留给\0。另外char* p Hello-World!;在旧标准里能编译新标准里字符串字面量是const char[]用char*接会报警告甚至报错选择题里出现这种写法要结合题干年份判断。5.4 标识符规则与关键字边界易漏点标识符不能以数字开头9stars不合法byte不是 C 关键字Java 里才是。friend、namespace、continue都是关键字。为什么漏同时学多门语言的人容易把关键字表串味。byte、var、async这些在别的语言里是关键字在 C 里不是。怎么补准备一张关键字表只记 C 自己的。判断标识符合法性看三条首字符是字母或下划线、只含字母数字下划线、不与关键字重名。三条里最常被忽略的是第一条和第三条。5.5 默认继承方式与权限矩阵易漏点class 定义派生类不写继承方式默认 privatestruct 默认 public。public 继承之后基类的 protected 还是 protectedpublic 还是 public不会全部变成 public。为什么漏一是把「public 继承」理解成「所有成员都变 public」二是被网上流传的错误答案页带偏同一道题在不同版本里答案字母对不上。怎么补自己动手画一遍 3×3 矩阵画完再做题。矩阵只有九格画一次能管很久。遇到答案和矩阵冲突的以矩阵为准扫描版真题的答案错位是常态尤其选择题连着几道答案字母看起来「跳」的时候多半是识别错位。6. 复习清单用错题反查知识点的三步法真题刷完一遍真正的复习才刚开始。我的做法是把每道错题当成一个入口反查它背后的规则而不是记住正确答案的字母。具体分三步。第一步给错题贴板块标签。六条主线里选一个贴不上标签的说明知识地图还没建起来回头补第 1 章那张图。第二步把标签对应的规则画出来。权限题画 3×3 矩阵顺序题画构造析构流程图虚函数题写三个生效条件画完再对照错题看是哪一格没对上。第三步用十行以内的代码复现。选择题的文字描述再准确也不如让编译器告诉你结果。板块必背规则自测方式类与对象默认构造的生成条件、const 对象调用限制写三种构造声明看哪种能编译继承与多态3×3 权限矩阵、构造析构本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?