前言:卡哇伊

img

华南理工大学期末考试

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 的条件

  1. 必须有虚函数:基类必须至少有一个虚函数。通常,这意味着基类需要定义一个虚析构函数。
  2. 必须在多态类型中使用:基类和派生类之间必须存在继承关系,且类型必须是多态的。

示例代码

以下是一个示例,演示如何使用 dynamic_cast 进行安全的向下转换:

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>
#include <typeinfo>

class Base {
public:
virtual ~Base() {} // 虚析构函数,确保多态性
};

class Derived : public Base {
public:
void display() {
std::cout << "Inside Derived class." << std::endl;
}
};

int main() {
Base* basePtr = new Derived; // 基类指针指向派生类对象
Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);

if (derivedPtr != nullptr) {
derivedPtr->display(); // 安全地调用派生类方法
} else {
std::cout << "Bad cast: basePtr is not pointing to a Derived object." << std::endl;
}

delete basePtr;
return 0;
}

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
#include <iomanip> // 必须包含这个头文件,因为我们使用了 std::ostream

// 定义一个自定义的参数化操作符重载函数
std::ostream& myManipulator(std::ostream& os, int param) {
// 这里可以进行自定义操作
os << "Parameter: " << param;
return os;
}

int main() {
int value = 10;

return 0;
}

* 10. Member function read cannot be used to read data from the input object cin**. ( )

这个说法是不正确的。read 成员函数可以用于从输入对象 cin 读取数据。需要理解的是,read 通常是 std::istream 类的成员函数,用于从流中读取二进制数据。

使用 read 成员函数

std::istream::read 成员函数的主要作用是读取一块原始的二进制数据,通常用于文件或二进制数据处理,但也可以用于从标准输入 cin 读取数据。其基本用法如下:

1
istream& read(char* s, streamsize n);
  • s:指向存储读取数据的字符数组的指针。
  • n:要读取的字符数。

示例代码

以下示例演示了如何使用 read 从标准输入 cin 读取数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>

int main() {
char buffer[11]; // 定义一个字符数组用于存储读取的数据

std::cout << "Please enter 10 characters: ";

// 使用 read 从标准输入 cin 读取 10 个字符到 buffer 中
std::cin.read(buffer, 10);

// 确保字符串以 null 终止
buffer[10] = '\0';

std::cout << "You entered: " << buffer << std::endl;

return 0;
}

解释

  1. 缓冲区:我们定义了一个字符数组 buffer,大小为 11 个字符。这是因为我们需要一个额外的位置来存储 null 终止符。
  2. 读取数据:使用 std::cin.read(buffer, 10) 从标准输入中读取 10 个字符并存储到 buffer 中。
  3. 添加终止符:手动添加一个 null 终止符 buffer[10] = '\0';,使其成为一个合法的 C 字符串。
  4. 输出结果:输出读取到的字符串。

注意事项

  • read 读取的是原始的二进制数据,不会跳过空白字符(如空格、换行符等),这与 operator>>getline 不同。
  • read 不会自动添加 null 终止符,因此需要手动添加。

总结

  • read 成员函数可以用于从 cin 读取数据,但它更适合于读取固定长度的二进制数据。
  • 在使用 read 时,需要注意手动处理字符串终止符。

因此,成员函数 read 可以用于从输入对象 cin 读取数据,这个说法是不正确的。 ***

11. The programmer must create the cin, cout, cerr and clog objects explicitly. ( )

这句话是错的。在 C++ 中,cincoutcerrclog 是预定义的输入/输出流对象,程序员不需要显式创建它们。这些流对象在程序开始时自动创建并初始化。 ***

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函数不能访问非静态成员

  1. 静态static成员函数不同于非静态函数,它只属于类本身,而不属于每一个对象实例。静态函数随着类的加载而独立存在。与之相反的是非静态成员,他们当且仅当实例化对象之后才存在。也就是说,静态成员函数产生在前,非静态成员函数产生在后,不可能让静态函数去访问一个不存在的东西。

  2. 在访问非静态变量的时候,用的是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
#include <iostream>
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
2
3
4
5
6
7
8
9
10
class A
{
public:
explicit A(int x) {}
};

class B: public A
{
using A::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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_ 

void main()

{ char buffer[100];

ifstream input (\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_)

while (\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_)

{ input.getline(\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_,80);

cout<< buffer<<endl;

}

input.close();

}

正确答案:

这段程序是一个简单的文件读取程序,目的是读取名为 "C:\boot.ini" 的文件,并将其内容逐行输出到屏幕上。

