前言:

img

软件学院《C++Ⅱ》试卷

======================== ## 一、给出一个有理数类,实现有理数的加法运算。请按后列要求作答。

1.仔细、完整地阅读程序,对应题号填写注释。注释的内容包括两部分:1.本行的语句是 什么;2.简述其功能或特点。例如,注释可以这样写:“友员函数;重载加法运算符(+)。”

(每小题 1 分,共 10 分)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include<iostream.h> 

#include<math.h>

#include<iomanip.h>

class Rational

{ private:

int numerator,denominator; //(例)数据成员;表示一个有理数的分子、分母。
int maxcommonFactor(int,int); //(1)

void simplify( ); //(2)

void DtoF(double,int&,int&); //(3)

public:
Rational(int n,int d=1); //(4)

Rational(double x=0); //(5)

~Rational(); //(6)

void plus(const Rational&); //(7)

Rational operator + (const Rational&); //(例)公有成员函数;重载加法运算符(+)。

void showFraction( ); //(8)

operator double( ); //(9)

friend ostream &operator<< //(10)

(ostream &output, Rational &t);

};

私有成员函数; 求两个整数的最大公因子。 (2) 私有成员函数; 分数约简。 (3) 私有成员函数; 小数转换为分数。 (4) 构造函数; 参数为分子、 分母形式。 (5) 重载构造函数; 参数为小数形式。 (6) 析构函数; 对象的释放清理。 (7) 公有成员函数; 实现加法运算。 (8) 公有成员函数; 以分数形式输出有理数。 (9) 类型转换函数; 把分数形式的对象转换成小数。 (10) 友员函数; 重载插入运算符 (<<) 。 ****

2. 对应题号把函数的定义补充完整。(每小题 3 分,共 9 分)

正确为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
int Rational::maxcommonFactor(int a, int b) {
if (a % b == 0)
return b;
return maxcommonFactor(b, a % b);
}

void Rational::simplify() {
int temp = maxcommonFactor(abs(numerator), abs(denominator));
if (temp == 1)
return;
numerator /= temp;
denominator /= temp;
}

void Rational::DtoF(double x, int &n, int &d) {
double s = fabs(x);
d = 1;
while ((s * d - int(s * d)) > 1e-15)
d *= 10;
n = int(x * d);
}

Rational::Rational(int n, int d) {
if (d == 0) {
cout << "错误! 分母不能为 0。有理数将置为 0。" << endl;
numerator = 0;
denominator = 1;
} else {
numerator = n;
denominator = d;
}
simplify();
}

Rational::Rational(double x) {
DtoF(x, numerator, denominator);
simplify();
}

Rational Rational::operator+(const Rational &t) {
Rational temp(0, 1);
temp.numerator = numerator * t.denominator + denominator * t.numerator;
temp.denominator = denominator * t.denominator;
temp.simplify();
return temp;
}

Rational::operator double() {
return static_cast<double>(numerator) / denominator;
}

ostream &operator<<(ostream &output, Rational &t) {
output << t.numerator << '/' << t.denominator;
return output;
}

void main() {
Rational a;
cout << double(a);
cout << endl;

Rational b(2, 0);
b.showFraction();
cout << endl;

Rational c(3, 4);
c.showFraction();
cout << endl;

Rational d(1.2);
d.showFraction();
cout << endl;

a = b + c;
cout << double(a);
cout << endl;
}

这段代码定义了一个表示有理数的类 Rational,并实现了几个功能,如最大公约数计算、简化分数、从双精度浮点数转换为分数、加法运算符重载、类型转换运算符重载以及输出运算符重载。下面是对每个部分的详细解释:

类定义及成员函数实现

最大公约数计算函数

1
2
3
4
5
int Rational::maxcommonFactor(int a, int b) {
if (a % b == 0)
return b;
return maxcommonFactor(b, a % b);
}
  • 使用递归方法计算两个整数 ab 的最大公约数。基于欧几里得算法,如果 a 能被 b 整除,则 b 是最大公约数,否则递归调用自己。

