华南理工大学期末考试
前言:卡哇伊
华南理工大学期末考试
《 C++程序设计(II) 》试卷 A
Sate whether each of the following is true or false. (20 scores, each 1 scores)
1. A constant object must be initialized, it can be modified after it is created.
这句话不正确。常量对象必须在创建时进行初始化,并且一旦初始化完成,就无法再修改其值 * ### 2. A static** class member represents class-wide information.
这句话正确。静态类成员表示类范围的信息,而不是特定对象的信息。 *** ### 3. C++ provides for multiple inheritance, which allows a derived class to inherit from many based classes, even if these base classes are unrelated.
这句话是正确的。C++确实支持多重继承,允许派生类从多个基类继承,即使这些基类是不相关的也可以。
4. Treating a bass-class object as a derived-class object can cause errors.
这句话是正确的。将基类对象视为派生类对象可能会导致错误,因为基类对象不具备派生类对象的属性和方法,这可能导致不可预期的行为或错误。 ***
5. Operator dynamic-cast can be used to downcast base-class pointers safely.
是的,这句话是正确的。dynamic_cast 运算符用于在运行时将基类指针或引用转换为派生类指针或引用。它可以安全地将基类指针转换为派生类指针,但在执行转换之前会进行类型检查,以确保转换是安全的。如果转换是不安全的,dynamic_cast 将返回空指针或引发 std::bad_cast 异常(如果指针转换)或抛出 std::bad_cast 异常(如果引用转换)。
dynamic_cast
操作符可以用来安全地将基类指针向下转换为派生类指针。它在 C++
中主要用于处理多态类型转换,并确保类型转换在运行时是安全的。
以下是一些关键点的解释:
什么是 dynamic_cast
dynamic_cast
是 C++ 中的一种类型转换操作符,用于在继承层次结构中进行类型转换。- 它通常用于将基类指针或引用转换为派生类指针或引用(向下转换)。
- 这种转换在运行时会进行类型检查,以确保转换是安全的。
使用 dynamic_cast
的条件
- 必须有虚函数:基类必须至少有一个虚函数。通常,这意味着基类需要定义一个虚析构函数。
- 必须在多态类型中使用:基类和派生类之间必须存在继承关系,且类型必须是多态的。
示例代码
以下是一个示例,演示如何使用 dynamic_cast
进行安全的向下转换:
1 |
|
dynamic_cast
的返回值
- 指针转换:如果转换成功,
dynamic_cast
返回派生类的指针;如果转换失败,返回nullptr
。 - 引用转换:如果转换成功,返回派生类的引用;如果转换失败,抛出
std::bad_cast
异常。
优点
- 安全性:在运行时进行类型检查,确保转换安全,避免非法内存访问。
- 清晰性:明确表明了类型转换的意图,代码可读性高。
适用场景
- 复杂的继承层次结构:在存在多个派生类的情况下,使用
dynamic_cast
可以安全地将基类指针转换为正确的派生类指针。 - 需要类型安全:在不确定类型的情况下,确保程序不会因为非法类型转换而崩溃。
总之,dynamic_cast
是 C++
中用于安全地向下转换基类指针的强大工具,它在多态类型中非常有用,能够在运行时进行类型检查,确保转换的安全性。
***
7. A function template can be overloaded by another function template with the same function name.
是的,这句话是正确的。函数模板可以被另一个具有相同函数名的函数模板进行重载。函数模板重载的条件与普通函数的重载相同,即它们具有相同的函数名但具有不同的参数类型或参数数量。
8. Input/output in C++ occurs as streams of bytes.
输入/输出在C++中通常被视为流
9.
When using parameterized manipulators, the header file must
be included.
这个说法并不准确。虽然在许多情况下使用参数化的操作符重载需要包含
<iostream>
头文件,但并不是所有情况都需要。例如,如果你仅仅是在程序中定义了自定义的参数化操作符重载函数,而没有在其中使用任何
<iostream>
头文件中的内容,那么就不需要包含
<iostream>
头文件。
这个就是不准确的,查阅了一下一些权威文献,答案是iomanip
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 定义一个自定义的参数化操作符重载函数
std::ostream& myManipulator(std::ostream& os, int param) {
// 这里可以进行自定义操作
os << "Parameter: " << param;
return os;
}
int main() {
int value = 10;
return 0;
}
这个说法是不正确的。read
成员函数可以用于从输入对象
cin
读取数据。需要理解的是,read
通常是
std::istream
类的成员函数,用于从流中读取二进制数据。
使用 read
成员函数
std::istream::read
成员函数的主要作用是读取一块原始的二进制数据,通常用于文件或二进制数据处理,但也可以用于从标准输入
cin
读取数据。其基本用法如下:
1 | istream& read(char* s, streamsize n); |
s
:指向存储读取数据的字符数组的指针。n
:要读取的字符数。
示例代码
以下示例演示了如何使用 read
从标准输入 cin
读取数据:
1 |
|
解释
- 缓冲区:我们定义了一个字符数组
buffer
,大小为 11 个字符。这是因为我们需要一个额外的位置来存储 null 终止符。 - 读取数据:使用
std::cin.read(buffer, 10)
从标准输入中读取 10 个字符并存储到buffer
中。 - 添加终止符:手动添加一个 null 终止符
buffer[10] = '\0';
,使其成为一个合法的 C 字符串。 - 输出结果:输出读取到的字符串。
注意事项
read
读取的是原始的二进制数据,不会跳过空白字符(如空格、换行符等),这与operator>>
和getline
不同。read
不会自动添加 null 终止符,因此需要手动添加。
总结
read
成员函数可以用于从cin
读取数据,但它更适合于读取固定长度的二进制数据。- 在使用
read
时,需要注意手动处理字符串终止符。
因此,成员函数 read
可以用于从输入对象 cin
读取数据,这个说法是不正确的。 ***
11. The programmer must create the cin, cout, cerr and clog objects explicitly. ( )
这句话是错的。在 C++ 中,
cin
、cout
、cerr
和clog
是预定义的输入/输出流对象,程序员不需要显式创建它们。这些流对象在程序开始时自动创建并初始化。 ***
12. A nonmember function must be declared as a friend of a class to have accessed to that class’s protected data members.
这句话应该是正确的。非成员函数必须声明为类的友元才能访问类的保护成员。
* ### 13. A member function can not be declared
static if it must access non-static
class member. ( )
为什么C++静态static函数不能访问非静态成员
静态static成员函数不同于非静态函数,它只属于类本身,而不属于每一个对象实例。静态函数随着类的加载而独立存在。与之相反的是非静态成员,他们当且仅当实例化对象之后才存在。也就是说,静态成员函数产生在前,非静态成员函数产生在后,不可能让静态函数去访问一个不存在的东西。
在访问非静态变量的时候,用的是this指针;而static静态函数没有this指针,所以静态函数也确实没有办法访问非静态成员。
14. The precedence, associativity and “arity” of an operator can not be changed by overloading the operator.
这句话的意思是,在 C++ 中,通过重载运算符是无法改变运算符的优先级(precedence)、结合性(associativity)和“arity”(运算符的操作数个数)的。运算符的优先级决定了在表达式中运算符被执行的顺序,结合性指的是相同优先级的运算符在没有括号的情况下如何组合,而“arity”表示一个运算符所期望的操作数的数量。
在 C++ 中,虽然可以重载大部分运算符,但是这些运算符的优先级、结合性和操作数个数是固定的,无法通过重载运算符来改变。例如,重载的加法运算符
+
仍然具有与内置的加法运算符相同的优先级和结合性,而且仍然是一个双目运算符,无法改变其操作数个数。 *** ### 15. In C++, all existing operators can be overloaded.
是的,这句话是错误的。在C++中,并非所有的运算符都可以被重载。有一些运算符是不能被重载的,比如作用域解析运算符
::
、成员选择运算符.
、成员指针运算符.*
、条件运算符?:
等等。这些运算符在语言中有着特定的含义和用法,无法通过重载来改变其行为。因此,正确的说法是,并非所有的运算符都可以被重载。 ***
16. Base-class constructors are not inherited by derived classes.
这句话是对的。派生类可以调用基类的构造函数来初始化基类部分,但这不是继承的一部分。派生类必须显式地调用基类的构造函数来完成这个过程。
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
using namespace std;
class Base {
public:
int baseValue;
Base(int val) : baseValue(val) {
cout << "Base class constructor called with value " << baseValue << endl;
}
};
class Derived : public Base {
public:
int derivedValue;
// 在派生类的构造函数中显式调用基类构造函数
Derived(int baseVal, int derivedVal) : Base(baseVal), derivedValue(derivedVal) {
cout << "Derived class constructor called with value " << derivedValue << endl;
}
};
int main() {
Derived obj(10, 20);
return 0;
}
如果你的编译器支持C++11标准,那么可以使用using
来实现构造函数的继承(打了个双关)。更多详情可以查看维基百科上的C++11文章。你可以这样写:
1 | class A |
这是全包或全不包 - 你不能只继承部分构造函数,如果你这样写,那么你将继承所有构造函数。如果要继承选择的构造函数,你需要手动编写每一个构造函数,并在其中根据需要调用基类的构造函数。
在历史上,构造函数无法在C++03标准中被继承。你需要手动一个一个地继承它们,通过自己调用基类的实现。
17. “has -a” relationship is implemented via inheritance.
这个说法是不准确的。在面向对象编程中,"has-a"关系通常指的是一个类包含另一个类作为其成员变量。这种关系通常通过对象组合来实现,而不是继承。在组合关系中,一个类(通常被称为"容器"类)包含另一个类(被称为"成员"类)的实例作为其成员变量。这种方式使得容器类可以通过调用成员类的方法来使用其功能,但它们之间没有继承关系。
继承关系通常用于实现"is-a"关系,其中子类是父类的一种类型。继承关系中,子类继承了父类的属性和方法,并且可以在此基础上添加新的属性和方法,以及修改已有的行为。 ***
18. Polymorphic programming can eliminate the need for switch logic.
这句话应该是对的。
多态编程可以消除对开关逻辑的需求。在传统的编程中,经常会使用开关语句(如switch-case语句)来根据不同的条件执行不同的操作。然而,使用多态性可以更加灵活地实现相同的功能,而无需使用开关逻辑。通过使用虚函数和动态绑定,程序可以根据对象的实际类型来决定调用哪个函数,从而避免了繁琐的开关逻辑。这使得代码更加简洁、易于理解和维护。
19. A friend function of a function template must be a function-template specialization.
这句话是错误的。在C++中,一个函数模板的友元函数不必是函数模板的特化。事实上,友元函数可以是非模板函数或者其他函数模板,只要它们符合友元函数的定义。友元函数的定义方式与普通函数的定义相同,只需要在类中声明友元即可。友元函数可以访问类的私有成员,但并不是类的成员函数,因此它们不受函数模板特化的限制。 * ### 20. The cin** stream normally is connected to the keyboard.
这句话是正确的。在C++中,cin流通常与键盘连接在一起。当你从键盘输入数据时,数据会被读取到程序中,cin流负责处理这些输入数据。因此,cin通常被用于从键盘接收用户输入的数据。
21. An exception thrown outside a try block causes a call to terminate.
这句话是正确的。在C++中,如果异常被抛出并且没有被相应的try-catch块捕获,那么程序会调用terminate函数来终止程序的执行。terminate函数是一个用于终止程序的标准C++库函数,它会执行一些清理工作,然后终止程序。通常情况下,这意味着程序会异常终止,并且可能会输出一条错误消息。
2. Answer the following questions.(29 scores)
1) Fill in the blanks in each of the following program. The program should read the record from the file “C:.ini”, and display it on screen. (8 scores)
1 |
|
正确答案:
这段程序是一个简单的文件读取程序,目的是读取名为 "C:\boot.ini" 的文件,并将其内容逐行输出到屏幕上。
#include <fstream>
:这行代码包含了文件输入输出流所需的头文件。void main()
:程序的主函数。char buffer[100];
:声明了一个字符数组 buffer,用于存储从文件中读取的内容。ifstream input ("C:\\boot.ini", ios::in);
:创建了一个输入文件流对象 input,并打开名为 "C:\boot.ini" 的文件用于读取。ios::in 参数表示以输入模式打开文件。while (input && !input.eof())
:使用 while 循环读取文件内容,条件是文件流对象 input 有效且未到达文件结尾。input.getline(buffer, 80);
:使用 input.getline() 函数从文件中读取一行内容,存储到 buffer 中,最多读取 80 个字符。cout << buffer << endl;
:将读取到的一行内容输出到屏幕上。input.close();
:关闭文件流对象,释放文件资源。
整体来说,这段程序打开文件 "C:\boot.ini",逐行读取其内容并输出到屏幕上,直到文件结尾。
1 |
|
2) Find the errors in the following program and explain how to correct them.(5scores)
1 | include <iostream.h> |
1 |
|
3. Fill in the blanks in each of the following program.(8 scores)
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
using std::cout;
using std::cin;
using std::endl;
template <class T> // 在类模板声明中指定模板参数类型为 T
class Stack {
public:
Stack( int = 10 );
~Stack() {delete [] stackPtr; }
bool push( const T& );
bool pop( T& );
bool isEmpty() const; // 在声明中添加分号
bool isFull() const; // 在声明中添加分号
private:
int size;
int top;
T *stackPtr;
}; // end class Stack
template <class T> // 在构造函数定义中指定模板参数类型为 T
Stack< T >::Stack( int s ){
size = s > 0 ? s : 10;
top = -1; // Stack initially empty
stackPtr = new T[ size ]; // allocate memory for elements
}
// 省略其他成员函数定义
int main() // main 函数返回类型应为 int
{
Stack<double> doubleStack; // 创建一个 double 类型的栈对象 doubleStack,大小为 5
double doubleValue = 1.1;
doubleStack.push( doubleValue ); // 将 doubleValue 压入 doubleStack
Stack<int> intStack; // 创建一个 int 类型的栈对象 intStack
int intValue = 1;
intStack.push(intValue); // 将 intValue 压入 intStack
return 0; // 返回 0 表示程序执行成功
} // end main
1 |
|
解释:
- 在类
CTest
中声明了私有数据成员x
和y
,以及公有构造函数和重载的后置递增运算符operator++(int)
。 - 后置递增运算符的重载实现中,首先创建一个临时对象
temp
,用来保存原始对象的值。然后,对原始对象的x
和y
分别进行递增操作。最后,返回保存原始对象值的临时对象。 - 在
main()
函数中,首先创建一个CTest
类型的对象d1
,并使用print()
方法输出其初始值。然后使用后置递增运算符对d1
进行递增操作,并再次使用print()
方法输出递增后的值。
2. For each of the following, show the output(25 scores, each 5 scores)**
1.
1 |
|
这个程序创建了一个链表,然后通过插入排序的方式将数组 k
中的元素插入到链表中,最后输出链表的内容。
输出是now the items of list are: 1 2 4 6 9
2).
能看懂Helloworld应该可以看懂这个
1 |
|
输出结果为:
1 | a = 6 b = 9 x = 8 y = 7 |
3).
1 |
|
输出结果为:
这个也是模拟
1 | x = 10 y = x * x = 100 |
4).
1 |
|
输出结果为:
1 | 123.456 |
这个程序演示了如何使用 cout
对象进行格式化输出。下面是对程序的解释:
cout.width(10);
设置输出宽度为10个字符,不足的部分会使用填充字符填充,默认为空格。cout.setf(ios::dec, ios::basefield);
设置输出的数值以十进制格式显示。cout << x << endl;
输出变量x
的值,并换行。- 结果是:
123.456
(因为宽度为10,而输出的数字占据了7个字符,所以前面填充了3个空格)。
- 结果是:
cout.setf(ios::left);
设置左对齐输出。cout << x << endl;
再次输出变量x
的值,并换行。- 结果是:
123.456
(左对齐,宽度为10,所以输出没有填充字符)。
- 结果是:
cout.width(15);
设置输出宽度为15个字符。cout.setf(ios::right, ios::left);
设置右对齐输出。cout << x << endl;
再次输出变量x
的值,并换行。- 结果是:
123.456
(右对齐,宽度为15,所以前面填充了9个空格)。
- 结果是:
cout.setf(ios::showpos);
设置显示正号。cout << x << endl;
输出变量x
的值,并换行。- 结果是:
+123.456
(因为设置了显示正号,所以正数前面会显示+
)。
- 结果是:
cout << -x << endl;
输出变量-x
的值,并换行。- 结果是:
-123.456
(负数默认显示负号)。
- 结果是:
cout.setf(ios::scientific);
设置科学计数法输出。cout << x << endl;
再次输出变量x
的值,并换行。- 结果是:
1.234560e+02
(以科学计数法显示x
的值)。
- 结果是:
5).
1 |
|
输出结果为:
1 | 0 |
在这个程序中:
- 创建了一个
Iclass
的对象obj
,传入参数为2, 4, 10
。 - 对象
obj
被切片为Bclass
类型的对象p1
。因为对象切片的原因,p1
只能调用Bclass
类的成员函数,所以输出为0。 - 创建了一个
Bclass
类型的引用p2
指向obj
,所以调用p2.fun()
时,会调用Iclass
类中的fun()
函数,输出结果为5。 - 使用作用域解析运算符
::
调用Bclass
类中的fun()
函数,结果也为5。 - 创建了一个指向
Bclass
类型的指针p3
指向obj
,因为fun()
是虚函数,所以会调用Iclass
类中的fun()
函数,输出结果为5。
4、Create a class RationaNumber(fractions) with the following capabilities: (12 scores)
- Enable input and output of fractions through the overloaded >> and << operators.
- Create a constructor that prevents a 0 denominator in a fraction.
- Overloaded the addition operator (+), subtraction operator (-) for
this class.
以下为完整代码
1 |
|
有理数的运算,理解一下就好了(大概) ***
5、Implement the Shape hierarchy shown in fig.1. Each TwoDimentsionShape should contain function getArea to calculate the area of the two-dimensional shape. Each ThreeDimentsionShape should contain functions getArea and getVolume to calculate the surface area and volume of the three-dimensional shape, respectively. Create a program that uses a vector, determine whether each shape is a TwoDimentsionShape or a TwoDimentsionShape. If shape is a TwoDimentsionShape, display its area. If shape is a ThreeDimentsionShape, display its area and volume. (16 scores)
TwoDimentsionShape ThreeDimentsionShape circle Square Cube
为了实现Shape
层次结构,我们将创建一个C++程序,其中包括基类Shape
、派生类TwoDimensionShape
和ThreeDimensionShape
,以及一些具体的形状类,如Circle
、Square
、Sphere
和Cube
。每个二维形状类将包含一个getArea
函数,每个三维形状类将包含getArea
和getVolume
函数。程序将使用std::vector
来存储这些形状对象,并确定每个形状是二维还是三维的,然后显示其面积和体积。
代码实现
1 |
|
解释
- 基类和派生类:
Shape
是一个抽象基类,定义了一个纯虚函数display
。TwoDimensionShape
和ThreeDimensionShape
分别是从Shape
派生出来的抽象类,定义了各自特有的纯虚函数getArea
和getVolume
。
- 具体形状类:
Circle
和Square
是从TwoDimensionShape
派生出来的具体类,分别实现了getArea
函数。Sphere
和Cube
是从ThreeDimensionShape
派生出来的具体类,分别实现了getArea
和getVolume
函数。
- 主函数:
- 使用
std::vector<std::shared_ptr<Shape>>
来存储各种形状对象。 - 创建具体的形状对象,并将其添加到
shapes
向量中。 - 遍历
shapes
向量,并调用每个形状对象的display
函数来输出其面积和体积。
- 使用
通过这种方式,我们实现了形状层次结构,并能够正确地确定每个形状是二维还是三维的,并显示其相应的面积和体积。