  1. #include <fstream>:这行代码包含了文件输入输出流所需的头文件。

  2. void main():程序的主函数。

  3. char buffer[100];:声明了一个字符数组 buffer,用于存储从文件中读取的内容。

  4. ifstream input ("C:\\boot.ini", ios::in);:创建了一个输入文件流对象 input,并打开名为 "C:\boot.ini" 的文件用于读取。ios::in 参数表示以输入模式打开文件。

  5. while (input && !input.eof()):使用 while 循环读取文件内容,条件是文件流对象 input 有效且未到达文件结尾。

  6. input.getline(buffer, 80);:使用 input.getline() 函数从文件中读取一行内容,存储到 buffer 中,最多读取 80 个字符。

  7. cout << buffer << endl;:将读取到的一行内容输出到屏幕上。

  8. input.close();:关闭文件流对象,释放文件资源。

整体来说,这段程序打开文件 "C:\boot.ini",逐行读取其内容并输出到屏幕上,直到文件结尾。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <fstream> 

void main()
{
char buffer[100];
ifstream input ("C:\\boot.ini", ios::in); // Open the file "C:\\boot.ini" for reading

while (input && !input.eof()) // Read lines from the file until the end of file is reached
{
input.getline(buffer, 80); // Read a line from the file into the buffer
cout << buffer << endl; // Output each line read from the file
}

input.close(); // Close the file stream
}

2) Find the errors in the following program and explain how to correct them.(5scores)

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
include <iostream.h> 
include <stdlib.h>
class CTest{

public:

CTest()

{ x=20;}

void use_this();

private:

int x;

}

void CTest::use_this()

{

CTest y,*pointer;

this=&y;

*this.x=10;

pointer=this;

pointer=&y; }

void main()

{

CTest y ; this->x=235;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# include <iostream.h>
# include <stdlib.h>
class CTest {
public:
CTest() { x=20; }
void use_this();
private:
int x;
}; // 在类定义末尾添加分号

void CTest::use_this() {
CTest y;
this->x=10; // 修正成员访问方式
CTest *pointer = &y; // 修正指针赋值语句
} // 修正函数体结束位置

void main()
{
CTest y;
} // main函数结束位置



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
#include <iostream> 

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

*** ### 4) Finish the definition and implement of class CTest。(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
#include <iostream> 

using namespace std;

class CTest {
private:
int x, y;
public:
CTest(int n1, int n2) { x = n1; y = n2; }

CTest operator++(int); // 后置递增运算符重载声明

void print() { cout << "x=" << x << " y=" << y << endl; }
};

// 后置递增运算符重载实现
CTest CTest::operator++(int)
{
CTest temp(*this); // 创建一个临时对象,保存原始对象的值
x++; // 对原始对象的 x 和 y 分别进行递增
y++;
return temp; // 返回保存原始对象值的临时对象
}

int main()
{
CTest d1(2, 3);

d1.print(); // 输出原始对象的值
d1++; // 使用后置递增运算符

d1.print(); // 输出递增后的对象的值

return 0;
}

解释:

  1. 在类 CTest 中声明了私有数据成员 xy,以及公有构造函数和重载的后置递增运算符 operator++(int)
  2. 后置递增运算符的重载实现中,首先创建一个临时对象 temp,用来保存原始对象的值。然后,对原始对象的 xy 分别进行递增操作。最后,返回保存原始对象值的临时对象。
  3. main() 函数中,首先创建一个 CTest 类型的对象 d1,并使用 print() 方法输出其初始值。然后使用后置递增运算符对 d1 进行递增操作,并再次使用 print() 方法输出递增后的值。

2. For each of the following, show the output25 scores, each 5 scores)**

1.

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
#include <iostream> 
using namespace std;

struct list
{
int data;
list* next;
};

list* head = NULL; // 初始化头指针为空指针

list* insert(int num)
{
list* s = new list; // 创建新节点
s->data = num;
s->next = NULL;

if (head == NULL || head->data > s->data)
{ // 如果链表为空或插入的元素值小于头节点的值
s->next = head;
head = s;
return head;
}

list* q = head;
list* p = head->next;
while (p != NULL)
{ // 遍历链表找到插入位置
if (p->data > s->data)
{
s->next = p;
q->next = s;
return head;
}
q = p;
p = p->next;
}
q->next = s;
return head;
}