简化分数函数

1
2
3
4
5
6
7
void Rational::simplify() {
int temp = maxcommonFactor(abs(numerator), abs(denominator));
if (temp == 1)
return;
numerator /= temp;
denominator /= temp;
}
  • 使用 maxcommonFactor 计算分子和分母的最大公约数,然后用该值除以分子和分母以简化分数。

双精度浮点数转换为分数函数

1
2
3
4
5
6
7
void Rational::DtoF(double x, int &n, int &d) {
double s = fabs(x);
d = 1;
while ((s * d - int(s * d)) > 1e-15)
d *= 10;
n = int(x * d);
}
  • 将双精度浮点数 x 转换为分子 n 和分母 d 的形式。通过不断增加 d,直到 s * d 变为整数。

构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Rational::Rational(int n, int d) {
if (d == 0) {
cout << "错误! 分母不能为 0。有理数将置为 0。" << endl;
numerator = 0;
denominator = 1;
} else {
numerator = n;
denominator = d;
}
simplify();
}

Rational::Rational(double x) {
DtoF(x, numerator, denominator);
simplify();
}
  • 构造函数Rational(int n, int d):根据给定的分子 n 和分母 d 初始化 Rational 对象。如果分母为 0,则输出错误信息并将对象设置为 0/1。然后调用 simplify 简化分数。
  • 构造函数 Rational(double x):将双精度浮点数 x 转换为分数形式并简化。

运算符重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Rational Rational::operator+(const Rational &t) {
Rational temp(0, 1);
temp.numerator = numerator * t.denominator + denominator * t.numerator;
temp.denominator = denominator * t.denominator;
temp.simplify();
return temp;
}

Rational::operator double() {
return static_cast<double>(numerator) / denominator;
}

ostream &operator<<(ostream &output, Rational &t) {
output << t.numerator << '/' << t.denominator;
return output;
}
  • operator+:实现有理数加法。创建一个临时 Rational 对象 temp,计算分子和分母的和并简化。
  • operator double():将 Rational 对象转换为 double 类型,即分子除以分母的结果。
  • 输出运算符重载 operator<<:用于输出 Rational 对象的分数形式。

主函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void main() {
Rational a;
cout << double(a);
cout << endl;

Rational b(2, 0);
b.showFraction();
cout << endl;

Rational c(3, 4);
c.showFraction();
cout << endl;

Rational d(1.2);
d.showFraction();
cout << endl;

a = b + c;
cout << double(a);
cout << endl;
}
  • 创建不同类型的 Rational 对象并进行测试:
    • a 使用默认构造函数。
    • b 尝试使用非法分母 0 创建对象,并输出其结果。
    • c 使用合法的分数创建对象,并输出其结果。
    • d 使用双精度浮点数创建对象,并输出其结果。
    • bc 相加,并输出其结果。

错误与改进

  • main 函数应该返回 int 类型,而不是 void
  • showFraction 函数在类定义中没有定义,需要补充其定义。
1
2
3
void Rational::showFraction() const {
cout << numerator << '/' << denominator;
}
  • 修改 main 函数的签名,并确保返回 0 以表示程序正常结束。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int main() {
Rational a;
cout << double(a);
cout << endl;

Rational b(2, 0);
b.showFraction();
cout << endl;

Rational c(3, 4);
c.showFraction();
cout << endl;

Rational d(1.2);
d.showFraction();
cout << endl;

a = b + c;
cout << double(a);
cout << endl;

return 0;
}

这样改进后,程序可以正常编译和运行,并正确显示每个有理数对象的值及其运算结果。 ***

2. (每小题 3 分,共 18 分)

在下面的程序中,类 B1B2 虚继承类 A,类 C 多重继承 B1B2。由于 A 是虚基类,创建间接派生类对象时,只有一个 A 对象的数据成员版本。请根据题目要求作答。注意:A 为虚基类。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>

