
简介这是一份东南大学C下期末考试笔试卷含完整原卷及参考答案要点并附有逐题知识点解析面向备考该校C课程的学生也适合需要巩固C核心机制的初学者使用。资源仅含1个doc文档压缩包大小49KB轻便易用。目前已有81人学习下载。试卷包含10道选择题、20处填空及多道阅读程序题覆盖二维数组访问、this指针、new内存分配失败、队列特性、指针运算、深复制、继承访问权限、虚函数与多态、二分查找、单链表、抽象类、文件流文本/二进制格式、运算符重载友元函数、引用与别名等高频考点。每道题均给出正确选项或填空结果阅读程序题还列出运行输出与错误修正说明能帮助学习者快速定位易错点系统梳理指针、类对象、继承、多态等重难点为期末笔试提供直接而高效的复习支持。1. 一份C笔试卷为什么值得逐题拆解拿到一份往年的C期末考试笔试卷多数人的第一反应是“背答案”但真正能拉开分差的从来不是记答案而是搞清楚每道题背后在考哪个机制。我手头这份某高校的C期末笔试卷题型覆盖了指针运算、二维数组、this指针、new/delete内存管理、深拷贝、继承访问权限、虚函数多态、文件流和数据结构基础几乎把期末复习的考点全部压缩进了主流的北向知识点里。对正在备考的学生来说它可以当查漏补缺的题库对已经工作想捡回C底子的开发者来说它更像一面镜子能照出自己的“指针盲区”和“重载误用”。下面我会按题型拆开讲把每道题对应的机制、容易踩的坑、阅卷时最看重的踩分点都说明白。2. 指针、数组与内存new/delete、二维数组、下标迁移的四个典型命题角度2.1 二维数组的行优先布局与b[2][2]的计算推导第一道选择题给了这样一个定义int b[][4] {{1},{3,4},{4,7,9,10},{8,5,6}};问b[2][2]的值。答案是9但很多人在考场上会写成7或者直接懵住。原因是对二维数组的行优先存储和“缺省元素补0”规则不敏感。b的列数是4初始化列表中第一行{1}等价于{1,0,0,0}第二行{3,4}等价于{3,4,0,0}第三行{4,7,9,10}四个元素齐全所以b[2][2]取第三行第三列也就是9。#include iostream using namespace std; int main() { int b[][4] {{1},{3,4},{4,7,9,10},{8,5,6}}; cout b[2][2] b[2][2] endl; // 输出 9 cout b[1][2] b[1][2] endl; // 输出 0缺省补0 return 0; }这里的核心是int b[][4]省略了第一维大小编译器会根据初始化列表的行数自动推导为4行。每一行固定4个元素没写全的行尾自动补0。很多考生只记住了“二维数组按行存储”却忽略了“行内缺省补0”这个前提导致b[1][2]这类边界下标算错。我建议做题时先把每一行按列数补全成矩形再按下标取值不要凭直觉跳步。2.2 new失败返0与内存泄漏双new只delete一次的判定逻辑试卷中有一道典型的改错分析题int *p new int [10]; p new int [10]; delete [] p; p NULL;问这段代码有没有问题答案是B有内存泄漏。第一次new int[10]分配的内存被记录下来但紧接着执行p new int[10]时p 被覆盖成新地址旧地址没有任何指针指向它也永远无法释放。无论之后delete[] p释放的是哪一块第一次分配的那块都已经变成不可达内存了。#include iostream using namespace std; int main() { int *p new int[10]; // 第一次分配 // 错误写法直接覆盖旧块泄漏 // p new int[10]; // 正确写法先释放再重新分配 delete[] p; p new int[10]; delete[] p; p nullptr; return 0; }这个考点关联到另一道选择题new 运算符动态分配失败时返回什么答案是0也就是空指针NULL。在 C 里 new 失败默认抛出 bad_alloc 异常但如果使用new (nothrow)或老版本编译器失败时返回空指针。考试写“0”没问题因为 NULL 在 C 中一定等于整型0。做题时如果看到“返回-1”的错误选项不要被迷惑new 失败不返回负数。2.3 指针算术的边界自增、下标、减法与“指针能相加吗”填空题里有一道非常经典的指针题int A[10] {1,2,0,4,5,9,7,8,6,4}; int *P1 A[1]; // 第2个元素 int *P2 A[4]; // 第5个元素P2 - P1的值是3A[5] - A[2]的值是9。前者是两个指针之间的元素个数差后者是两个数组元素的数值差两者含义完全不同。指针减法得到的是ptrdiff_t类型的偏移量单位是元素不是字节。指针加法指的是“指针加整数”表示向后偏移若干个元素而不是两个指针变量相加两个指针相加没有任何语义是未定义行为。另一道题考数组名与指针的差异int fibon[10]; int *pfib fibon;问访问第二个元素的错误写法答案是*fibon。数组名是常量地址不能自增pfib是指针变量可以自增。*pfib先移动指针到第二个元素再取值合法*(pfib1)同样合法且不改变 pfib 的指向fibon[1]是下标访问最直接。这类题侧重“数组名不是指针变量”这个概念数组名退化后虽能当指针用但禁止自增、赋值。2.4 对半查找的下标序列与数据结构基础概念对半查找那题给了一个有序数组B[17]要查B[11]答案下标依次是8、12、10、11。推导逻辑初始区间0到16中间下标(016)/28B[8]小于目标值区间缩到9到16新中间下标(916)/212B[12]大于目标值区间缩到9到11中间下标(911)/210B[10]小于目标值区间缩到11最后命中11。数据结构部分还考了队列的概念队列是限制存取位置的线性表元素服从先进先出FIFO规则单链表结点由数据域和指针域组成含有纯虚函数的类是抽象类不能创建对象。这些属于记忆型送分题但容易因为和栈的先进后出混淆丢分复习时要把“栈、队列、顺序表、链表”四者的存取规则放一起对比记忆。3. 继承、多态与深拷贝从访问权限到虚函数机制的考点闭环3.1 私有继承下的成员访问边界继承访问权限是每张卷子的必考点。题目假设 Person 类包含公有成员 name、私有成员 id、受保护成员 GetIDStudent 类私有继承 Person问 Student 类的成员函数能直接访问哪些成员。答案选C仅公有成员 name 和受保护成员 GetID。私有继承会把基类的公有成员和保护成员全部变成派生类的私有成员所以 Student 类的成员函数内部能访问但类外部不行。id是基类的私有成员任何派生类都无法直接访问。这里我一般建议画一张三列权限表基类成员原属性、继承方式、派生类内的可见性填完再答题避免凭感觉。class Person { public: string name; private: int id; protected: void GetID() {} }; class Student : private Person { public: void func() { cout name; // 合法公有变私有类内可访问 GetID(); // 合法保护变私有类内可访问 // cout id; // 非法基类私有成员不可直接访问 } };这段代码的逻辑点是“类内可访问”不等于“外部可访问”。私有继承常用于“实现继承”场景表示 Student 只是复用 Person 的实现而不是语义上的 is-a 关系。考试如果问外部代码s.name是否可访问答案一定是不可访问。3.2 虚函数重写的三个一致同名、同参数、同返回类型virtual不必须多态那道选择题问“实现多态的派生类函数不必用什么修饰”答案是 virtual。派生类重写基类虚函数时即使不写 virtual只要函数名、参数表、返回类型一致它仍然是虚函数因为虚函数的属性会继承下来。阅读程序题里 B 类重写了 fun1、fun2(int x)、fun3但主函数通过基类指针 p 调用。修改前的输出是B fun1、B fun2、A fun3。为什么 fun2 输出 B 但 fun3 输出 A因为fun2(int x)参数表和基类fun2()不一致这不是重写而是隐藏基类指针调用的仍然是基类的fun2()。但题目里主函数调用的是p-fun2()由于 B 类的fun2(int x)带参数根本不是同一个函数签名所以解析到基类版本输出 A fun2。这道题正确答案的表述是“修改前输出 B fun1、A fun2、A fun3”试卷中填空给的“B fun2”其实是带参版本被误判的一处典型陷阱。class A { public: virtual void fun1() { cout A fun1 endl; } virtual void fun2() { cout A fun2 endl; } void fun3() { cout A fun3 endl; } }; class B : public A { public: void fun1() { cout B fun1 endl; } void fun2(int x) { cout B fun2 endl; } void fun3() { cout B fun3 endl; } };修改方案有两处把fun2(int x)的参数去掉恢复成和基类一致的fun2()把fun3()声明为 virtual。修改后三个函数全部走虚函数机制基类指针指向派生类对象时输出变为 B fun1、B fun2、B fun3。常见错误是只改 fun3 忘了 fun2或者只删参数忘了在基类加 virtual。我一般建议把“同名、同参数、同返回类型”三个条件抄在草稿纸边上逐条核对完再写答案。3.3 深拷贝三件套构造函数为什么不算必选项深拷贝那道选择题问的是“不是必须自定义的成员函数”答案是构造函数。实现深拷贝必须自定义复制构造函数、析构函数、复制赋值操作符函数普通构造函数与拷贝语义无关所以不必须自定义。完善程序题里有一个 fruit 类的深拷贝实现class fruit { double *price; // 指向四个季度均价数组 public: fruit(const string N, const string S, const string C, double W, double *p 0) : name(N), shape(S), color(C), weight(W), price(p) {} ~fruit() { if (price) delete[] price; } fruit(fruit F) { name F.name; shape F.shape; color F.color; weight F.weight; if (F.price) { price new double[4]; for (int i 0; i 4; i) price[i] F.price[i]; } else { price F.price; } } };这段代码的坑点在于如果直接把price F.price两个对象会共享同一块堆内存析构时被删除两次程序崩溃。深拷贝的做法是为新对象单独new double[4]然后逐元素复制。题目还要求补析构函数里的delete[] price和默认构造参数p0这两处都体现“谁分配谁释放”的内存管理原则。做题时看到类成员里有指针第一反应就该是深拷贝三件套再看题目是否给全了复制构造、析构、复制赋值。4. 阅读程序题与改错题四类题型的做题节奏与阅卷踩分点4.1 追指针输出题先画内存草图再动笔阅读程序题第一题包含一段很典型的指针输出题int k 4, m 6, *p1 k, *p2 m; int arr[] {30,25,20,15,10,5}, *p arr; p; cout *(p3) endl; // p指向253指向10 a (p1 m); // p1指向k与m地址不同a0 b (*p1) / (*p2) 7; // 4/60b7输出结果是10、a0、b7。我在实际带复习时要求学生把每一步的p指向画成箭头图再写下当前地址上的值不要心算。p之后 p 指向 arr[1]此时*(p3)是 arr[4]值为10。p1 m是比较两个地址是否相等p1 指向 k 的地址m 是 m 的地址两者不等结果为0。整数除法4/6在 C 中结果为0所以 b 是7。4.2 构造与析构顺序多重继承与成员对象的顺序记忆链构造析构顺序题给了一个多重继承示例类 A 继承 B2、B3同时包含一个 B1 类型的成员对象。输出顺序是 B2 Constructor、B3 Constructor、B1 Constructor、A Constructor然后析构相反A Destructor、B1 Destructor、B2 Destructor、B3 Destructor。记忆口诀是基类按继承声明顺序先构造成员对象按声明顺序构造最后构造派生类自身析构严格逆序。做题时先看冒号后面的继承列表顺序再看类体内成员声明顺序。这题有干扰项把 B3 放在 B2 前面因为初始化列表里写了A():B3(),B2(),b1()但实际执行顺序不受初始化列表书写顺序影响只由继承声明顺序和成员声明顺序决定。这一点是阅卷时最容易扣分的地方。4.3 完善程序填空题把“空”当接口契约来补完善程序题一共四道核心考的是“看得懂接口、算得清边界”。第一道求上三角元素和for (int i 0; i n; i) for (int j i; j n; j) sum elements[i][j];上三角的判定条件是“列号大于等于行号”所以j的起始值就是i而不是0。第二道 Line 类用聚合加派生两种方式组合 Point派生起点继承 Point终点用 Point 成员对象构造函数初始化列表分别写Point(xv1, yv1)和end(xv2, yv2)。第三道结构体写入二进制文件核心是ofstream对象、ios::binary打开模式、失败判断!outdate、write((char*)tt, sizeof(tt))四步缺一不可。第四道选择排序的空填posj和posi这里比较容易混淆的是忘记更新最小位置下标。5. 这份试卷里的避坑记录五个高频翻车点5.1 现象p new int[10] 执行两次后只 delete 一次程序长时间运行内存持续上涨原因第一次 new 出来的堆内存被 p 覆盖旧地址丢失编译器不会替你释放。解决第二次分配前先delete[] p或者声明两个不同的指针变量分别管理两块内存。我还会额外建议用完指针后立即置空避免空悬指针。5.2 现象代码里写了~Sample(int a)编译报错报错信息指向析构函数原因析构函数不允许有参数。这一题本身就是改错题把~Sample(int a) {value 0;}改成~Sample() {}即可。常见迷惑选项是保留参数然后里面置零这也是错的。5.3 现象考卷程序里出现void main(void)照着写但本机编译器报错原因标准C中 main 函数必须返回 int。很多老教材和旧试卷仍然用 void main但在 Visual Studio 和 GCC 新版本中会有警告或报错。解决考试作答一律写int main()阅卷时这条往往是隐性要求。程序输出题不用改但补全或改错题碰到 main 返回类型要按新标准写。5.4 现象Sample 类里 Max 函数重载定义了一个带默认参数版本调用s.Max(10,20)时编译器报“二义性调用”原因Max(int,int)和Max(int,int,int0)在调用时都能匹配两个实参编译器无法确定选哪个。解决去掉其中一个重载或者去掉默认参数。这类题考的既是重载规则也是代码设计问题默认参数和重载放在同一个类里容易互相踩踏。5.5 现象私有继承 Student 后在主函数里尝试直接访问 name 成员编译失败原因私有继承把基类公有成员变成派生类的私有成员类外不可见。解决确认访问位置——类内成员函数可以访问 name 和 GetID类外不行。如果想要外部也能访问应该改成公有继承或用 using 声明提升访问级别。做题时会遇到“哪些成员可访问”的选择题关键判断标准是“类的内部还是外部”以及“基类成员原本的访问属性”。6. 把这张卷子变成复习工具错题标签化与变式验证6.1 标签化分类复习到后期我会把这张卷子的题目按“机制类”“易错类”“程序分析类”打标签。机制类包括 this 指针语义、new 失败返回值、队列FIFO、纯虚函数与抽象类。易错类包括二维数组缺省补0、指针自增边界、私有继承的可见性、重载二义性。程序分析类包括构造析构顺序、运算符重载、深拷贝、文件流写入。标签的好处是查漏时能直接定位薄弱环节比如标签里“易错类”占了5道那就说明考试时的丢分点集中在概念边界上而不是语法不熟。6.2 变式验证光打标签还不够我会对高频考点做变式改写把边角条件改掉再看结论是否变化。比如把int b[][4]改成int b[][3]初始化列表不变b[2][2] 就从9变成0因为第三行{4,7,9,10}只保留前三个元素9被截断。再比如把p1m改成p1ka 的值变成1考点从“比较地址”变成了“地址是否相等的本质”。还有把 fun2 的参数从 int 改成无参但把返回类型改成 void多态结论不变但代码语义变了做这种变式能加深“同参数表、同返回类型”的匹配意识。原题干条件变式条件输出变化int b[][4]b[2][2]int b[][3]b[2][2]9变为0p1mp1ka从0变为1fun2(int x)fun2() 无参B fun2delete[] p 一次两次用同一指针重复delete未定义行为我通常在考前一天做一次“变式输出默写”不看答案只给变式条件把程序输出写出来再对照原答案。这个过程能逼着自己把指针指向、对象构造顺序、虚函数绑定这些机制在脑子里完整跑一遍而不是停留在“这道题我做过”的错觉上。那些年我几次在期末考前用这套方法复盘基本都能在指针和继承这两块稳住分。从那以后我每次拿到一套旧试卷都会先花半小时做标签化再做一组针对错题的变式改写。这张卷子最大的价值不是答案本身而是它标记出的每一个机制盲区希望帮到你。本文还有配套的精品资源点击获取