void showlist(const list* head) {
cout << "now the items of list are: \n";
while (head)
{
cout << head->data << '\t';
head = head->next;
}
cout << endl;
}

int main() {
int k[5] = {2, 9, 1, 6, 4};
for (int i = 0; i < 5; i++) {
head = insert(k[i]);
}
showlist(head);
return 0;
}

这个程序创建了一个链表,然后通过插入排序的方式将数组 k 中的元素插入到链表中,最后输出链表的内容。

输出是now the items of list are: 1 2 4 6 9


2).

能看懂Helloworld应该可以看懂这个

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
#include <iostream>

using namespace std;

class BASE {
public:
void get(int i, int j, int k, int l) {
a = i; b = j; x = k; y = l;
}

void print() {
cout << "a = " << a << '\t' << "b = " << b << '\t' << "x = " << x << '\t' << "y = " << y << endl;
}

protected:
int a, b;
int x, y;
};

class A: public BASE {
public:
void get(int i, int j, int k, int l) {
BASE::get(50, 60, 70, 80);
BASE::print(); // Note: Calling base class method to print values from obj3
a = i; b = j; x = k; y = l;
u = a + b + BASE::a; // Accessing a from the BASE class
v = y - x + BASE::b; // Accessing b from the BASE class
}

void print() {
cout << "a = " << a << '\t' << "b = " << b << '\t' << "x = " << x << '\t' << "y = " << y << endl;
cout << "u = " << u << '\t' << "v = " << v << endl;
}

private:
int u, v;
};

int main() {
BASE obj1;
A obj2;

obj1.get(6, 9, 8, 7);
obj2.get(8, 3, 5, 6);

obj1.print();
obj2.print();

return 0;
}

输出结果为:

1
2
3
4
a = 6    b = 9    x = 8    y = 7
a = 50 b = 60 x = 70 y = 80
a = 8 b = 3 x = 5 y = 6
u = 68 v = -59

3).

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
#include <iostream>

using namespace std;

class BASE {
public:
virtual void getxy(int i, int j = 0) { x = i; y = j; }
virtual void fun() = 0;

protected:
int x, y;
};

class A: public BASE {
public:
void fun() override {
cout << "x = " << x << '\t' << "y = x * x = " << x * x << endl;
}
};

class B: public BASE {
public:
void fun() override {
cout << "x = " << x << '\t' << "y = " << y << endl;
cout << "y = x / y = " << x / y << endl;
}
};

int main() {
BASE *pb;
A obj1;
B obj2;

pb = &obj1;
pb->getxy(10);
pb->fun();

pb = &obj2;
pb->getxy(80, 5);
pb->fun();

return 0;
}

输出结果为:

这个也是模拟

1
2
3
x = 10   y = x * x = 100
x = 80 y = 5
y = x / y = 16

4).

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
#include <iostream>

using namespace std;

int main() {
double x = 123.456;

cout.width(10);
cout.setf(ios::dec, ios::basefield);
cout << x << endl;

cout.setf(ios::left);
cout << x << endl;

cout.width(15);
cout.setf(ios::right, ios::left);
cout << x << endl;

cout.setf(ios::showpos);
cout << x << endl;
cout << -x << endl;

cout.setf(ios::scientific);
cout << x << endl;

return 0;
}

输出结果为:

1
2
3
4
5
6
    123.456
123.456
123.456
+123.456
-123.456
1.234560e+02

这个程序演示了如何使用 cout 对象进行格式化输出。下面是对程序的解释:

  1. cout.width(10); 设置输出宽度为10个字符,不足的部分会使用填充字符填充,默认为空格。
  2. cout.setf(ios::dec, ios::basefield); 设置输出的数值以十进制格式显示。
  3. cout << x << endl; 输出变量 x 的值,并换行。
    • 结果是:123.456(因为宽度为10,而输出的数字占据了7个字符,所以前面填充了3个空格)。
  4. cout.setf(ios::left); 设置左对齐输出。
  5. cout << x << endl; 再次输出变量 x 的值,并换行。
    • 结果是:123.456(左对齐,宽度为10,所以输出没有填充字符)。
  6. cout.width(15); 设置输出宽度为15个字符。
  7. cout.setf(ios::right, ios::left); 设置右对齐输出。
  8. cout << x << endl; 再次输出变量 x 的值,并换行。
    • 结果是:123.456(右对齐,宽度为15,所以前面填充了9个空格)。
  9. cout.setf(ios::showpos); 设置显示正号。
  10. cout << x << endl; 输出变量 x 的值,并换行。
    • 结果是:+123.456(因为设置了显示正号,所以正数前面会显示+)。
  11. cout << -x << endl; 输出变量 -x 的值,并换行。
    • 结果是:-123.456(负数默认显示负号)。
  12. cout.setf(ios::scientific); 设置科学计数法输出。
  13. cout << x << endl; 再次输出变量 x 的值,并换行。
    • 结果是:1.234560e+02(以科学计数法显示 x 的值)。