using namespace std;

class A { // 类 A 有数据成员 x 和 y
public:
double x, y;
A(double px = 1, double py = 1) : x(px), y(py) {}
virtual ~A() {}
virtual void show() { cout << "[A ]=>" << x << "," << y << endl; }
};

class B1 : virtual public A { // 类 B1 计算矩形面积
public:
B1(double px = 2, double py = 2) : A(px, py) {}
void show() override { cout << "[B1]=>" << x * y << endl; }
};

class B2 : virtual public A { // 类 B2 计算梯形面积
public:
double z;
B2(double px = 3, double py = 3, double pz = 3) : A(px, py), z(pz) {}
void show() override { cout << "[B2]=>" << 0.5 * (x + y) * z << endl; }
};

class C : public B1, public B2 { // 类 C 计算体积
public:
double h;
C(double px = 4, double py = 4, double pz = 4, double ph = 4) : A(px, py), B1(px, py), B2(px, py, pz), h(ph) {}
void show() override {
cout << "[C ]=>" << x << "," << y << "/" << x * y * h << "/" << 0.5 * (x + y) * z * h << endl;
}
};

int main() {
A a; B1 b1; B2 b2; C c;

A* p = &a; p->show();
p = &b1; p->show();
p = &b2; p->show();
p = &c; p->show();

return 0;
}

运行结果

1
2
3
4
[A ]=>1,1
[B1]=>4
[B2]=>9
[C ]=>4,4/64/96

解释

  1. A 的构造函数:
    • 默认构造函数 A(double px=1, double py=1) 初始化 xy1
    • a 对象使用默认构造函数,所以 axy 均为 1
    • 调用 p->show() 输出 [A ]=>1,1
  2. B1 的构造函数:
    • B1(double px=2, double py=2) 初始化 xy2
    • b1 对象使用 B1 的默认构造函数,所以 b1xy 均为 2
    • 调用 p->show() 输出 [B1]=>4 (2 * 2 = 4)。
  3. B2 的构造函数:
    • B2(double px=3, double py=3, double pz=3) 初始化 xyz3
    • b2 对象使用 B2 的默认构造函数,所以 b2xy 均为 3z3
    • 调用 p->show() 输出 [B2]=>9 (0.5 * (3 + 3) * 3 = 9)。
  4. C 的构造函数:
    • C(double px=4, double py=4, double pz=4, double ph=4) 初始化 xy4z4h4
    • c 对象使用 C 的默认构造函数,所以 cxy 均为 4z4h4
    • 调用 p->show() 输出 [C ]=>4,4/64/96:
      • 4,4xy 的值。
      • x * y * h = 4 * 4 * 4 = 64
      • 0.5 * (x + y) * z * h = 0.5 * (4 + 4) * 4 * 4 = 96

通过虚继承,类 C 只有一个基类 A 的实例,所以所有的 xy 都指向同一个 A 的成员变量。

·写出上述程序的运行结果。

·在类 A 的 show 函数之前加上关键字 virtual,再写出上述程序的运行结果。

和上面应该没有差别

·在(28)题基础上,于原 main 函数的末尾添加 ((B2)c).show( ); 结果将多显示一行: (29)

·把类 A 的 show 函数改写成纯虚函数。

·若类 A 的 show 函数改写成纯虚函数,原来的 main 函数不能正常运行,为什么?请简单

说明理由。

  1. 将类 A 的 show 函数改写成纯虚函数后,即改为 virtual void show() = 0;,原来的 main 函数不能正常运行。因为类 A 的对象不能再被实例化,而 main 函数中使用了类 A 的对象进行实例化。

·类 A 的 show 函数改写成纯虚函数后,请你对原来的 main 函数作简单的删改,使其可以

