C++高级程序设计2024 期末回忆

一 简答题(共12题,每题5分,共60分)

1. 解释C++内存管理的机制,并说明栈、堆、静态存储区的区别。

答:C++中的内存管理分为栈、堆和静态存储区: 栈用于存储局部变量,由编译器自动分配和释放,生命周期短,访问快;堆用于动态分配内存,由程序员手动管理,适合大块数据,生命周期由程序控制;静态存储区存储全局变量和静态变量,整个程序生命周期内有效,访问速度与栈相当。

2. 简要解释指针和数组的关系,并说明如何通过指针操作数组元素。

答:数组名相当于指向其首元素的指针。通过指针偏移可以访问数组元素,使用*(指针+偏移量)解引用操作具体的元素。

3. 虚函数是如何支持多态性的?虚函数被调用时,如何确保正确的函数被调用?

答:虚函数通过动态绑定支持多态性。当基类指针或引用指向派生类对象时,调用虚函数时由虚函数表和虚表指针机制决定具体调用哪个函数。每个类有一个虚函数表,记录函数地址,派生类会覆盖基类的对应入口。调用虚函数时,通过对象的虚表指针定位到正确的函数地址,确保调用派生类的实现。

4. 隐式类型转换和显示类型转换(强制转换)的区别?

答:隐式类型转换由编译器自动完成,适用于兼容类型,但可能引发精度丢失或逻辑错误;显式类型转换由程序员通过特定语法主动指定,提供更高的控制权,但需要自行确保转换合理性。

5. 什么是RAII原则?举例说明RAII如何在C++中实现资源管理,并解释这一原则如何帮助简化异常安全的编程。

答:RAIl(ResourceAcquisitionIsInitialization,资源获取即初始化)原则指资源的获取和释放与对象的生命周期绑定。通过构造函数获取资源,析构函数释放资源。RAII在异常发生时通过自动调用析构函数释放资源,避免资源泄漏,从而简化异常安全的编程。

6. 什么是对象切片?给出一个对象切片实例,说明产生的原因及解决办法。

答:对象切片是指将派生类对象赋值给基类对象时,派生类特有的成员和行为被“切掉“只保密基类部分。其原因是赋值操作按值拷贝,只处理基类部分。解决方法是使用基类指针或引用代替按值传递。

7. 内联函数和宏的区别?内联函数的优点和缺点?

答:内联函数由编译器处理,提供类型检查和调试支持,而宏由预处理器展开,不进行类型检查。内联函数的优点是提高运行效率和增强安全性,但缺点是可能导致代码膨胀和编译时间增加。

8. 解释拷贝构造函数与移动构造函数的作用与区别。在什么情况下调用拷贝构造函数而非移动构造函数?

答:拷贝构造函数通过深拷贝创建对象副本,移动构造函数通过转移资源避免深拷贝以提升性能。当源对象无法被修改(如const对象)或不支持移动语义时,会调用拷贝构造函数而非移动构造函数。

9. C++如何解决多重继承中的“菱形继承”问题?

答:C++通过虚拟继承解决“菱形继承”中基类重复的问题。使用virtual关键字声明继承关系,确保派生类共享基类的唯一实例,从而避免重复定义和访问冲突。

10. 解释构造函数中成员初始化列表的作用。与构造函数体内的赋值语句相比,成员初始化列表的优势是什么?

答:成员初始化列表用于在对象构造时直接初始化成员变量。相比于构造函数体内的赋值语句,它避免了默认构造后再赋值的额外开销,且必须用于const成员、引用成员或没有默认构造函数的成员变量。

11. 解释constexpr关键字的作用以及与传统常量const的区别。

答:constexpr用于定义在编译期即可确定值的常量,与const相比,constexpr保证了表达式在编译期求值,支持常量表达式的优化;而const只保证值不可修改,但不强制编译期求值。

12. 比较Lambda函数与函数对象的异同。

答:Lambda函数和函数对象都可以表示可调用对象,但Lambda 函数是轻量级的匿名函数,定义更简洁;函数对象是通过类重载operator()实现,适合复杂逻辑。Lambda函数支持直接捕获变量。

程序理解题(共10题,每题3分,共30分)

请仔细阅读代码,并判断代码是否存在编译错误(不包含警告)。若有错误,请指出错误的代码和错误原因;若无错误,请写出代码运行结果。注意,本节所有题目的代码,均已#include<iostream>。

第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
class A {
public:
A(int x): a(x){}
virtual ~A() f std::cout << "A destructor\n";}
void show() { std::cout << a << std::endl; }
private:
int a;
};

class B : public A {
public:
B(int x, int y) : A(x), b(y) {}
~B() { std::cout << "B destructor\n";}
void show() { std::cout << b << std::endl;}
private:
int b;
};

int main() {
A* ptr = new B(1, 2);
ptr->show();
delete ptr;
return 0;
}


第2题

1
2
3
4
5
6
7
8
9
10
11
12
void func(int a, int b = 10){
std::cout << a + b << std::endl;
}

void func(int a, double b = 20.5){
std::cout << a + b << std::endl;
}

int main(){
func(5);
return 0;
}

