ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

C++面向对象实战:多项式类与String类的内存安全实现

C++面向对象实战:多项式类与String类的内存安全实现 简介本资源是吉林大学《面向对象程序设计》课程配套的C上机实验报告面向计算机专业本科生及C初学者聚焦面向对象核心能力训练解决多项式类设计中动态内存管理、运算符重载、多文件组织等典型实践难点。压缩包为单个303KB的Word文档.doc完整呈现了从需求分析、类设计Polynomial.h/.cpp、关键代码实现含构造/析构/拷贝构造、input/output/calculate函数、运算符重载细节、-、*、含友元考量到主函数测试的全流程附有静态成员计数、错误输入处理、系数为0的输出优化等工程化细节。已有1810人学习下载读者可直接复用该报告结构撰写课程作业深入理解类封装、资源管理与const成员函数设计原则并获得可编译运行的完整多文件代码框架与规范注释。1. 这不是一份交差作业而是一套能跑通、能调试、能改出新功能的 C 面向对象实战模板你有没有遇到过这种场景刚学完 C 类、构造函数、运算符重载一写多项式就卡在new和delete[]的配对上输入一个系数输错程序直接崩两个多项式相乘结果全是乱码operator写完一运行就报double free or corruption这不是你手生——是原始实验报告里藏着一堆没填坑的内存黑匣子、没声明 const 的接口、没处理边界 case 的输出逻辑更别说多文件编译时头文件循环包含、静态成员未初始化、友元函数缺失这些“玄学翻车点”。这份来自吉林大学《面向对象程序课程设计》的上机实验报告表面看是五份独立 C 实验多项式类、String 类、继承体系、工资系统、图形管理但实际是一套完整覆盖 C 核心机制的工程化训练链从动态内存管理A 部分、深拷贝/浅拷贝陷阱E 部分到访问控制与继承语义C 部分、抽象基类与多态调度D 部分再到资源封装与 RAII 初探B 部分。它不教你怎么背语法而是逼你在cin 崩溃后查cin.clear()在p[i]越界后补n1容量在operator返回临时对象时确认析构时机——每一处报错都是 C 编译器在给你发 debug 提示。如果你正卡在“知道概念但写不出可运行代码”的阶段或者想用一个真实项目验证自己对const成员函数、static生命周期、protected继承边界的理解这份报告就是你的最小可行练兵场。它不追求炫技但每行代码都直指 C 工程落地中最容易踩的坑。接下来我会带你把这份报告里的五组源码真正变成你本地能g -stdc11编译、能 gdb 调试、能加日志验证、能安全二次开发的可执行资产。2. 多项式类Polynomial动态数组 运算符重载的完整闭环实现这份实验最硬核的部分不是写个operator而是让整个类在任意输入、任意运算组合下不崩溃、不越界、不泄漏。原始报告里Polynomial.h和Polynomial.cpp存在多处关键缺陷默认构造函数分配了n0的数组却未置零、output()函数对系数为 ±1 的项处理不全、calculate()用pow(x,i)引入浮点误差且未处理x0边界。我们先修复结构再逐模块拆解。2.1 目录结构与编译命令从混乱到清晰原始报告中文件名大小写不统一Ploynomial.cppvsPolynomial.h、头文件宏定义不一致#ifndef POLYNOMIALvs#ifndef STRING_H_、主函数分散在多个.cpp文件里。这会导致g main.cpp Polynomial.cpp时链接失败或#include Polynomial.h找不到文件。规范后的目录结构推荐polynomial/ ├── include/ │ └── Polynomial.h // 统一宏定义#ifndef POLYNOMIAL_H_ ├── src/ │ ├── Polynomial.cpp // 实现所有成员函数 │ └── main.cpp // 唯一入口测试全部功能 └── CMakeLists.txt // 可选但强烈建议关键编译命令Linux/macOS# 单文件快速验证跳过头文件依赖 g -stdc11 -o poly src/Polynomial.cpp src/main.cpp # 带警告的生产级编译必须加 g -stdc11 -Wall -Wextra -Werror -o poly src/Polynomial.cpp src/main.cpp # 运行 ./poly提示-Wall -Wextra -Werror是 C 开发的后悔药。原始代码中p[0] \0给 int 数组赋 char 值、for (int i n; i 0; i--)i 为无符号时死循环等隐患开这些警告会直接报错逼你修复。2.2 核心类定义补齐 const、static、友元三大要素原始Polynomial.h缺失const修饰、static计数器未声明、operator未定义。修复后的头文件关键段如下// include/Polynomial.h #ifndef POLYNOMIAL_H_ #define POLYNOMIAL_H_ #include iostream #include cmath // 用于 calculate但注意应避免 pow()见后文 #include vector // 更安全的替代方案此处先保留原逻辑 class Polynomial { private: int n; // 最高次数 int* p; // 动态数组p[i] 表示 x^i 的系数 static int count; // 静态成员已创建的 Polynomial 对象总数 public: // 构造函数族 Polynomial(); // 默认构造n0, pnullptr explicit Polynomial(int degree); // 显式构造分配 degree1 个 int Polynomial(const Polynomial other); // 拷贝构造深拷贝 ~Polynomial(); // 析构释放 p // 输入输出非 const因修改对象状态 void input(); void output() const; // 关键output 不修改对象声明为 const // 计算const因只读取对象数据 double calculate(int x) const; // 返回值而非 void cout // 运算符重载返回新对象不修改 this Polynomial operator(const Polynomial rhs) const; Polynomial operator-(const Polynomial rhs) const; Polynomial operator*(const Polynomial rhs) const; Polynomial operator(const Polynomial rhs); // 赋值返回引用 // 友元函数支持 cout poly friend std::ostream operator(std::ostream os, const Polynomial poly); }; // 静态成员定义必须在 .cpp 中不能在头文件内 // extern int Polynomial::count; // 错误这是声明不是定义 // 正确做法见 2.3 节 #endif // POLYNOMIAL_H_参数说明与设计理由explicit Polynomial(int degree)加explicit防止隐式转换如Polynomial p 5;这是 C 现代实践。output() const和calculate() const明确告诉编译器和调用者“此函数不改变对象状态”提升可读性与安全性。static int count记录对象数量需在.cpp中定义并初始化int Polynomial::count 0;否则链接时报undefined reference。friend std::ostream operator让std::cout p1;可用比p1.output()更符合 C 习惯。2.3 关键成员函数实现内存安全与边界处理原始Polynomial.cpp中input()函数对错误输入的处理不彻底output()对x^1、x^0、系数 ±1 的格式化有遗漏calculate()用pow()引入精度问题。以下是修复后的核心实现src/Polynomial.cpp// src/Polynomial.cpp #include include/Polynomial.h #include iostream #include iomanip // 用于 setw控制输出对齐可选 #include algorithm // 用于 std::max // 静态成员定义必须 int Polynomial::count 0; // 默认构造n0, pnullptr更安全避免 new int[0] Polynomial::Polynomial() : n(0), p(nullptr) { count; } // 显式构造分配 degree1 个 int全部初始化为 0 Polynomial::Polynomial(int degree) : n(degree), p(new int[degree 1]()) { // () 初始化为 0避免垃圾值 count; } // 拷贝构造深拷贝 Polynomial::Polynomial(const Polynomial other) : n(other.n), p(new int[other.n 1]) { for (int i 0; i other.n; i) { p[i] other.p[i]; } count; } // 析构安全释放 Polynomial::~Polynomial() { delete[] p; // 注意delete[]不是 delete --count; } // 输入带完整错误处理 void Polynomial::input() { std::cout 请输入多项式的最高次数 n (0): ; while (!(std::cin n) || n 0) { std::cin.clear(); // 清除错误标志 std::cin.ignore(10000, \n); // 丢弃错误输入的整行 std::cout 输入错误n 必须是非负整数请重新输入: ; } // 释放旧内存如果存在 delete[] p; p new int[n 1](); // 分配并初始化为 0 std::cout 请按升序输入系数a0, a1, ..., an空格分隔: ; for (int i 0; i n; i) { while (!(std::cin p[i])) { std::cin.clear(); std::cin.ignore(10000, \n); std::cout 系数 i 输入错误请重新输入: ; } } } // 输出严格按 -x^4-6x^35 格式处理所有边界 void Polynomial::output() const { if (n 0 p[0] 0) { std::cout 0; return; } bool firstTerm true; // 从最高次项开始遍历 for (int i n; i 0; --i) { if (p[i] 0) continue; // 跳过系数为 0 的项 if (!firstTerm) { std::cout (p[i] 0 ? : - ); } else { if (p[i] 0) std::cout -; } int absCoeff std::abs(p[i]); if (i 0) { // 常数项直接输出系数绝对值 std::cout absCoeff; } else if (i 1) { // x^1 项输出系数若为1则省略 x if (absCoeff 1) { std::cout x; } else { std::cout absCoeff x; } } else { // x^i 项输出系数若为1则省略 x^ i if (absCoeff 1) { std::cout x^ i; } else { std::cout absCoeff x^ i; } } firstTerm false; } if (firstTerm) std::cout 0; // 全零多项式 } // 计算用霍纳法Horners method替代 pow()避免浮点误差和性能问题 double Polynomial::calculate(int x) const { if (n 0) return 0.0; if (n 0) return static_castdouble(p[0]); double result static_castdouble(p[n]); for (int i n - 1; i 0; --i) { result result * x p[i]; } return result; } // 友元输出流重载 std::ostream operator(std::ostream os, const Polynomial poly) { poly.output(); return os; }逻辑说明与参数详解input()用while (!(std::cin n) || n 0)同时检查输入流状态和业务逻辑n≥0std::cin.ignore(10000, \n)比while (cin.get() ! \n)更鲁棒防止缓冲区残留。output()核心是firstTerm标志位。首项不加/-后续项根据系数正负决定符号absCoeff 1时省略系数如x^2而非1x^2i0和i1单独处理确保5和3x格式正确。calculate()霍纳法是重点。原始pow(x,i)在x10, i10时计算10^10不仅慢且pow()返回doubleint系数转double再乘可能丢失精度。霍纳法((a_n * x a_{n-1}) * x ...)全整数运算精准高效。operator作为友元可直接访问poly.output()保持接口一致性。2.4 运算符重载实现深拷贝、内存分配与异常安全原始Operation.cpp中operator和operator*存在严重缺陷operator未处理n不同导致的数组越界operator*的嵌套循环中multiply.p[i j]可能越界ij最大为ndxs.n但multiply.p只分配了ndxs.n1索引0..ndxs.n合法ij正好在此范围但原始代码multiply.p[i j] multiply.p[i j] temp[j];未初始化multiply.p为 0导致垃圾值累加。修复后的operator和operator*接续Polynomial.cpp// 加法结果次数为 max(n, rhs.n)系数对应相加 Polynomial Polynomial::operator(const Polynomial rhs) const { int maxN std::max(n, rhs.n); Polynomial result(maxN); // 将当前对象系数复制到 result for (int i 0; i n i maxN; i) { result.p[i] p[i]; } // 将 rhs 系数加到 result for (int i 0; i rhs.n i maxN; i) { result.p[i] rhs.p[i]; } return result; // RVO 优化无需额外拷贝 } // 乘法结果次数为 n rhs.n使用标准卷积 Polynomial Polynomial::operator*(const Polynomial rhs) const { if (n 0 || rhs.n 0) return Polynomial(0); // 边界处理 int resultN n rhs.n; Polynomial result(resultN); // 初始化 result.p 为 0由构造函数保证 // 卷积result.p[k] sum_{ijk} p[i] * rhs.p[j] for (int i 0; i n; i) { for (int j 0; j rhs.n; j) { result.p[i j] p[i] * rhs.p[j]; } } return result; } // 赋值运算符经典三步法检查自赋值、释放旧资源、深拷贝 Polynomial Polynomial::operator(const Polynomial rhs) { if (this rhs) return *this; // 自赋值检查 delete[] p; // 释放当前资源 n rhs.n; p new int[n 1]; for (int i 0; i n; i) { p[i] rhs.p[i]; } return *this; }关键点解析operator用std::max(n, rhs.n)确定结果次数循环中i n i maxN防止p[i]越界当rhs.n n时i只遍历到n。operator*双重循环实现数学卷积result.p[ij] p[i] * rhs.p[j]是核心。result构造时已用()初始化为 0确保累加正确。operator三步法是铁律。原始代码缺失自赋值检查if (this rhs)若p1 p1先delete[] p再p[i] rhs.p[i]会访问已释放内存必崩。3. String 类手动内存管理与 RAII 思想的启蒙实践B 部分的String类看似简单实则是 C 内存管理的“照妖镜”。原始String.h中cat()函数有致命 bugstrcat(str,st.str)修改了this-str但未检查this-str容量是否足够导致缓冲区溢出String::String(const char * s)未处理s为nullptr的情况operator的is.get(temp,max)未检查temp是否以\0结尾。这些不是小疏忽而是 C 程序员必须刻进 DNA 的安全红线。3.1 类接口重构从“能用”到“健壮”原始String类缺少移动语义C11、c_str()接口、operator[]下标访问且cat()命名不达意应为append。我们按现代 C 习惯重构// include/String.h #ifndef STRING_H_ #define STRING_H_ #include iostream #include cstring #include cstdlib // for strlen, strcpy, strcat class String { private: char* str; // 指向堆内存的指针 size_t len; // 当前长度不含 \0 public: // 构造 String(); // 空字符串 String(const char* s); // 从 C 字符串构造 String(const String other); // 拷贝构造 String(String other) noexcept; // 移动构造C11 // 析构 ~String(); // 赋值 String operator(const String other); // 拷贝赋值 String operator(String other) noexcept; // 移动赋值 // 访问 size_t length() const { return len; } const char* c_str() const { return str; } char operator[](size_t i) { return str[i]; } // 可修改 const char operator[](size_t i) const { return str[i]; } // 只读 // 连接更名 append语义清晰 String append(const String other); // 友元流操作符 friend std::ostream operator(std::ostream os, const String s); friend std::istream operator(std::istream is, String s); }; #endif // STRING_H_设计意图size_t len比int len更符合字符串长度语义无符号范围更大。String(String)和operator(String)移动语义可避免深拷贝开销是 C11 后必备技能。c_str()提供与 C 库交互的标准接口。append()替代cat()STLstd::string命名惯例降低学习成本。3.2 关键函数实现防御式编程与异常安全String.cpp中的核心实现必须处理所有边界// src/String.cpp #include include/String.h #include iostream #include stdexcept // 默认构造 String::String() : str(new char[1]), len(0) { str[0] \0; } // C 字符串构造处理 nullptr String::String(const char* s) : len(0), str(nullptr) { if (s nullptr) { str new char[1]; str[0] \0; return; } len std::strlen(s); str new char[len 1]; std::strcpy(str, s); } // 拷贝构造 String::String(const String other) : len(other.len) { str new char[len 1]; std::strcpy(str, other.str); } // 移动构造 String::String(String other) noexcept : str(other.str), len(other.len) { other.str nullptr; other.len 0; } // 析构 String::~String() { delete[] str; } // 拷贝赋值三步法 String String::operator(const String other) { if (this other) return *this; delete[] str; len other.len; str new char[len 1]; std::strcpy(str, other.str); return *this; } // 移动赋值 String String::operator(String other) noexcept { if (this other) return *this; delete[] str; str other.str; len other.len; other.str nullptr; other.len 0; return *this; } // 连接先扩容再 strcat String String::append(const String other) { if (other.len 0) return *this; // 空字符串直接返回 size_t newLen len other.len; char* newStr new char[newLen 1]; // 复制原字符串 std::strcpy(newStr, str); // 追加 other 字符串 std::strcat(newStr, other.str); delete[] str; str newStr; len newLen; return *this; } // 友元输出 std::ostream operator(std::ostream os, const String s) { os s.str; return os; } // 友元输入安全读取避免缓冲区溢出 std::istream operator(std::istream is, String s) { const size_t MAX_INPUT 1024; char buffer[MAX_INPUT]; is.getline(buffer, MAX_INPUT); // 读取一行自动处理 \n if (is.gcount() MAX_INPUT - 1 is.peek() ! \n) { // 输入超长清空剩余 is.clear(); is.ignore(10000, \n); } s String(buffer); // 调用拷贝赋值 return is; }血泪经验总结String(const char* s)必须检查s nullptr。C 中向strlen(nullptr)传参是未定义行为UB程序可能立即崩溃。append()原始cat()直接strcat(str,st.str)是定时炸弹。正确做法是new一块足够大的新内存strcpystrcat再delete[]旧内存。这是 RAIIResource Acquisition Is Initialization思想的朴素体现资源生命周期与对象绑定。operator用getline()替代get()并检查gcount()判断是否读满缓冲区再ignore()清理杜绝输入攻击。3.3 主函数测试验证所有路径main.cpp应覆盖所有构造、赋值、连接场景// src/main_string.cpp #include include/String.h #include iostream int main() { // 测试 1字面量构造 String s 吉林大学; std::cout s \ s \, len s.length() std::endl; // 测试 2拷贝构造 String t s; std::cout t \ t \ (copy of s) std::endl; // 测试 3默认构造 赋值 String m; m t; std::cout m \ m \ (assigned from t) std::endl; // 测试 4append 连接 String a 软件学院; m.append(a); std::cout m.append(a) \ m \ std::endl; // 测试 5移动语义C11 String tmp 临时对象; String moved std::move(tmp); std::cout moved \ moved \, tmp.len tmp.length() std::endl; // 测试 6输入 std::cout 请输入新字符串: ; String userInput; std::cin userInput; std::cout userInput \ userInput \ std::endl; return 0; }编译运行g -stdc11 -Wall -Wextra -Werror -o string src/String.cpp src/main_string.cpp ./string4. 继承体系Camera/Chip与多态工资系统访问控制与抽象基类的实战C 和 D 部分是面向对象设计的“高阶考场”。C 部分通过Camera→Chip→Car_camera_*的三级继承强制你理解public/protected/private继承对派生类接口的塑造力D 部分用Employee抽象基类和四类雇员让你亲手写出第一个真正的多态系统。原始代码中Car_camera_two的options()调用Chip::options()会失败protected成员在类外不可见D 部分的SalariedEmployee多重继承Employee和Birthdate导致菱形继承歧义且CalculateSalary()未声明为virtual破坏多态。4.1 继承实验public/protected/private 的访问边界可视化原始Camera.cpp中Car_camera_twoprotected继承试图在main()中调用b.options()但options()在Chip中是publicprotected继承后在Car_camera_two外部不可见。我们必须用类内函数桥接// include/Camera.h #ifndef CAMERA_H_ #define CAMERA_H_ #include iostream #include string class Camera { protected: // 关键take_photo 改为 protected供派生类使用 virtual void take_photo() { std::cout 摄像功能启动 std::endl; } public: void image_set() { std::cout 图像质量设定功能启动 std::endl; } virtual ~Camera() default; // 虚析构确保多态删除安全 }; class Chip : public Camera { // Chip 公共继承 Camera protected: bool menu; double speed; public: Chip() : menu(false), speed(0.0) {} void options() { std::cout 进入操作菜单功能请选择 std::endl; } void show() const { std::cout 当前车速为 speed std::endl; } void Resetmenu(bool v) { menu v; } }; // 三种继承方式的行车记录仪 class Car_camera_one : public Chip { // public 继承所有 public 成员对外可见 public: void open() { std::cout 行车记录仪启动 std::endl; } void close() { std::cout 行车记录仪关闭Bye std::endl; } // 可直接使用 Chip::options() 和 Camera::image_set() }; class Car_camera_two : protected Chip { // protected 继承Chip 的 public 变成 protected public: void open() { std::cout 行车记录仪启动 std::endl; } void close() { std::cout 行车记录仪关闭Bye std::endl; } // 提供 public 接口来暴露需要的功能 void use_options() { options(); } // 在类内调用合法 void use_image_set() { image_set(); } // Camera::image_set() 是 publicprotected 继承后仍为 protected但可在派生类内调用 }; class Car_camera_three : private Chip { // private 继承所有成员变为 private public: void open() { std::cout 行车记录仪启动 std::endl; } void close() { std::cout 行车记录仪关闭Bye std::endl; } void use_options() { options(); } // 同样类内可调用 }; #endif // CAMERA_H_关键修正点Camera::take_photo()改为protectedpublic方法被protected/private继承后外部无法调用但派生类内部需要它所以设为protected。Car_camera_two::use_options()protected继承后options()对main()不可见但Car_camera_two类内可调用因此提供use_options()作为门面。virtual ~Camera() default虚析构是多态删除的基石。若delete ptr_to_Chip没有虚析构Camera部分不会被正确析构。4.2 工资系统抽象基类、多态与容器管理D 部分原始代码最大问题是Employee未声明为抽象基类virtual函数未 0且SalariedEmployee等类未重写CalculateSalary()为virtual导致vectorEmployee*中调用的是基类空实现。此外Birthdate应作为Employee的成员而非多重继承避免菱形问题。重构后的Employee体系include/Employee.h// include/Employee.h #ifndef EMPLOYEE_H_ #define EMPLOYEE_H_ #include iostream #include string #include vector #include memory // 用智能指针管理 // 日期类作为 Employee 成员 class Date { public: int year, month, day; Date(int y 1970, int m 1, int d 1) : year(y), month(m), day(d) {} bool isBirthdayThisMonth(int currentMonth) const { return month currentMonth; } }; // 抽象基类 class Employee { protected: std::string m_name; int m_id; Date birthDate; public: Employee(const std::string name, int id, const Date bd) : m_name(name), m_id(id), birthDate(bd) {} // 纯虚函数强制派生类实现 virtual double CalculateSalary(int currentMonth) 0; virtual void Output(int currentMonth) 0; virtual ~Employee() default; // 虚析构 }; // 周薪雇员 class SalariedEmployee : public Employee { private: double weeklySalary; public: SalariedEmployee(const std::string name, int id, const Date bd, double salary) : Employee(name, id, bd), weeklySalary(salary) {} double CalculateSalary(int currentMonth) override { double base weeklySalary; if (birthDate.isBirthdayThisMonth(currentMonth)) { base 100.0; } return base; } void Output(int currentMonth) override { std::cout 员工类型: 周薪雇员 | 姓名: m_name | 工号: m_id | 工资: CalculateSalary(currentMonth) std::endl; } }; // 时薪雇员简化版仅示意多态 class HourlyEmployee : public Employee { private: double hourlyWage; int hoursWorked; public: HourlyEmployee(const std::string name, int id, const Date bd, double wage, int hours) : Employee(name, id, bd), hourlyWage(wage), hoursWorked(hours) {} double CalculateSalary(int currentMonth) override { double base (hoursWorked 40) ? hoursWorked * hourlyWage : 40 * hourlyWage (hoursWorked - 40) * hourlyWage * 1.5; if (birthDate.isBirthdayThisMonth(currentMonth)) { base 100.0; } return base; } void Output(int currentMonth) override { std::cout 员工类型: 时薪雇员 | 姓名: m_name | 工号: m_id | 工资: CalculateSalary(currentMonth) std::endl; } }; // 容器管理类 class Company { private: std::vectorstd::unique_ptrEmployee employees; public: void addEmployee(std::unique_ptrEmployee emp) { employees.push_back(std::move(emp)); } void payRoll(int currentMonth) { std::cout \n 本月工资发放 std::endl; for (const auto emp : employees) { emp-Output(currentMonth); } } }; #endif // EMPLOYEE_H_核心改进Employee是纯抽象类CalculateSalary()和Output()均为0无法实例化。override关键字显本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表