输出派生类的数据。

  1. 类 A 的 show 函数改写成纯虚函数后,原来的 main 函数不能正常运行的原因是类 A 变成了抽象类,无法创建其对象。为了使程序正常运行,可以修改 main 函数,使用派生类的对象进行实例化,例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int main() 
{
A a;
B1 b1;
B2 b2;
C c;

A* p1 = &b1;
A* p2 = &b2;
A* p3 = &c;

p1->show();
p2->show();
p3->show();
((B2)c).show();

return 0;
}

三、给出一些关于使用模板的程序段,按要求作答。

1.下列程序用函数模板实现两个数据的交换,请把有关的语句填充完整。

(每小题 3 分,共 9 分)

知识:

这道题考察了函数模板的使用。函数模板是一种通用的函数定义,可以在不指定具体数据类型的情况下定义函数,使得函数可以接受任意类型的参数。在这个例子中,我们使用了函数模板 swap 来实现两个数据的交换操作。

总结起来,函数模板的使用需要注意以下几点:

  1. 函数模板的声明以关键字 template 开始,后面跟有模板参数列表,用尖括号 < > 括起来,参数列表中可以包含一个或多个模板参数。
  2. 模板参数可以是任意有效的数据类型,包括基本数据类型(如 int, double, char 等)、类类型、指针类型等。
  3. 在函数模板定义中,使用模板参数来定义函数的参数类型、返回类型等,使得函数可以处理不同类型的数据。
  4. 在调用函数模板时,编译器会根据实际参数的类型来推断模板参数的具体类型,并生成对应的函数实例。
  5. 函数模板可以和普通函数一样进行重载,可以定义多个相同名字的函数模板,只要它们的参数列表或模板参数列表不同即可。
  6. 函数模板可以用于各种类型的参数,包括基本数据类型、用户自定义类型、指针类型等,提高了代码的通用性和重用性。

在实际编程中,函数模板通常用于编写通用的算法函数,例如容器类的排序、搜索等操作,以及数据类型无关的算法实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<iostream.h> 

template <class T>

void swap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}

void main()
{
int j=1,k=3;
cout<<"int 数据类型:\n"<<j<<","<<k<<"=>";
swap(j,k);
cout<<j<<","<<k<<endl;

double x=1.23,y=9.87;
cout<<"double 数据类型:\n"<<x<<","<<y<<"=>";
swap(x,y);
cout<<x<<","<<y<<endl;

char p='A',q='B';
cout<<"char 数据类型:\n"<<p<<","<<q<<"=>";
swap(p,q);
cout<<p<<","<<q<<endl ;
}

2.下列程序使用了向量 vector 和算法 sort 实现数组的排序,请把有关的语句填充完整。

(每小题 3 分,共 9 分)

应该很容易,如果打过acm或者说学过一点点algorithm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
using namespace std;

const int size = 10;

void display(vector<int> V, int n)
{
int i;
for (i = 0; i < n; i++)
cout << V[i] << " ";
cout << endl;
}

bool down(int x, int y)
{
return x > y;
}

int main()
{
int a[size] = {10, 3, 17, 6, 15, 8, 13, 34, 25, 2};

vector<int> V(a, a + size); // 用数组对模板向量赋初值

cout << "输出原始数组: \n";
display(V, size);

sort(V.begin(), V.end()); // 对向量按升序排序

cout << "输出升序排列后的数组: \n";
display(V, size);

sort(V.begin(), V.end(), down); // 对向量按降序排序

cout << "输出降序排列后的数组: \n";
display(V, size);

return 0;
}

四、给出一个输入输出流操作的程序段,请把有关的语句填充完整。

(每小题 3 分,共 6 分)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream> 
#include <fstream>
#include <cstdlib>

using namespace std;

int main()
{
ofstream outstuf; // 建立输出文件流对象
outstuf.open("e:\\newfile.dat", ios::app); // 打开文件并将写入位置定位到文件末尾

if(!outstuf) // 检查文件是否成功打开
{
cerr << "Error!" << endl;
abort();
}

outstuf << "This is a file of example.\n"; // 写入一行内容

outstuf.close(); // 关闭文件

return 0;
}