5).

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
#include <iostream>

using namespace std;

class Bclass {
public:
Bclass(int i, int j)
{
x = i;
y = j;
}
virtual int fun() { return 0; }
protected:
int x, y;
};

class Iclass : public Bclass
{
public:
Iclass(int i, int j, int k) : Bclass(i, j)
{
z = k;
}
int fun() override { return (x + y + z) / 3; }
private:
int z;
};

int main() {
Iclass obj(2, 4, 10);

// Slicing occurs here
Bclass p1 = obj;
cout << p1.fun() << endl; // This will output 0, as p1 is a Bclass object

// Reference to base class
Bclass& p2 = obj;
cout << p2.fun() << endl; // This will output the result of Iclass::fun()

cout << p2.Bclass::fun() << endl; // This will also output the result of Iclass::fun()

// Pointer to base class
Bclass* p3 = &obj;
cout << p3->fun() << endl; // This will output the result of Iclass::fun()

return 0;
}

输出结果为:

1
2
3
4
0
5
5
5

在这个程序中:

  1. 创建了一个 Iclass 的对象 obj,传入参数为 2, 4, 10
  2. 对象 obj 被切片为 Bclass 类型的对象 p1。因为对象切片的原因,p1 只能调用 Bclass 类的成员函数,所以输出为0。
  3. 创建了一个 Bclass 类型的引用 p2 指向 obj,所以调用 p2.fun() 时,会调用 Iclass 类中的 fun() 函数,输出结果为5。
  4. 使用作用域解析运算符 :: 调用 Bclass 类中的 fun() 函数,结果也为5。
  5. 创建了一个指向 Bclass 类型的指针 p3 指向 obj,因为 fun() 是虚函数,所以会调用 Iclass 类中的 fun() 函数,输出结果为5。

4、Create a class RationaNumber(fractions) with the following capabilities: (12 scores)

  1. Enable input and output of fractions through the overloaded >> and << operators.
  2. Create a constructor that prevents a 0 denominator in a fraction.
  3. Overloaded the addition operator (+), subtraction operator (-) for this class.
    以下为完整代码
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
79
80
81
82
83
84
85
86
87
88
89
#include <iostream>
using namespace std;

class RationalNumber {
private:
int nomi, denom;

public:
RationalNumber(int n = 0, int d = 1) {
if (d == 0)
{
cout << "Error: Denominator cannot be zero!" << endl;
exit(1);
}
nomi = n;
denom = d;
}

friend RationalNumber& operator+(RationalNumber&, RationalNumber&);
friend RationalNumber& operator-(RationalNumber&, RationalNumber&);
friend ostream& operator<<(ostream&, RationalNumber&);
friend istream& operator>>(istream&, RationalNumber&);
};

RationalNumber& operator+(RationalNumber& r1, RationalNumber& r2) {
RationalNumber result;
result.denom = r1.denom * r2.denom;
result.nomi = r1.nomi * r2.denom + r2.nomi * r1.denom;

// Simplify fraction
if (result.denom % result.nomi == 0 || result.nomi % result.denom == 0) {
int quot = (result.denom / result.nomi == 0) ? result.nomi / result.denom : result.denom / result.nomi;
result.denom /= quot;
result.nomi /= quot;
}

return result;
}

RationalNumber& operator-(RationalNumber& r1, RationalNumber& r2) {
RationalNumber result;
result.denom = r1.denom * r2.denom;
result.nomi = r1.nomi * r2.denom - r2.nomi * r1.denom;

// Simplify fraction
if (result.denom % result.nomi == 0 || result.nomi % result.denom == 0) {
int quot = (result.denom / result.nomi == 0) ? result.nomi / result.denom : result.denom / result.nomi;
result.denom /= quot;
result.nomi /= quot;
}

return result;
}

