《C++ 程序设计》期末模拟卷 B(偏代码计算版)
满分 100 分 建议用时 120 分钟。题型与分值清单:一、单项选择题 18 题 × 1 分 = 18 分;二、读程序写输出 7 题 × 5 分 = 35 分;三、程序填空 4 题(共约 17 空)= 17 分;四、编程大题 3 题 × 10 分 = 30 分。本卷为偏代码计算版:读程序写输出与编程大题难度上浮,重点考查指针、类、动态数据结构的代码追踪能力。请逐行模拟程序执行,注意构造/析构次序、static 记忆性、引用回写、对象切片等招牌陷阱。参考答案与解析见文末(前端默认折叠,自测时请先独立作答)。
下列关于整数运算的说法中,正确的是:
5 / 9 * 100 的结果是 55
7 % 2.0 是合法表达式且结果为 1.0
(-7) / 2 在 C++ 中结果为 -3
a % b 与 a - (a / b) * b(a、b 均为 int)结果相同
已知 int a = 3;,执行 int b = a++ + ++a; 后,b 的值是:
6
7
8
9
下列代码片段的输出是:
int x = 2;
switch (x) {
case 1: cout << "A";
case 2: cout << "B";
case 3: cout << "C"; break;
default: cout << "D";
}
B
BC
BCD
D
设 int arr[5] = {10, 20, 30, 40, 50}; int *p = arr;,则 *(p + 2) + 1 的值是:
21
31
41
32
关于 const 与指针,下列定义中编译错误的是:
int m = 1, n = 2;
const int *p1 = &m; // (Ⅰ)
int * const p2 = &m; // (Ⅱ)
p1 = &n;
*p1 = 10;
*p2 = 10;
B 和 C 都错误
下列运算符中,只能重载为类的成员函数(不能重载为友元/全局函数)的是:
+
==
[]
<<
执行下列声明,共调用类 Box 的构造函数(含拷贝构造)多少次?
Box a(1), b[3], *p[4], &r = a;
3
4
5
8
关于静态数据成员,下列说法错误的是:
静态数据成员被该类的所有对象共享
静态数据成员必须在类外单独定义并分配空间
静态常量整型成员(static const int)可以在类定义中直接初始化
静态数据成员的存储空间随每个对象创建而单独分配
设 D 是 B 的公有派生类,有 B b, *pb; D d, *pd;,下列语句中不符合赋值兼容规则的是:
pb = &d;
pb = pd;
b = d;
pd = pb;
关于虚函数与多态,下列说法正确的是:
通过对象名(而非指针或引用)调用虚函数也能实现动态绑定
把派生类对象赋给基类对象时,派生类特有的数据成员仍被保留
经基类指针调用虚函数时,实际调用的版本由指针所指对象的真实类型决定
构造函数可以声明为虚函数
下列关于函数参数传递的说法正确的是:
数组作形参时按值传递整个数组的副本
引用形参在函数内的修改不会影响实参
引用形参等价于给实参起了一个别名,函数内对其修改会回写到实参
指针形参无法修改实参指向的内容
关于 static 局部变量,下列说法错误的是:
它在程序运行期间始终存在,多次进入函数时保留上次的值
若未显式初始化则自动初始化为 0
它的初始化语句在每次进入函数时都会重新执行一次
其他函数不能直接访问它
给定 char *s[] = {"red", "green", "blue"};,下列输出为 reen 的表达式是:
cout << s[0];
cout << s[1] + 1;
cout << *(s[1]) + 1;
cout << s[1][1];
对于类模板,下列说法错误的是:
类模板本身不是一个具体类,必须实例化后才能创建对象
同一类模板用不同类型参数实例化得到的是互不相同的类
函数模板中模板参数的类型由调用时的实参类型自动推导
类模板的成员函数都必须在类体内部定义,不能在类体外定义
关于异常处理 try/throw/catch,下列说法正确的是:
catch(double) 能捕获 throw 3;(int)抛出的异常
throw 抛出异常后,当前已经构造完成的局部对象不会被析构
异常类型匹配是精确匹配,不会发生像函数调用那样的标准类型提升
一个 try 块只能跟随一个 catch 块
关于继承体系中的构造与析构次序,下列正确的是:
构造次序为:派生类 → 对象成员 → 基类
析构次序为:基类 → 对象成员 → 派生类
构造次序为:基类 → 对象成员 → 派生类,析构次序相反
对象成员的构造次序由初始化列表中书写的先后决定
关于文件流,下列说法正确的是:
ofstream 以默认模式打开已存在文件时会保留原有内容并在末尾追加
if (!fin) 可用于判断文件是否打开失败
while (fin >> x) 在读到文件末尾时仍会再执行一次循环体
ifstream 默认以 out 模式打开
关于深拷贝与三法则(Rule of Three),下列说法错误的是:
含有指向动态内存指针的类,若只用编译器合成的拷贝构造函数会导致两对象共享同一块内存
operator= 应在释放原有空间前先判断是否为自赋值
浅拷贝产生的两个对象析构时会对同一块内存执行两次 delete,导致未定义行为
只要定义了拷贝构造函数,编译器就一定不会再要求定义 operator=
(构造/拷贝构造/析构调用追踪)
#include <iostream>
using namespace std;
class Node {
int id;
static int cnt;
public:
Node(int i) : id(i) { cnt++; cout << "ctor " << id << " (live=" << cnt << ")\n"; }
Node(const Node &n) : id(n.id + 100) { cnt++; cout << "copy " << id << " (live=" << cnt << ")\n"; }
~Node() { cnt--; cout << "dtor " << id << " (live=" << cnt << ")\n"; }
};
int Node::cnt = 0;
Node make(Node n) { return n; }
int main() {
Node a(1);
Node *p = new Node(2);
make(a);
delete p;
return 0;
}
(static 局部变量 + 值传参 vs 引用传参)
#include <iostream>
using namespace std;
void g(int x, int &y) {
static int s = 10;
s += x;
x = x + s;
y = x - y;
cout << "x=" << x << " y=" << y << " s=" << s << endl;
}
int main() {
int a = 3, b = 5;
g(a, b);
cout << "a=" << a << " b=" << b << endl;
g(a + 1, b);
cout << "a=" << a << " b=" << b << endl;
return 0;
}
(指针算术与字符指针数组)
#include <iostream>
using namespace std;
int main() {
int data[] = {2, 4, 6, 8, 10};
int *p = data + 1;
cout << *p++ << " " << *p << " " << *(p + 2) << endl;
char *w[] = {"sjtu", "cpp", "exam"};
cout << w[2] + 2 << " " << *(w[1] + 1) << " " << w[0][3] << endl;
return 0;
}
(虚函数多态 vs 对象切片)
#include <iostream>
using namespace std;
class Animal {
public:
virtual void cry() { cout << "..." << endl; }
void name() { cout << "Animal" << endl; }
};
class Dog : public Animal {
public:
void cry() { cout << "Woof" << endl; }
void name() { cout << "Dog" << endl; }
};
int main() {
Dog d;
Animal *pa = &d;
Animal oa = d; // 对象赋值
pa->cry();
pa->name();
oa.cry();
oa.name();
return 0;
}
(运算符重载:前置/后置 ++ 与友元 <<)
#include <iostream>
using namespace std;
class Counter {
int v;
public:
Counter(int x = 0) : v(x) {}
Counter& operator++() { v += 2; return *this; } // 前置
Counter operator++(int) { Counter t = *this; v += 2; return t; } // 后置
friend ostream& operator<<(ostream &os, const Counter &c) {
os << c.v;
return os;
}
};
int main() {
Counter c(5);
cout << c++ << " " << c << endl;
cout << ++c << " " << c << endl;
return 0;
}
(递归函数追踪)
#include <iostream>
using namespace std;
int f(int n) {
if (n <= 1) return 1;
int r = f(n - 1) + f(n - 2);
cout << "f(" << n << ")=" << r << " ";
return r;
}
int main() {
cout << "result=" << f(4) << endl;
return 0;
}
(IO 流操纵符作用范围)
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
int n = 255;
cout << setfill('0') << setw(5) << n << endl;
cout << hex << n << " " << n << endl;
cout << setw(6) << oct << n << "#" << endl;
return 0;
}
(自管理字符串类的深拷贝赋值,6 分,每空 1 分)
#include <iostream>
#include <cstring>
using namespace std;
class MyStr {
char *str;
public:
MyStr(const char *s = "") {
str = new char[strlen(s) + 1];
strcpy(str, s);
}
MyStr(const MyStr &o) { // 拷贝构造(深拷贝)
str = new char[ ____(1)____ ];
strcpy(str, o.str);
}
MyStr& operator=(const MyStr &o) { // 赋值运算符(深拷贝 + 防自赋值)
if ( ____(2)____ ) return *this; // 自赋值检查
____(3)____ ; // 释放原有空间
str = new char[strlen(o.str) + 1];
strcpy(str, o.str);
return ____(4)____ ;
}
~MyStr() { ____(5)____ ; } // 释放空间
void print() const { cout << str << endl; }
};
int main() {
MyStr a("hello"), b;
b = a;
b.print();
return 0;
}
(二分查找补全,4 分,每空 1 分)
// 在升序数组 nums[0..len-1] 中查找 target,找到返回下标,否则返回 -1
int bsearch(int *nums, int len, int target) {
int left = 0, right = len - 1, mid;
while ( ____(1)____ ) {
mid = left + (right - left) / 2;
if (nums[mid] == target)
return mid;
else if ( ____(2)____ ) // 中间值偏大,向左半区收缩
____(3)____ ;
else
____(4)____ ; // 否则向右半区收缩
}
return -1;
}
(文件读取与统计,4 分,每空 1 分)
#include <iostream>
#include <fstream>
using namespace std;
// 打开 fname,逐个读入整数并累加求和,输出和与个数
void sumFile(const char *fname) {
____(1)____ fin(fname); // 定义输入文件流并打开
if ( ____(2)____ ) { // 打开失败判断
cout << "open error" << endl;
return;
}
int x, sum = 0, cnt = 0;
while ( ____(3)____ ) { // 逐个读取整数直到文件尾
sum += x;
cnt++;
}
cout << "sum=" << sum << " cnt=" << cnt << endl;
____(4)____ ; // 关闭文件
}
(递归字符串反转,3 分,每空 1 分)
#include <iostream>
#include <cstring>
using namespace std;
// 用递归把字符数组 s 原地逆序,i、j 分别为待交换的左右下标
void reverse(char *s, int i, int j) {
if ( ____(1)____ ) return; // 递归终止条件
char t = s[i];
s[i] = s[j];
s[j] = t;
____(2)____ ; // 向内递归一层
}
int main() {
char buf[] = "abcde";
reverse(buf, 0, ____(3)____ ); // 传入初始右边界下标
cout << buf << endl;
return 0;
}
(类设计 + 运算符重载 + 友元,第 10 章,10 分)
设计一个复数类 Complex,要求:
- 含
double re, im;两个私有数据成员,提供带默认参数的构造函数Complex(double r = 0, double i = 0); - 重载
operator+,实现两个复数相加,返回新的Complex对象; - 以友元函数形式重载
operator<<,按a+bi格式输出(例如3+4i,虚部为负时输出3-4i); - 以友元函数形式实现
double modulus(const Complex &c),返回复数的模 。
请写出完整类定义与各函数实现,并给出一个能演示上述功能的 main 函数。
(抽象类与多态层次,第 12 章,10 分)
设计一个图形继承体系:
- 抽象基类
Shape,含一个纯虚函数double area() const;,并提供普通成员函数void show() const;,在其中调用area()输出该图形的面积; - 派生类
Circle(数据成员半径r,面积 )与Rectangle(数据成员宽w、高h,面积 ),各自重写area(); - 用类内共享常量表示(如
static const double PI;); - 在
main中建立一个Shape*指针数组,分别指向Circle和Rectangle对象,通过遍历该数组以多态方式调用show()输出各图形面积。
请写出完整代码。
(动态内存数据结构类,第 7 章 + 第 10 章,10 分)
设计一个动态整型数组类 IntVector,要求实现完整的「三法则」(Rule of Three):
- 私有数据成员:
int *data;(指向动态分配的数组)与int size;; - 构造函数
IntVector(int n):动态分配n个int并全部初始化为 0; - 拷贝构造函数:实现深拷贝(不能与源对象共享同一块内存);
- 重载
operator=:实现深拷贝赋值,并正确处理自赋值与原有空间释放; - 重载
operator[],使其可作为左值访问元素(返回引用); - 析构函数:正确释放动态内存;
- 提供成员函数
void print() const;顺序输出全部元素。
请写出完整类定义与各成员函数实现。