一 简答题(共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.答:正确
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 类:
包含两个整型成员变量 length 和 width ,用于描述矩形的长和宽。
重载乘法 * 运算符:将矩形的长和宽分别与给定的整数相乘,返回一个新的 Rectangle2D 对象。
重载输出流运算符 << ,用于输出矩形的各个尺寸值,格式为 [Rectangle:Length=X,Width = Y]。
Cuboid3D 类:
- 继承自 Rectangle2D,并增加一个整型成员变量 height,用于描述长方体的高。
- 重载乘法 * 运算符:将长方体的长、宽、高分别与给定的整数相乘,返回一个新的 Cuboid3D 对象。
- 重载输出流运算符 << ,用于输出长方体的各个尺寸值,格式为[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。