ostream& operator<<(ostream& out, RationalNumber& r) {
out << r.nomi << "/" << r.denom;
return out;
}

istream& operator>>(istream& in, RationalNumber& r) {
cout << "Enter numerator: ";
in >> r.nomi;
cout << "Enter denominator: ";
in >> r.denom;

if (r.denom == 0) {
cout << "Error: Denominator cannot be zero!" << endl;
exit(1);
}

return in;
}

int main() {
RationalNumber r1, r2, result;
cout << "Enter the first fraction:" << endl;
cin >> r1;
cout << "Enter the second fraction:" << endl;
cin >> r2;

result = r1 + r2;
cout << "Sum: " << result << endl;

result = r1 - r2;
cout << "Difference: " << result << endl;

return 0;
}

有理数的运算,理解一下就好了(大概) ***

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、派生类TwoDimensionShapeThreeDimensionShape,以及一些具体的形状类,如CircleSquareSphereCube。每个二维形状类将包含一个getArea函数,每个三维形状类将包含getAreagetVolume函数。程序将使用std::vector来存储这些形状对象,并确定每个形状是二维还是三维的,然后显示其面积和体积。

代码实现

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#include <iostream>
#include <vector>
#include <memory>
#include <cmath>

using namespace std;

// 抽象基类 Shape
class Shape {
public:
virtual ~Shape() = default;
virtual void display() const = 0;
};

// 抽象基类 TwoDimensionShape
class TwoDimensionShape : public Shape {
public:
virtual double getArea() const = 0;
void display() const override {
cout << "Area: " << getArea() << endl;
}
};

// 抽象基类 ThreeDimensionShape
class ThreeDimensionShape : public Shape {
public:
virtual double getArea() const = 0;
virtual double getVolume() const = 0;
void display() const override {
cout << "Surface Area: " << getArea() << endl;
cout << "Volume: " << getVolume() << endl;
}
};

// 具体类 Circle
class Circle : public TwoDimensionShape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
double getArea() const override {
return M_PI * radius * radius;
}
};

// 具体类 Square
class Square : public TwoDimensionShape {
private:
double side;
public:
Square(double s) : side(s) {}
double getArea() const override {
return side * side;
}
};

// 具体类 Sphere
class Sphere : public ThreeDimensionShape {
private:
double radius;
public:
Sphere(double r) : radius(r) {}
double getArea() const override {
return 4 * M_PI * radius * radius;
}
double getVolume() const override {
return (4.0/3) * M_PI * pow(radius, 3);
}
};

// 具体类 Cube
class Cube : public ThreeDimensionShape {
private:
double side;
public:
Cube(double s) : side(s) {}
double getArea() const override {
return 6 * side * side;
}
double getVolume() const override {
return side * side * side;
}
};

int main() {
vector<shared_ptr<Shape>> shapes;

// 创建一些形状对象并将其添加到 vector 中
shapes.push_back(make_shared<Circle>(5.0));
shapes.push_back(make_shared<Square>(4.0));
shapes.push_back(make_shared<Sphere>(3.0));
shapes.push_back(make_shared<Cube>(2.0));

// 遍历 vector 并显示每个形状的面积和体积
for (const auto& shape : shapes) {
shape->display();
}

return 0;
}

解释

  1. 基类和派生类
    • Shape 是一个抽象基类,定义了一个纯虚函数 display
    • TwoDimensionShapeThreeDimensionShape 分别是从 Shape 派生出来的抽象类,定义了各自特有的纯虚函数 getAreagetVolume
  2. 具体形状类
    • CircleSquare 是从 TwoDimensionShape 派生出来的具体类,分别实现了 getArea 函数。
    • SphereCube 是从 ThreeDimensionShape 派生出来的具体类,分别实现了 getAreagetVolume 函数。
  3. 主函数
    • 使用 std::vector<std::shared_ptr<Shape>> 来存储各种形状对象。
    • 创建具体的形状对象,并将其添加到 shapes 向量中。
    • 遍历 shapes 向量,并调用每个形状对象的 display 函数来输出其面积和体积。

通过这种方式,我们实现了形状层次结构,并能够正确地确定每个形状是二维还是三维的,并显示其相应的面积和体积。