第3题

1
2
3
4
5
6
7
8
9
void f (int&& x){
std::cout << x << std::endl;
}
int main(){
int a= 10;
f(std::move(a));
std::cout << a << std::endl;
return 0;
}

第4题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class A {
public:
A() { std::cout << "A's constructor" << std::endl; }
~A() { std::cout << "A's destructor" << std::endl; }
};

void fun(){
A a;
throw 1;
}

int main(){
try {
fun();
} catch (...) {
std::cout << "Exception caught" << std::endl:
}
return 0;
}

第5题

1
2
3
4
5
6
int main()(
char *str = "hello";
str[0]= 'H';
std::cout << str;
return 0;
}

第6题

1
2
3
4
5
6
7
int main(){
int x=10,y=20;
int * const ptr = &x;
*ptr =y;
std::cout << *ptr;
return 0;
}

第7题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Base {
public:
Base() {std::cout << "Base constructor called" << std::endl;}
virtual ~Base() {std::cout << "Base destructor called" << std::endl;)
virtual void fun() const = 0;
}

class Derived : public Base {
public:
Derived() {std::cout << "Derived constructor called" << std::endl;}
Derived() {std::cout << "Derived destructor called" << std::endl;}
void fun() override {std::cout << "Derived::fun called" << std::endl;}
};

int main(){
Derived d;
d.fun();
return 0;
}

第8题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ShapeErrors { }:
class CireleError : public ShapeErrors {};
int main() {
CireleError e,
try{
throw e;
}
cateh (ShapeErrors &e){
std::cout << "ShapeErrors" << std::endl;
}
cateh (CireleError &e){
std::cout << "CireleError" << std::endl;
}
return 0;
}

第9题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Animal {
public:
void move() {std::cout << "move" <<std::endl;}
};
class Bird: protected Animal{
public:
void fly() {move();std::cout << "fly" << std::endl;}
}

int main(){
Bird b;
b.fly();
b.move();
return 0;
}

第10题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
class Vehicle {
public:
virtual void drive() {std::cout << "vehicle is driving" << std::endl;}
};
class Car : public Vehicle {
public:
void drive() override {std::cout << "Car is driving" << std::endl;})
};

template <typename T>
void operate(T t){t.drive();}

int main(){
Car car;
operate<Car>(car);
return 0;
}

解答

1.答:正确

1
2
3
1
B destructor
A destructor

2.答:错误。函数 fnc 存在二义性,无法解析调用 func(5)时匹配的版本。

3.答:正确

1
2
10
10

4.答:正确

1
2
3
A's constructor
A's destructor
Exception caught

5.答:错误。指针指向字符串字面量。

6.答:正确。20

7.答:错误。fun()不是const,无法override。

8.答:正确:ShapeErrors

9.答:错误。b.move()不能访问

10.答:正确。Car is driving

三、编程题(共1题,共10分)

第1题

定义一个用于描述二维矩形的类 Rectangle2D 和三维长方体的类 Cuboid3D,要求如下:
Rectangle2D 类:

  1. 包含两个整型成员变量 length 和 width ,用于描述矩形的长和宽。

  2. 重载乘法 * 运算符:将矩形的长和宽分别与给定的整数相乘,返回一个新的 Rectangle2D 对象。

  3. 重载输出流运算符 << ,用于输出矩形的各个尺寸值,格式为 [Rectangle:Length=X,Width = Y]。

Cuboid3D 类:

  1. 继承自 Rectangle2D,并增加一个整型成员变量 height,用于描述长方体的高。
  2. 重载乘法 * 运算符:将长方体的长、宽、高分别与给定的整数相乘,返回一个新的 Cuboid3D 对象。
  3. 重载输出流运算符 << ,用于输出长方体的各个尺寸值,格式为[Cuboid:Length=X,Width = Y, Height = Z]。

解答

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>

class Rectangle2D {
protected:
double length, width:

public:
Rectangle2D(double l = 1. double w = 1) : length(l), width(w){}

Rectangle2D operator*(int multiplier) const {
return Rectangle2D(length * multiplier, width * multiplier);
}

virtual void display(std::ostream& out) const {
out << "Length = " << length << ", Width = " << width:
}
};

class Cuboid3D : public Rectangle2D {
private:
double height;

public:
Cuboid3D(double l = 1, double w = 1. double h = 1): Rectangle2(l, w), height(h){}

Cuboid3D operator*(int multiplier) const {
return Cuboid3D(length * multiplier, width * multiplier, height * multiplier);
}

void display(std::ostream& out) const override {
Rectangle2D::display(out);
out << ", Height = " << height;
}
};

std::ostream& operator<<(std:;ostream& out, const Rectangle2D& rect) {
rect.display(out);
return out;
}

int main() {
Rectangle2D rect2D(4, 5);
Cuboid3D cuboid3D(3, 6, 7);
std::cout << "Rectangle2D: " << rect2D << std::endl;
std::cout << "Rectangle3D: " << cuboid3D << std::endl;
return 0;
}

注:operator<<无法在每个类中重载,要在全局范围内重载,然后Rectangle2D和Cuboid3D分别重载其中的display。