《面向对象程序设计》复习课课堂回顾
最后一个是图书馆里模拟借阅系统,老师说自己回去要改一改,这是否意味着会在卷子中体现
封装 设计类 必考
class xx{
public:
private:
};
必考:
构造函数(默认形参值)
拷贝构造函数
display()
setData()
重载 =运算符 浅拷贝,指针 new的那个
继承
一个父类 三个子类
抽象基类:纯虚函数
子类 重写
运算符重载
必考:+ - << >> 老师说为了减轻复习负担,统一友元friend 但是题目也可能会指定用成员函数(可能不是运算符重载这个题)
复习策略:重点友元重载
然后是隐式的结合线性代数、离散数学、数据结构(明确指出考察线性表,估计就是new动态内存分配)
力的合成和分解(就是向量,坐标,求合力的大小)
二维
三维:长方体,球体,圆柱体
要结合工程背景
然后选择题,全是课件上的
论述题有题库
- 给出工程背景,构造向量类,重载+ - 然后求合力的长度
老师给的二维的,我们来个三维的
综合设计题:空间力学向量与运算符重载 (20分)
【工程背景】在进行 FDM 3D 打印建筑结构模型的设计与支撑强度测试时,模型的承重节点通常会受到多个方向的空间力作用。例如,挤出头带来的物理推力、材料冷却收缩产生的拉拔力、以及模型自身的重力等。为了确保打印过程中悬空结构不发生断裂或坍塌,工程软件需要将这些力抽象为三维空间向量,计算节点受到的总合力(力的合成),或者计算理论受力与实际受力之间的误差(力的分解/相减),并实时监测该受力的绝对大小(力的长度/模长)是否超过了材料的屈服极限。
【题目要求】请设计一个表示三维空间力的向量类 SpaceForce,具体要求如下:私有数据成员:double x, y, z; 分别表示力在三个坐标轴上的分量。构造函数:包含带有默认形参值的构造函数(默认值为0)。友元函数重载:为了统一规范,使用 friend 友元函数重载加法运算符 +(模拟力的合成)和减法运算符 -(模拟计算力的偏差)。成员函数:实现 double getMagnitude() 函数,用于计算并返回该力的绝对大小(即向量的模长,公式为 $F = \sqrt{x^2 + y^2 + z^2}$)。友元函数重载输入/输出:重载 << 以便直接打印力的大小。
主函数测试:在 main 函数中实例化两个力向量,求它们的合力,并输出合力的大小。
【标准参考答案与得分点解析】你可以先自己尝试在 IDE 里敲一遍,然后再对照下面的标准答案寻找盲点:
C++#include <iostream>
#include <cmath> // 必考点:求长度需要用到 sqrt,必须包含 cmath
using namespace std;
class SpaceForce {
private:
double x;
double y;
double z;
public:
// 1. 构造函数(带有默认形参值) - 必考点
SpaceForce(double x = 0.0, double y = 0.0, double z = 0.0) {
this->x = x;
this->y = y;
this->z = z;
}
// 2. 求合力的长度(向量的模)
double getMagnitude() const {
// 常成员函数,保证不修改对象本身
return sqrt(x * x + y * y + z * z);
}
// 3. 友元重载 + 运算符(模拟力的合成) - 老师强调的重点
friend SpaceForce operator+(const SpaceForce& f1, const SpaceForce& f2) {
return SpaceForce(f1.x + f2.x, f1.y + f2.y, f1.z + f2.z);
}
// 4. 友元重载 - 运算符(模拟力的偏差/分解)
friend SpaceForce operator-(const SpaceForce& f1, const SpaceForce& f2) {
return SpaceForce(f1.x - f2.x, f1.y - f2.y, f1.z - f2.z);
}
// 5. 友元重载 << 运算符(方便直接打印输出)
friend ostream& operator<<(ostream& os, const SpaceForce& f) {
os << "Force(" << f.x << ", " << f.y << ", " << f.z << ")";
return os;
}
};
int main() {
// 模拟工程场景:
// f1: 材料挤出推力
SpaceForce f1(10.5, 5.0, -2.0);
// f2: 冷却收缩拉拔力
SpaceForce f2(-3.5, 4.0, 8.0);
// 力的合成(触发重载的 + 运算符)
SpaceForce resultantForce = f1 + f2;
// 力的偏差(触发重载的 - 运算符)
SpaceForce errorForce = f1 - f2;
cout << "推力 f1: " << f1 << endl;
cout << "拉拔力 f2: " << f2 << endl;
cout << "------------------------" << endl;
cout << "节点总合力: " << resultantForce << endl;
cout << "总合力的大小(长度): " << resultantForce.getMagnitude() << " N" << endl;
cout << "两力的偏差: " << errorForce << endl;
return 0;
}
二维
没问题!如果是二维(2D)的场景,代码逻辑和三维几乎一模一样,仅仅是去掉了 $z$ 轴的分量,计算模长(合力大小)的公式退化为勾股定理:$F = \sqrt{x^2 + y^2}$。
在工程背景中,二维力学计算通常用于平面受力分析(例如 3D 打印机挤出头在 XY 平面上的移动拉力,或者步进电机的牵引力与皮带摩擦力的合成)。下面是为你准备的二维平面力 PlanarForce 类的标准答案代码。为了加深理解,代码之后我为你配置了一个直观的交互式演示工具,你可以亲自拖拽滑块,感受二维向量加减法在坐标系中的几何意义。二维平面力向量与运算符重载代码
#include <iostream>
#include <cmath> // 依然必不可少,用于 sqrt
using namespace std;
class PlanarForce {
private:
double x; // X轴方向的分力
double y; // Y轴方向的分力
public:
// 1. 构造函数(带有默认形参值)
PlanarForce(double x = 0.0, double y = 0.0) {
this->x = x;
this->y = y;
}
// 2. 求力的长度(向量的模,二维勾股定理)
double getMagnitude() const {
return sqrt(x * x + y * y);
}
// 3. 友元重载 + 运算符(平面力的合成)
friend PlanarForce operator+(const PlanarForce& f1, const PlanarForce& f2) {
return PlanarForce(f1.x + f2.x, f1.y + f2.y);
}
// 4. 友元重载 - 运算符(平面力的分解/偏差)
friend PlanarForce operator-(const PlanarForce& f1, const PlanarForce& f2) {
return PlanarForce(f1.x - f2.x, f1.y - f2.y);
}
// 5. 友元重载 << 运算符
friend ostream& operator<<(ostream& os, const PlanarForce& f) {
os << "Force2D(" << f.x << ", " << f.y << ")";
return os;
}
};
int main() {
// 模拟 XY 平面内的受力
PlanarForce f1(10.0, 5.0); // 电机牵引力
PlanarForce f2(-3.0, 4.0); // 皮带反向阻力
// 力的合成
PlanarForce resultant = f1 + f2;
cout << "牵引力 f1: " << f1 << endl;
cout << "阻力 f2: " << f2 << endl;
cout << "------------------------" << endl;
cout << "平面总合力: " << resultant << endl;
cout << "总合力大小: " << resultant.getMagnitude() << " N" << endl;
return 0;
}
2. 设计成绩记录表,记录成绩,支持动态扩容
```cpp
class ScoreList{
private:
double* scores; // 动态数组存储成绩
int count; // 成绩数量
int capacity; // 最多能容纳多少个,用于一开始不知道多少个,申请动态数组
public:
构造函数(初始化为0)
析构
拷贝构造函数深拷贝
重载赋值运算符
display()
addSocre(int socre)
getCount()
};
【背景描述】
在教务系统中,经常需要处理未知数量的学生成绩数据。为了有效管理内存,请你结合线性表的基础概念,设计一个支持动态扩容的成绩记录表类 ScoreList。该类需要在初始时可以不分配或分配极少的内存,而在不断录入新成绩(调用 addScore)导致当前容量不足时,能够自动向系统申请更大的连续内存空间,并确保在对象生命周期结束或发生复制时,严格遵守 C++ 的内存管理规范,避免出现“悬挂指针”或“内存泄漏”。
【具体设计要求】
请根据以下给定的类框架,补全 ScoreList 类的完整实现:
私有数据成员:
double* scores; // 动态数组指针,用于存储成绩。
int count; // 当前已存入的成绩实际数量。
int capacity; // 当前动态数组的最大容量。
构造与析构:
实现带有默认形参值的构造函数,使得不传参时初始 capacity 为 0,并将指针安全地初始化。
实现析构函数,正确释放动态申请的内存。
核心内存管理(Big Three)—— 必考重点:
实现拷贝构造函数,要求必须完成深拷贝。
重载赋值运算符 =,要求处理好自赋值检查、旧内存释放与深拷贝过程。
动态扩容机制(核心算法):
实现 void addScore(double score); 方法。
扩容规则: 每次添加新成绩时,若 count == capacity,则触发扩容。若初始容量为 0,则按容量为 2 开辟新数组;若不为 0,则按当前容量的 2 倍开辟新数组。必须将旧数据安全迁移至新数组,并释放旧数组内存。
常规功能:
实现 void display() const;,按顺序输出所有已录入的成绩。
实现 int getCount() const;,返回当前成绩数量。
【待补全的代码框架】
C++
include
using namespace std;
class ScoreList {
private:
double* scores;
int count;
int capacity;
public:
// 1. 构造函数(初始化)
ScoreList(int initialCapacity = 0);
// 2. 析构函数
~ScoreList();
// 3. 拷贝构造函数(深拷贝)
ScoreList(const ScoreList& other);
// 4. 重载赋值运算符
ScoreList& operator=(const ScoreList& other);
// 5. 打印成绩
void display() const;
// 6. 添加成绩(需支持动态扩容)
void addScore(double score);
// 7. 获取数量
int getCount() const;
};
#include <iostream>
using namespace std;
class ScoreList {
private:
double* scores; // 动态数组存储成绩
int count; // 当前实际成绩数量
int capacity; // 最多能容纳多少个
public:
// 1. 构造函数 (初始化)
// 默认容量设为0,指针置为空,严格遵守“初始化为0”的要求
ScoreList(int initialCapacity = 0) {
count = 0;
capacity = initialCapacity;
if (capacity > 0) {
scores = new double[capacity];
} else {
scores = nullptr; // 良好的指针初始化习惯
}
}
// 2. 析构函数 (释放动态内存)
~ScoreList() {
if (scores != nullptr) {
delete[] scores;
scores = nullptr; // 防止悬挂指针
}
}
// 3. 拷贝构造函数 (深拷贝 - 必考点)
ScoreList(const ScoreList& other) {
count = other.count;
capacity = other.capacity;
if (capacity > 0) {
scores = new double[capacity]; // 重新开辟一块独立的内存
for (int i = 0; i < count; i++) {
scores[i] = other.scores[i]; // 逐个元素赋值
}
} else {
scores = nullptr;
}
}
// 4. 重载赋值运算符 (深拷贝 - 必考点)
ScoreList& operator=(const ScoreList& other) {
if (this == &other) return *this;// 第一步:必须检查是否自赋值
delete[] scores; // 第二步:必须先释放原有的内存,防止内存泄漏
count = other.count;
capacity = other.capacity;
if (capacity > 0) {
scores = new double[capacity]; // 第三步:深拷贝
for (int i = 0; i < count; i++) {
scores[i] = other.scores[i];
}
} else {
scores = nullptr;
}
return *this; // 第四步:返回自身引用,支持 a = b = c 连等
}
// 5. 添加成绩并支持动态扩容 (核心难点)
void addScore(double score) {
// 如果当前数组已满(或者刚初始化容量为0),则需要扩容
if (count == capacity) {
// 扩容策略:如果容量为0则分配2个空间,否则容量翻倍 (类似STL底层机制)
int newCapacity = (capacity == 0) ? 2 : capacity * 2;
// a. 开辟一块更大的新内存
double* newScores = new double[newCapacity];
// b. 将老数据搬运到新内存中
for (int i = 0; i < count; i++) {
newScores[i] = scores[i];
}
// c. 释放老内存 (非常重要,否则内存泄漏)
delete[] scores;
// d. 让原指针指向新内存,并更新容量
scores = newScores;
capacity = newCapacity;
}
// 存入新成绩,数量+1
scores[count] = score;
count++;
}
// 6. 打印成绩
void display() const {
cout << "当前数量: " << count << ", 当前容量: " << capacity << " | 成绩: ";
if (count == 0) {
cout << "无数据";
} else {
for (int i = 0; i < count; i++) {
cout << scores[i]; // 先输出成绩数字
// 如果当前不是最后一个元素,则输出逗号进行分隔
if (i < count - 1) {
cout << ", ";
}
}
}
cout << endl;
}
// 7. 获取当前成绩数量
int getCount() const {
return count;
}
};
3 . 设计 抽象类,派生圆柱体,球体,正方体,求表面积和容积
【工程背景】在 3D 打印切片软件(如 Cura 或 PrusaSlicer)中,软件需要对用户导入的各种三维基础几何模型进行耗材预估。为了计算打印所需的时间和材料,软件必须计算每个模型的表面积(用于生成外壳 Shell)和容积/体积(用于计算内部填充 Material Volume)。现要求设计一个基于面向对象多态特性的程序,统一管理各种不同的几何实体(正方体、球体、圆柱体)。
【具体设计要求】设计抽象基类 Solid(空间几何体):包含至少两个纯虚函数:求表面积 getSurfaceArea() 和求容积 getVolume()。必须包含一个虚析构函数,以确保通过基类指针释放子类对象时不会发生内存泄漏。派生三个具体子类:Cube(正方体): 包含私有成员 side(边长)。Sphere(球体): 包含私有成员 radius(半径)。Cylinder(圆柱体): 包含私有成员 radius(底面半径)和 height(高)。主函数测试(多态的体现):在 main 函数中,定义一个 Solid* 类型的指针数组,分别指向动态创建的 Cube、Sphere 和 Cylinder 对象。利用循环遍历该指针数组,调用统一的接口输出各自的表面积和容积。最后,正确释放所有动态分配的内存。(注:圆周率 $\pi$ 可取 3.14159)
【标准参考答案与得分点解析】强烈建议你自己先在草稿纸上写出类的继承结构和虚函数定义,然后再核对以下代码。C++#include
#include <cmath>
using namespace std;
const double PI = 3.14159;
// 1. 抽象基类 Solid
class Solid {
public:
// 【踩分点1】纯虚函数,类体中必须写 = 0
virtual double getSurfaceArea() const = 0;
virtual double getVolume() const = 0;
// 【踩分点2】虚析构函数(极其重要!)
virtual ~Solid() {
// 基类虚析构,保证 delete 基类指针时能正确调用子类析构
}
};
// 2. 派生类:正方体 Cube
class Cube : public Solid {
private:
double side; // 边长
public:
Cube(double s = 0.0) : side(s) {}
// 重写表面积:6 * a^2
double getSurfaceArea() const override {
return 6.0 * side * side;
}
// 重写容积:a^3
double getVolume() const override {
return side * side * side;
}
};
// 3. 派生类:球体 Sphere
class Sphere : public Solid {
private:
double radius; // 半径
public:
Sphere(double r = 0.0) : radius(r) {}
// 重写表面积:4 * PI * r^2
double getSurfaceArea() const override {
return 4.0 * PI * radius * radius;
}
// 重写容积:(4/3) * PI * r^3
// 【踩分点3】C++整数除法陷阱!绝不能写成 4 / 3,必须写 4.0 / 3.0
double getVolume() const override {
return (4.0 / 3.0) * PI * pow(radius, 3);
}
};
// 4. 派生类:圆柱体 Cylinder
class Cylinder : public Solid {
private:
double radius; // 底面半径
double height; // 高度
public:
Cylinder(double r = 0.0, double h = 0.0) : radius(r), height(h) {}
// 重写表面积:2个底面积 + 侧面积 (2*PI*r*h + 2*PI*r^2)
double getSurfaceArea() const override {
return 2.0 * PI * radius * height + 2.0 * PI * radius * radius;
}
// 重写容积:底面积 * 高 (PI*r^2 * h)
double getVolume() const override {
return PI * radius * radius * height;
}
};
// 主函数:测试运行时多态
int main() {
// 【踩分点4】基类指针数组,体现多态的核心用法
Solid* shapes[3];
// 使用 new 动态分配子类对象,向上转型(Upcasting)为基类指针
shapes[0] = new Cube(10.0); // 边长为10的正方体
shapes[1] = new Sphere(5.0); // 半径为5的球体
shapes[2] = new Cylinder(5.0, 10.0); // 半径5,高10的圆柱体
cout << "--- 3D打印耗材预估系统 ---" << endl;
// 利用循环统一调用,体现“同一个接口,不同的实现”
for (int i = 0; i < 3; i++) {
cout << "模型 " << i + 1 << ":" << endl;
// 编译器会根据指针实际指向的对象类型,动态查找虚函数表(vtable)
cout << " 表面积 (外壳): " << shapes[i]->getSurfaceArea() << " 平方毫米" << endl;
cout << " 体积 (填充): " << shapes[i]->getVolume() << " 立方毫米" << endl;
cout << "--------------------------" << endl;
}
// 【踩分点5】释放内存。如果 Solid 没有虚析构函数,这里只会调用 Solid 的析构,导致子类内存泄漏!
for (int i = 0; i < 3; i++) {
delete shapes[i];
}
return 0;
}
【工程背景】某科创企业需要开发一个简易的员工年度绩效考核模块。该模块需要记录员工的姓名以及百分制考核分数,并根据分数自动计算员工的绩效等级(卓越、良好、合格、待改进、不合格),同时判断该员工是否通过了年度考核(满足合格线即为通过)。
【具体设计要求】设计 EmployeeRating 类:私有数据成员: string employeeName(姓名),float rating(分数)。信息设置函数: 实现 void setEmployeeInfo(string name, float r)。要求具有数据安全边界检查:若传入的分数小于 0,则将分数强行置为 0;若大于 100,则强行置为 100;否则正常赋值。等级评定函数: 实现 string getRatingGrade()。评级标准如下:$[90, 100]$:卓越$[80, 90)$:良好$[70, 80)$:合格$[60, 70)$:待改进其它(即 $<60$):不合格考核状态函数: 实现 bool isQualified()。只要分数大于等于 60 分,即返回 true(通过),否则返回 false。输出函数: 实现 void display(),打印员工的姓名、分数、等级以及是否通过考核。主函数测试(结合对象数组):在 main 函数中创建一个包含 3 个员工的对象数组。利用循环为这 3 个员工录入信息(包含一个超过 100 分的异常数据以测试边界拦截)。遍历数组调用 display() 显示所有人信息。
【标准参考答案与得分点解析】
C++#include <iostream>
#include <string>
using namespace std;
class EmployeeRating {
private:
string employeeName;
float rating;
public:
// 默认构造函数(考试时养成写构造函数的好习惯)
EmployeeRating(string name = "未命名", float r = 0.0) {
setEmployeeInfo(name, r); // 直接复用 set 函数的安全校验逻辑
}
// 1. 设置员工信息(带边界安全校验)
void setEmployeeInfo(string name, float r) {
employeeName = name;
if (r < 0) {
rating = 0;
} else if (r > 100) {
rating = 100;
} else {
rating = r;
}
}
// 2. 获取绩效等级
// 【踩分点1】不修改成员变量的函数,强烈建议加上 const
string getRatingGrade() const {
if (rating >= 90 && rating <= 100) {
return "卓越";
} else if (rating >= 80 && rating < 90) {
return "良好";
} else if (rating >= 70 && rating < 80) {
return "合格";
} else if (rating >= 60 && rating < 70) {
return "待改进";
} else {
return "不合格";
}
}
// 3. 判断是否合格
bool isQualified() const {
// 修复了你原代码中的拼写错误 socre -> rating
return rating >= 60;
}
// 4. 显示信息
void display() const {
cout << "姓名: " << employeeName
<< "\t| 分数: " << rating
<< "\t| 等级: " << getRatingGrade()
<< "\t| 状态: " << (isQualified() ? "通过" : "未通过")
<< endl;
}
};
int main() {
cout << "--- 员工绩效考核系统 ---" << endl;
// 【踩分点2】对象数组的创建与使用
EmployeeRating employees[3];
// 测试常规数据
employees[0].setEmployeeInfo("张三", 85.5);
// 测试负数边界拦截
employees[1].setEmployeeInfo("李四", -15.0);
// 测试超出100分边界拦截
employees[2].setEmployeeInfo("王五", 120.0);
// 遍历输出结果
for (int i = 0; i < 3; i++) {
employees[i].display();
}
return 0;
}
【业务背景】
图书馆信息管理系统中,每一笔借阅记录(BookLoan)都需要一个全局唯一且连续递增的流水号(借阅 ID)。为了实现这一功能,系统要求使用面向对象中的静态数据成员来跟踪下一个可用的流水号。同时,系统需要记录借阅的状态,并处理不同数据类型之间的转化。
【具体设计要求】
请根据给出的类骨架,补全 BookLoan 类的设计:
完善私有属性: 包含静态变量 nextLoanID,普通变量 loanId(借阅单号),borrower(借阅人),bookTitle(书名),duration(借阅时长,字符串格式),status(状态,包含“借阅中”、“已归还”、“已逾期”三种)。
静态变量初始化: 在类外部将 nextLoanID 的初始值设置为 24045000。
完善构造函数:
使用初始化列表初始化 borrower 和 bookTitle。
在函数体内部,将当前的 nextLoanID 赋值给 loanId,并让 nextLoanID 自增。
将传入的整型 days 转换为字符串附加“天”字后赋给 duration(例如传入 30,存储为 "30天")。
将初始 status 设置为 "借阅中"。
状态修改与输出:
提供 void returnBook() 函数,将状态修改为 "已归还"。
提供 void display() const 函数,格式化打印该条借阅记录的详细信息。
【标准参考答案与得分点解析】
C++
#include <iostream>
#include <string>
using namespace std;
class BookLoan {
private:
static int nextLoanID; // 静态成员:自动生成借阅ID
int loanId; // 当前记录的ID
string borrower; // 借阅人
string bookTitle; // 书名
string duration; // 借阅时长 (要求为string类型)
string status; // 借阅状态 (借阅中 / 已归还 / 已逾期)
public:
// 1. 构造函数
// 【踩分点1】初始化列表的使用
BookLoan(string borrowerName, string bookTitle, int days)
: borrower(borrowerName), bookTitle(bookTitle) {
// 【踩分点2】静态变量自增赋给普通变量,实现流水号自动生成
loanId = nextLoanID++;
// 【踩分点3】类型转换:将 int 转换为 string
duration = to_string(days) + "天";
// 初始业务状态
status = "借阅中";
}
// 2. 归还图书(修改状态)
void returnBook() {
status = "已归还";
}
// 3. 打印信息
void display() const {
cout << "借阅单号: " << loanId
<< " | 借阅人: " << borrower
<< " | 书名: 《" << bookTitle << "》"
<< " | 时长: " << duration
<< " | 状态: " << status << endl;
}
};
// 【致命踩分点4】静态成员变量必须在类外部进行初始化!绝对不能漏!
int BookLoan::nextLoanID = 24045000;
int main() {
cout << "--- 图书馆借阅系统 ---" << endl;
// 创建第一条借阅记录
BookLoan loan1("陈仓宜", "C++ Primer Plus", 30);
loan1.display();
// 创建第二条借阅记录(测试ID是否会自动递增)
BookLoan loan2("张三", "计算机组成原理", 14);
loan2.display();
cout << "\n--- 办理还书手续 ---" << endl;
loan1.returnBook();
loan1.display();
return 0;
}
浙公网安备 33010602011771号