华南理工大学C++期末考试
华南理工大学期末考试
《高级语言程序设计(2)》试卷 A
一.单项选择题(每题 2 分,共 20 分)
1. 在 C++中,有关类和对象正确说法是( A )。
- 对象是类的一个实例
B.对象是类的存储空间
C.一个类只能有一个对象
D.类是程序包,对象是存储空间
解释: > 对象是类的一个实例,这显然正确。 >对象是类在内存中分配的具体实例 >一个类可以有多个对象实例 >类不是程序包,是一种用户自定义的数据类型,对象是类的实例化
2. 在类定义中,称为接口的成员是( C )。
- 所有类成员
B. private或protected的类成员 C. public的类成员
D. public或private的类成员
解释: > 接口时指类对外暴露的公共部分,就是外部程序可以访问的部分
他们构成了类的接口
称为接口显然就是public成员了。
3. 一个类的友员函数能够通过( D )访问该类的所有成员。
- 静态数据
B.析构造函数
C.this 指针
D.类对象参数
友元可以访问类的私有成员和保护成员,成员函数作为类的成员函数时候,可以通过this指针来访问,但如果友元函数以类对象作为参数的话,就可以通过该参数访问类的所有成员。
友元可以通过类对象参数来访问该类的所有成员
4. 下面描述错误的是( B )。
- 自定义构造函数应该是公有成员函数
B.构造函数可以是虚函数 C.构造函数在建立对象时自动调用执行 D.构造函数可以重载
解释: > 自定义构造函数应该是公有函数,构造函数负责初始化对象,在默认情况下,它们应该是公有的,以便外部代码能够正确地创建和初始化对象。
构造函数在建立对象时自动调用执行:构造函数在对象创建时自动调用执行,用来初始化对象的状态
构造函数可以重载:构造函数可以根据参数列表的不同进行重载,以便支持多种不同的初始化方式
但是,构造函数不能是虚函数。虚函数在运行时多态性的实现中起作用,而构造函数在对象创建时就被调用,此时还没有形成对象的动态类型,因此构造函数不能是虚函数。
总而言之,构造函数不可用时虚函数
5. 在类的继承关系中,基类的( B )成员在派生类中可见。
- 所有
B. public和protected C. 只有public
D. 只有protected
解释: > 在类的继承关系中,基类的 B. public 和 protected 成员在派生类中可见。
- 公有成员(public):在派生类中继承自基类的公有成员在外部和派生类内部都是可见的,并且可以被派生类的对象访问。
- 保护成员(protected):在派生类中继承自基类的保护成员在外部不可见,但在派生类的成员函数和友元函数中可见。
私有成员(private):基类的私有成员在派生类中是不可见的,即使它们被继承下来了,派生类也无法直接访问。
因此,基类的公有成员和保护成员在派生类中是可见的,而私有成员则不可见。
记住私有成员不可见此题即可解决
6. 设 B 类是 A 类的派生类,有说明语句
A a, *ap; B b, *bp;
则以下正确语句是( C )。
A. a=b;
B. b=a;
C. ap=&b;
D. bp=&a;
解释:
B类型对象赋值给A类型和A类型赋值给B都是不可以的,因为类型不匹配
C是可以的,因为一个指向A类型的指针ap指向了一个B类型的对象b,这是可以的,B类型是A类型的派生类,可以将指向派生类的对象的指针赋值给基类。
但是D不可用,因为将一个指向基类对象的指针赋给一个指向派生类对象的指针会造成类型不匹配。
知道多态感觉这题很容易选,多态本身就是这样实现的
7. C++中,以下( D )语法形式不属于运行时的多态。
- 根据if语句的求值决定程序流程
B. 根据基类指针指向对象调用成员函数 C. 根据switch语句的求值决定程序流程
D. 根据参数个数、类型调用重载函数
解释:
重载函数的选择编译时就已经确定了,而不是在运行时。编译器在编译时根据函数调用的参数个数和类型来选择调用哪个重载函数,这个过程是静态的,不涉及运行时的多态性。因此,重载函数的调用属于编译时多态(静态多态),而不是运行时多态。
就是说前3个都是运行多态,最后一个是编译时候多态,不是运行时候多态
多态分两种,重载就是典型的静态多态
8. 假设对 A 类定义一个重载“+”号运算符的成员函数,以便实现两个 A 类对象的加法,并返回相加结果,则该成员函数的函数原型为( B )。
A.A operator +( const A &A1, const A &A2 ); B.A A::
operator +( const A &A2 ); C.A::operator +( A &A2 );
D.A A::operator +( );
解释: >正确的函数原型是 B.A A:: operator +( const A &A2 );
这是因为重载运算符时,需要使用成员函数的形式,其中 A:: 表示该函数是 A 类的成员函数,而 operator+ 则表示重载的是加法运算符。在函数原型中,参数列表中应该包含需要进行运算的另一个对象的引用(const A &A2),因为我们不希望修改传入的对象,而且应该返回一个 A 类型的对象,表示两个 A 类对象相加的结果。 成员函数重载,要传引用,返回一个A类型的对象
9. 一个类模板定义了静态数据成员,则( A )。
A.每一个实例化的模板类都有一个自己的静态数据成员。
B.每一个实例化的对象都有一个自己的静态数据成员。
C.它的类型必须是类模板定义的抽象类型。
D.所有模板类的对象公享一个静态数据成员。
解释: >本题具有一定迷惑性,类模板实例化的每个模板类都有自己的类模板静态数据成员,该模板类的所有对象共享一个静态数据成员。模板类的静态数据成员应在文件范围内初始化。每个模板类有自己的类模板的静态数据成员副本。
10. 读一个 C++数据文件,要创建一个( A )流对象。
- ifstream
B.ofstream
C.cin
D.cout
解释: >在 C++ 中,要读取一个数据文件,你需要使用 ifstream 类来创建一个输入文件流对象。这个对象可以打开文件并从中读取数据。ifstream 类是 C++ 标准库中的一部分,专门用于读取输入文件。
选项 B 中的 ofstream 类是用于写入数据到文件的输出文件流对象。
选项 C 和 D 中的 cin 和 cout 分别是用于标准输入和标准输出的流对象,它们用于与用户在控制台进行交互,而不是读写文件。
二.简答题(每小题 4 分,共 20 分)
1. 有右图所示类格。类 X 中有数据成员 int a。根据以下函数注释的编译信息,分析 int X::a 的访问特性,class Y 对 class X 和 class Z 对 class Y 的继承性质。
1 | void Y::funY() { cout<<a<<endl; } //正确 |
答案:
int X::a 是 class X 的 public 数据成员,class Y 为 protected 继承 class X,class Z 为 private 继承 class Y。
根据提供的信息,我们可以得出以下结论:
函数
void Y::funY() { cout<<a<<endl; }
是在类 Y 的成员函数中访问数据成员int X::a
,这是正确的,因为类 Y 是类 X 的保护继承,所以类 Y 中可以访问类 X 的保护成员。函数
void Z::funX() { cout<<a<<endl; }
是在类 Z 的成员函数中访问数据成员int X::a
,这是错误的,因为类 Z 是类 Y 的私有继承,所以无法访问类 Y 中的保护成员。在主函数中,
cout<<x.a<<endl;
是在类外部直接访问类 X 的数据成员int X::a
,这是正确的,因为int X::a
是类 X 的公有成员。cout<<y.a<<endl;
和cout<<z.a<<endl;
都是在类外部直接访问类 Y 和类 Z 的数据成员,但根据继承关系,类 Y 对类 X 是保护继承,类 Z 对类 Y 是私有继承,所以都无法直接访问类 X 的数据成员int X::a
。
2. 有人定义一个教师类派生一个学生类。他认为“姓名”和“性别”是教师、学生共有的属性,声明为 public,“职称”和“工资”是教师特有的,声明为 private。在学生类中定义特有的属性“班级”和“成绩”。所以有
1 | class teacher |
你认为这样定义合适吗?请做出你认为合理的类结构定义。
解释:
>在原始定义中,将教师类派生为学生类并不合适。因为学生和教师虽然共享姓名和性别这些属性,但是职称、工资、班级和成绩是两者不同的属性,不应该在继承关系中出现。
修正代码如下: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class person
{ public:
char name[20]; char sex;
};
class teacher : public person
{ private:
char title[20]; double salary;
};
class student : public person
{ private:
char grade[20] ; int score;
};
正确应该如此
3. 有类定义
1 | class Test |
有人认为“Test 和 Set 函数的功能一样,只要定义其中一个就够了”。这种说法正确吗?为什么?
解释: >带参数的构造函数用于建立对象数据初始化,成员函数用于程序运行时修改数据成员的值。
4. 若有声明
1 | template <typename T> class Tclass { /*……*/ } ;建立一个 Tclass 对象用以下语句 |
有错误吗?若出错,请分析原因,并写出一个正确的说明语句。没有实例化类属参数。
解释: Tclass Tobj<int>;
创建Tclass对象时候未提供模板参数
导致编译器无法确认Tclass的类型
5. C++的文本文件可以用 binary 方式打开吗?若有以下语句 fstream of("d:testfile", ios::out|ios::binary); double PI=3.1415;
请写出把 PI 的值写入文件"d:testfile"末尾的语句。可以。
1 | of.seekp(0,ios::end); |
解释: > 这段代码首先使用 seekp() 函数将文件指针移到文件末尾,然后使用 write() 函数将 PI 的值以二进制形式写入文件。(char)&PI 将 PI 的地址转换为 char 类型的指针,sizeof(double) 给出了 PI 的大小,以确保将正确数量的字节写入文件。
三.阅读下列程序,写出执行结果(每题 6 分,共 24 分)
1.
1 |
|
解释:
FLASE TRUE FALSE TRUE
这段代码实现了一个名为 Boolean 的类,用于表示逻辑值(True 或 False),并重载了加法运算符(+)和乘法运算符(*)。
在类定义中:
类成员 logic 表示逻辑值,其类型为 BoolConst 枚举类型。
构造函数可以接受 BoolConst 类型的参数,用于初始化逻辑值。
print() 函数用于打印当前对象的逻辑值。
在类外部定义了两个友元函数 operator+ 和 operator*:
operator+ 函数实现了逻辑或运算,如果任意一个操作数为 True,则结果为 True。
operator* 函数实现了逻辑与运算,只有当两个操作数都为 True 时结果才为 True。
2.
1 |
|
2 4 6 8 10
Total=5
这段代码实现了一个链表类 List,其中包含模板和静态数据成员。
在类定义中:
- List 类有一个模板参数 T,用于表示链表中存储的数据类型。
- 类有一个默认构造函数,用于初始化链表节点的数据。
- append() 函数用于将节点添加到链表的末尾。
- getnext() 函数用于获取下一个节点的指针。
- getdata() 函数用于获取当前节点的数据。
在类外部定义了 List 类的静态数据成员 total,并初始化为 0。
在主函数中:
- 创建了一个头节点 headnode,其数据类型为 int。
- 使用循环创建了包含 n 个节点的链表,节点的数据依次为 2、4、6、8、10。
- 遍历链表,并输出每个节点的数据。
- 输出链表的总节点数,即静态数据成员 total 的值。
根据输出结果可以看出,链表中存储的数据为 2、4、6、8、10,总共有 5 个节点。
3.
1 |
|
输出: Point构造函数 Point构造函数 Distance构造函数 The distance is 20
这个代码很简单啊,考察了很基本的顺序问题
这段代码定义了两个类:Point 类和 Distance 类,用于表示二维平面上的点和两点之间的距离。
Point 类包含了两个私有成员变量 x 和 y,用于表示点的横纵坐标。它还包含了一个构造函数,用于初始化点的坐标。
Distance 类表示两个点之间的距离。它包含了两个私有成员变量 p1 和 p2,分别表示两个点。它还有一个私有成员变量 dist,用于存储两点之间的距离。Distance 类中包含了一个构造函数,用于计算两个点之间的距离,并将结果保存在 dist 变量中。
在主函数中,首先创建了两个 Point 对象 myp1 和 myp2,分别表示坐标为 (0,0) 和 (0,20) 的两个点。然后创建了一个 Distance 对象 mydist,表示两个点之间的距离。最后输出两点之间的距离。
在输出结果中,首先输出了 Point 类的构造函数被调用的信息,然后输出 Distance 类的构造函数被调用的信息,最后输出了两点之间的距离。
4. 写出 data.txt 中的结果和屏幕显示的结果。
1 |
|
解释: Data.txt
Data: 20 50.5
输出
这段代码首先打开一个名为 "d:\data.txt" 的文件,然后向其中写入一行数据,然后关闭文件。接着重新打开这个文件并读取其中的数据,最后将读取到的数据输出到屏幕上。
假设在运行程序之前,文件 "d:\data.txt" 中原本没有内容。
首先,程序向文件 "d:\data.txt" 中写入一行数据,数据为 "Data: 20 50.5",这是 a 的值加上 10 和 x 的值,之后换行。然后关闭文件。
接着程序重新打开文件 "d:\data.txt" 并读取其中的数据。根据代码中的逻辑,程序首先读取字符串 "Data:" 到 str 变量中,然后读取整数值 20 到变量 a 中,最后读取浮点数值 50.5 到变量 x 中。
因此,最后程序将会输出如下内容到屏幕上:
1 |
|
其中,字符串 "Data:" 被读取到了 str 变量中,整数值 20 被读取到了变量 a 中,浮点数值 50.5 被读取到了变量 x 中。
string= Data:
a=20, x=50.5
四. 根据程序输出填空。(每空 2 分,共 24 分)
1.
1 | //成员和友员 |
解释: 程序输出:
8:30PM
20:30
10:45AM
10:45
这段代码定义了一个 Time 类,用于表示时间。Time 类包含了两个私有成员变量 hours 和 minutes,分别表示小时和分钟。
在类的定义中,有两个函数声明和一个友元函数声明:
函数声明 (1):这是一个构造函数声明,但它的函数名和返回类型都被省略了。根据后续的代码和程序输出,我们可以确定这是一个默认构造函数的声明。
函数声明 (2):这是一个成员函数声明,用于将时间表示为 24 小时制。
友元函数声明:这是一个友元函数的声明,用于将时间表示为 12 小时制。
在类的定义外部,有两个函数的定义:
函数定义 (3):这是 Time 类的成员函数 Time12() 的定义,用于将时间表示为 12 小时制。
函数定义 (4):这是友元函数 Time24() 的定义,用于将时间表示为 24 小时制。
在主函数中,创建了两个 Time 对象 T1 和 T2,并分别调用了它们的成员函数 Time12() 和友元函数 Time24()。这两个函数分别将时间表示为 12 小时制和 24 小时制,并输出到屏幕上。
根据程序输出结果,可以看出:
T1 的时间为 20:30,将其表示为 12 小时制后为 8:30PM,表示为 24 小时制后为 20:30。
T2 的时间为 10:45,将其表示为 12 小时制后为 10:45AM,表示为 24 小时制后仍为 10:45。
因此,程序的输出符合预期。
2. 虚继承
1 |
|
程序输出:
class A class B class C class D
这段代码涉及到了C++中的继承和构造函数的相关知识点。
继承:在C++中,类可以通过继承来获得另一个类的成员。在这段代码中,类B和类C都通过继承类A而派生出来。类D同时继承了类B和类C,从而也间接继承了类A。
构造函数:构造函数用于初始化对象的数据成员。在类D的构造函数中,通过成员初始化列表调用了类A、类B和类C的构造函数,以初始化这些类的对象。构造函数的调用顺序与继承关系有关,在这里先调用了类A的构造函数,然后是类B和类C的构造函数。
虚拟继承:虚拟继承是解决多重继承中的菱形继承问题的一种方法。在这段代码中,类C通过虚拟继承类A,避免了在类D中包含两个类A的子对象,从而解决了二义性问题。
析构函数:尽管在这段代码中没有显示定义析构函数,但是类A定义了一个空的析构函数,用于释放对象的资源。
这段代码定义了四个类:A、B、C 和 D。其中,B 和 C 是通过虚拟继承 A 而派生出来的。D 类同时继承了 B 和 C。
3.
1 |
|
程序输出:
9818 Ranry
0 Jenny
0 no name
这段代码涉及到了C++中的继承、构造函数和构造函数的默认参数等知识点。
继承:类student继承了类studentID。这意味着类student可以访问类studentID中的成员变量和成员函数。
构造函数:类student和类studentID都定义了构造函数。在类的实例化过程中,构造函数被调用来初始化对象的数据成员。在这里,类student和类studentID的构造函数都用于初始化对象的数据成员value和name。
构造函数的默认参数:在类studentID的构造函数声明中,使用了构造函数的默认参数。这表示当创建studentID对象时,如果没有提供参数,则会使用默认值0来初始化value成员。而在类student的构造函数声明中,则没有使用默认参数。
字符串拷贝:在类student的构造函数中,使用了strncpy函数将字符串拷贝到name数组中。该函数会将指定长度的字符串从源地址复制到目标地址,保证不会溢出目标数组。
程序输出:根据代码,程序输出了三个对象的初始化情况。s1对象的名字为"Ranry",学号为9818;s2对象的名字为"Jenny",但未提供学号,默认为0;s3对象未提供任何参数,名字默认为"no name",学号默认为0。
因此,程序输出了:
1 |
|
4. 多态
1 |
|
程序输出:
15 45 30
这段代码定义了一个名为 p_class
的类,其中包含一个整型成员变量 num
和两个成员函数
set_num()
和
show_num()
。set_num()
函数用于设置
num
的值,而 show_num()
函数用于输出
num
的值。
在主函数中,首先创建了一个长度为 3 的 p_class
类型的数组
ob[3]
,然后定义了一个 p_class
类型的指针
p
。
接着,通过循环为数组 ob
中的每个对象调用
set_num()
函数设置了不同的值,即 15、30 和 45。
在输出部分,分别进行了以下操作:
通过指针
p
输出了ob[0]
的num
值,即 15。将指针
p
指向了数组ob
的首地址,即ob[0]
。通过指针
p
输出了ob[0]
的num
值,即 15。将指针
p
指向了数组ob
的末尾元素,即ob[2]
。通过指针
p
输出了ob[2]
的num
值,即 45。将指针
p
指向了数组ob
的第二个元素,即ob[1]
。
因此,程序输出了:
1 |
|
其实只需要读懂这个程序然后再加上一点点的指针移动就可以解决这个问题
五、完成程序。(第 1 小题 4 分,第 2 小题 8 分,共 12 分)
1. 根据程序输出,以最小形式补充 A 类和 B 类的成员函数。
1 |
|
输出:
B_object destroyed. A_object destroyed.
首先根据是B的先被调用了,于是我们可以判断不是构造函数而是应该是析构函数。
在类 A 中,析构函数应该是虚函数,因为我们在动态分配内存时使用了基类指针指向派生类对象,而我们希望在删除对象时能正确调用派生类的析构函数。因此,我们需要在类 A 中声明虚析构函数:
virtual ~A() { cout << "A_object destroyed.\n"; }
在类 B 中,我们只需要定义析构函数,它会自动继承基类的虚析构函数:
~B() { cout << "B_object destroyed.\n"; }
通过这样的补充,程序可以正确输出 B_object destroyed. A_object destroyed.
2. 给出基类 Figure 定义和 main 函数如下: class Figure
1 | { |
编写派生类 Triangle 和 Square 的最小定义,以便在 main 函数中调用派生类函数 showarea()的不同实现版本求直角三角形和矩形的面积。
1 | class Triangle : public Figure |
这段代码定义了两个派生类:Triangle(三角形)和 Square(正方形),它们都继承自基类 Figure。
在 Triangle 类中,重写了基类 Figure 中的虚函数 showarea()。在这个重写的函数中,根据三角形的高和底边计算面积,并将结果输出到标准输出流中。
在 Square 类中,同样也重写了基类 Figure 中的虚函数 showarea()。在这个重写的函数中,根据正方形的边长计算面积,并将结果输出到标准输出流中。
在主函数 main() 中,创建了两个派生类对象 Triangle 和 Square,并分别调用了它们的 set() 函数来设置对象的参数。然后调用了它们各自的 showarea() 函数来计算并显示图形的面积。
通过这样的设计,可以在基类中定义一个通用的接口(虚函数 showarea()),并在派生类中根据具体图形的特点实现不同的功能,实现了面向对象编程中的多态性。