实验五
实验任务1
源代码publisher.hpp
1 #pragma once 2 #include <string> 3 // 发行/出版物类:Publisher (抽象类) 4 class Publisher { 5 public: 6 Publisher(const std::string& name_ = ""); // 构造函数 7 virtual ~Publisher() = default; 8 public: 9 virtual void publish() const = 0; // 纯虚函数,作为接口继承 10 virtual void use() const = 0; // 纯虚函数,作为接口继承 11 protected: 12 std::string name; // 发行/出版物名称 13 }; 14 // 图书类: Book 15 class Book : public Publisher { 16 public: 17 Book(const std::string& name_ = "", const std::string& author_ = ""); // 构造函数 18 public: 19 void publish() const override; // 接口 20 void use() const override; // 接口 21 private: 22 std::string author; // 作者 23 }; 24 // 电影类: Film 25 class Film : public Publisher { 26 public: 27 Film(const std::string& name_ = "", const std::string& director_ = ""); // 构造函数 28 public: 29 void publish() const override; // 接口 30 void use() const override; // 接口 31 private: 32 std::string director; // 导演 33 }; 34 // 音乐类:Music 35 class Music : public Publisher { 36 public: 37 Music(const std::string& name_ = "", const std::string& artist_ = ""); 38 public: 39 void publish() const override; // 接口 40 void use() const override; // 接口 41 private: 42 std::string artist; // 音乐艺术家名称 43 };
源代码publisher.cpp
1 #include <iostream> 2 #include <string> 3 #include "publisher.hpp" 4 // Publisher类:实现 5 Publisher::Publisher(const std::string& name_) : name{ name_ } { 6 } 7 // Book类: 实现 8 Book::Book(const std::string& name_, const std::string& author_) : Publisher{ name_ }, 9 author{ author_ } { 10 } 11 void Book::publish() const { 12 std::cout << "Publishing book《" << name << "》 by " << author << '\n'; 13 } 14 void Book::use() const { 15 std::cout << "Reading book 《" << name << "》 by " << author << '\n'; 16 } 17 // Film类:实现 18 Film::Film(const std::string& name_, const std::string 19 & director_) :Publisher{ name_ }, director{ director_ } { 20 } 21 void Film::publish() const { 22 std::cout << "Publishing film <" << name << "> directed by " << director << '\n'; 23 } 24 void Film::use() const { 25 std::cout << "Watching film <" << name << "> directed by " << director << '\n'; 26 } 27 // Music类:实现 28 Music::Music(const std::string& name_, const std::string& artist_) : Publisher{ name_ }, 29 artist{ artist_ } { 30 } 31 void Music::publish() const { 32 std::cout << "Publishing music <" << name << "> by " << artist << '\n'; 33 } 34 void Music::use() const { 35 std::cout << "Listening to music <" << name << "> by " << artist << '\n'; 36 }
源代码task1.cpp
1 #include <memory> 2 #include <iostream> 3 #include <vector> 4 #include "publisher.hpp" 5 void test1() { 6 std::vector<Publisher*> v; 7 v.push_back(new Book("Harry Potter", "J.K. Rowling")); 8 v.push_back(new Film("The Godfather", "Francis Ford Coppola")); 9 v.push_back(new Music("Blowing in the wind", "Bob Dylan")); 10 for (Publisher* ptr : v) { 11 ptr->publish(); 12 ptr->use(); 13 std::cout << '\n'; 14 delete ptr; 15 } 16 } 17 void test2() { 18 std::vector<std::unique_ptr<Publisher>> v; 19 v.push_back(std::make_unique<Book>("Harry Potter", "J.K. Rowling")); 20 v.push_back(std::make_unique<Film>("The Godfather", "Francis Ford Coppola")); 21 v.push_back(std::make_unique<Music>("Blowing in the wind", "Bob Dylan")); 22 for (const auto& ptr : v) { 23 ptr->publish(); 24 ptr->use(); 25 std::cout << '\n'; 26 } 27 } 28 void test3() { 29 Book book("A Philosophy of Software Design", "John Ousterhout"); 30 book.publish(); 31 book.use(); 32 } 33 int main() { 34 std::cout << "运行时多态:纯虚函数、抽象类\n"; 35 std::cout << "\n测试1: 使用原始指针\n"; 36 test1(); 37 std::cout << "\n测试2: 使用智能指针\n"; 38 test2(); 39 std::cout << "\n测试3: 直接使用类\n"; 40 test3(); 41 }
运行测试结果截图

问题1:抽象类机制
(1)是什么决定了 Publisher 是抽象类?用一句话说明,并指出代码中的具体依据。
(2)如果在 main.cpp 里直接写 Publisher p; 能否编译通过?为什么
答:(1)Publisher 类包含纯虚函数,因此它是抽象类。具体依据如下:
virtual void publish() const = 0; // 纯虚函数,作为接口继承 virtual void use() const = 0; // 纯虚函数,作为接口继承
(2)不能编译通过,因为 Publisher 是一个抽象类,抽象类不能被实例化即不能拥有对象。
问题2:纯虚函数与接口继承
(1) Book 、 Film 、 Music 必须实现哪两个函数才能通过编译?请写出其完整函数声明。
void publish() const override; //override用于显式标记函数是重写基类虚函数的
void use() const override;
(2)报错信息如下所示

这是因为去掉 const 后函数签名不匹配。基类函数有 const,派生类实现缺少 const,导致无法找到重载的成员函数
问题3:运行时多态与虚析构
(1)在 test1() 里, for (Publisher *ptr : v) 中 ptr 的声明类型是什么?
(2)当循环执行到 ptr->publish(); 时, ptr 实际指向的对象类型分别有哪些?(按循环顺序写出)
(3)基类 Publisher 的析构函数为何声明为 virtual ?若删除 virtual ,执行 delete ptr; 会出现什么问题?
答:(1)ptr 的声明类型是指向基类 Publisher 的指针。
(2)在循环中,ptr 实际依次指向的对象类型是:Book、Film、Music
(3)将基类的析构函数声明 virtual 的原因是确保通过基类指针删除派生类对象时,能正确调用派生类的析构函数。若不声明 virtual,那么用基类指针销毁对象时,只调用 Publisher 的析构函数,Book、Film、Music 的析构函数不会被调用,导致派生类特有的资源(如 author、director 等)不会被销毁,从而导致内存泄漏。
实验任务二
源代码book.hpp
1 #pragma once 2 #include <string> 3 4 // 图书描述信息类Book: 声明 5 class Book { 6 public: 7 Book(const std::string& name_, 8 const std::string& author_, 9 const std::string& translator_, 10 const std::string& isbn_, 11 double price_); 12 13 friend std::ostream& operator<<(std::ostream& out, const Book& book); 14 15 private: 16 std::string name; // 书名 17 std::string author; // 作者 18 std::string translator; // 译者 19 std::string isbn; // isbn号 20 double price; // 定价 21 };
源代码book.cpp
1 #include <iomanip> 2 #include <iostream> 3 #include <string> 4 #include "book.hpp" 5 // 图书描述信息类Book: 实现 6 Book::Book(const std::string& name_, 7 const std::string& author_, 8 const std::string& translator_, 9 const std::string& isbn_, 10 double price_) :name{ name_ }, author{ author_ }, translator{ translator_ }, 11 isbn{ isbn_ }, price{ price_ } { 12 } 13 // 运算符<<重载实现 14 std::ostream& operator<<(std::ostream& out, const Book& book) { 15 using std::left; 16 using std::setw; 17 out << left; 18 out << setw(15) << "书名:" << book.name << '\n' 19 << setw(15) << "作者:" << book.author << '\n' 20 << setw(15) << "译者:" << book.translator << '\n' 21 << setw(15) << "ISBN:" << book.isbn << '\n' 22 << setw(15) << "定价:" << book.price; 23 return out; 24 }
源代码booksale.hpp
1 #pragma once 2 #include <string> 3 #include "book.hpp" 4 // 图书销售记录类BookSales:声明 5 class BookSale { 6 public: 7 BookSale(const Book& rb_, double sales_price_, int sales_amount_); 8 int get_amount() const; // 返回销售数量 9 double get_revenue() const; // 返回营收 10 friend std::ostream& operator<<(std::ostream& out, const BookSale& item); 11 private: 12 Book rb; 13 double sales_price; // 售价 14 int sales_amount; // 销售数量 15 };
源代码booksale.cpp
1 #include <iomanip> 2 #include <iostream> 3 #include <string> 4 #include "booksale.hpp" 5 // 图书销售记录类BookSales:实现 6 BookSale::BookSale(const Book& rb_, 7 double sales_price_, 8 int sales_amount_) : rb{ rb_ }, sales_price{ sales_price_ }, 9 sales_amount{ sales_amount_ } { 10 } 11 int BookSale::get_amount() const { 12 return sales_amount; 13 } 14 double BookSale::get_revenue() const { 15 return sales_amount * sales_price; 16 } 17 // 运算符<<重载实现 18 std::ostream& operator<<(std::ostream& out, const BookSale& item) { 19 using std::left; 20 using std::setw; 21 out << left; 22 out << item.rb << '\n' 23 << setw(15) << "售价:" << item.sales_price << '\n' 24 << setw(15) << "销售数量:" << item.sales_amount << '\n' 25 << setw(15) << "营收:" << item.get_revenue(); 26 return out; 27 }
源代码task2.cpp
1 #include "booksale.hpp" 2 #include <iostream> 3 #include <string> 4 #include <vector> 5 #include <algorithm> 6 // 按图书销售数额比较 7 bool compare_by_amount(const BookSale& x1, const BookSale& x2) { 8 return x1.get_amount() > x2.get_amount(); 9 } 10 void test() { 11 using namespace std; 12 vector<BookSale> sales_lst; // 存放图书销售记录 13 int books_number; 14 cout << "录入图书数量: "; 15 cin >> books_number; 16 cout << "录入图书销售记录" << endl; 17 for (int i = 0; i < books_number; ++i) { 18 string name, author, translator, isbn; 19 float price; 20 cout << string(20, '-') << "第" << i + 1 << "本图书信息录入" << string(20, '-') << 21 endl; 22 cout << "录入书名: "; cin >> name; 23 cout << "录入作者: "; cin >> author; 24 cout << "录入译者: "; cin >> translator; 25 cout << "录入isbn: "; cin >> isbn; 26 cout << "录入定价: "; cin >> price; 27 Book book(name, author, translator, isbn, price); 28 float sales_price; 29 int sales_amount; 30 cout << "录入售价: "; cin >> sales_price; 31 cout << "录入销售数量: "; cin >> sales_amount; 32 BookSale record(book, sales_price, sales_amount); 33 sales_lst.push_back(record); 34 } 35 // 按销售册数排序 36 sort(sales_lst.begin(), sales_lst.end(), compare_by_amount); 37 // 按销售册数降序输出图书销售信息 38 cout << string(20, '=') << "图书销售统计" << string(20, '=') << endl; 39 for (auto& t : sales_lst) { 40 cout << t << endl; 41 cout << string(40, '-') << endl; 42 } 43 } 44 int main() { 45 test(); 46 }
运行结果测试截图

// 在 booksale.cpp 中 std::ostream& operator<<(std::ostream& out, const BookSale& item) { // ... out << item.rb << '\n'; // 这里使用了重载的 << 输出 Book 对象 // ... }
第二处(输出 BookSale 对象):
// 在 task2.cpp 中 for (auto& t : sales_lst) { cout << t << endl; // 这里使用了重载的 << 输出 BookSale 对象 cout << string(40, '-') << endl; }
bool compare_by_amount(const BookSale& x1, const BookSale& x2) {//接收两个 BookSale 对象 x1 和 x2 return x1.get_amount() > x2.get_amount();//当 x1 的销售数量大于 x2 的销售数量时,函数返回 true }
std::sort 使用 compare_by_amount 作为第三个参数,指导排序算法如何比较两个元素,从而按照销售数量进行降序排列。
// 按销售册数降序排序 sort(sales_lst.begin(), sales_lst.end(), compare_by_amount);
(2)使用lambda表达式实现
sort(sales_lst.begin(), sales_lst.end(), [](const BookSale& x1, const BookSale& x2) {return x1.get_amount() > x2.get_amount(); });
实验任务四
源代码pets.hpp
1 #include<string> 2 #include<iostream> 3 using namespace std; 4 5 class MachinePet { 6 private: 7 string nickname; 8 public: 9 MachinePet(const string& name) :nickname(name) {} //要const引用,支持字面值参数 10 virtual ~MachinePet() = default; //析构函数要定义为虚函数 11 string get_nickname()const { return nickname; } // 添加const,支持const对象调用 12 virtual string talk()const = 0; //定义为纯虚函数,使该类成为抽象类 13 }; 14 15 class PetCat :public MachinePet { 16 public: 17 PetCat(const string& name) :MachinePet(name) {} 18 string talk()const override { 19 return "miao wu~"; 20 } 21 }; 22 23 class PetDog :public MachinePet { 24 public: 25 PetDog(const string& name) :MachinePet(name) {} 26 string talk()const override { 27 return "wang wang~"; 28 } 29 };
源代码task4.cpp
1 #include <iostream> 2 #include <memory> 3 #include <vector> 4 #include "pet.hpp" 5 void test1() { 6 std::vector<MachinePet*> pets; 7 pets.push_back(new PetCat("miku")); 8 pets.push_back(new PetDog("da huang")); 9 for (MachinePet* ptr : pets) { 10 std::cout << ptr->get_nickname() << " says " << ptr->talk() << '\n'; 11 delete ptr; // 须手动释放资源 12 } 13 } 14 void test2() { 15 std::vector<std::unique_ptr<MachinePet>> pets; 16 pets.push_back(std::make_unique<PetCat>("miku")); 17 pets.push_back(std::make_unique<PetDog>("da huang")); 18 for (auto const& ptr : pets) 19 std::cout << ptr->get_nickname() << " says " << ptr->talk() << '\n'; 20 } 21 void test3() { 22 // MachinePet pet("little cutie"); // 编译报错:无法定义抽象类对象 23 const PetCat cat("miku"); 24 std::cout << cat.get_nickname() << " says " << cat.talk() << '\n'; 25 const PetDog dog("da huang"); 26 std::cout << dog.get_nickname() << " says " << dog.talk() << '\n'; 27 } 28 int main() { 29 std::cout << "测试1: 使用原始指针\n"; 30 test1(); 31 std::cout << "\n测试2: 使用智能指针\n"; 32 test2(); 33 std::cout << "\n测试3: 直接使用类\n"; 34 test3(); 35 }
运行结果测试截图

实验任务五
源代码Complex.hpp
1 #include<iostream> 2 using namespace std; 3 4 template<typename T> 5 class Complex { 6 private: 7 T real, imag; 8 9 public: 10 Complex() : real(0), imag(0) {} 11 Complex(T r, T i) : real(r), imag(i) {} 12 Complex(const Complex& c) : real(c.real), imag(c.imag) {} 13 T get_real() const { 14 return real; 15 } 16 T get_imag() const { 17 return imag; 18 } 19 Complex& operator+=(const Complex& c) { 20 real += c.real; 21 imag += c.imag; 22 return *this; 23 } 24 Complex operator+(const Complex& c) const { 25 return Complex(real + c.real, imag + c.imag); 26 } 27 bool operator==(const Complex& c) const { 28 return real == c.real && imag == c.imag; 29 } 30 template<typename U> 31 friend istream& operator>>(istream& in, Complex<U>& c); 32 33 template<typename U> 34 friend ostream& operator<<(ostream& out, const Complex<U>& c); 35 }; 36 37 38 template<typename T> 39 istream& operator>>(istream& in, Complex<T>& c) { 40 in >> c.real >> c.imag; 41 return in; 42 } 43 44 template<typename T> 45 ostream& operator<<(ostream& out, const Complex<T>& c) { 46 out << c.real; 47 if (c.imag > 0) { 48 out << " + " << c.imag << "i"; 49 } 50 else if (c.imag < 0) { 51 out << " - " << -c.imag << "i"; 52 } 53 return out; 54 }
源代码task5.cpp
1 #include <iostream> 2 #include "Complex.hpp" 3 void test1() { 4 using std::cout; 5 using std::boolalpha; 6 Complex<int> c1(2, -5), c2(c1); 7 cout << "c1 = " << c1 << '\n'; 8 cout << "c2 = " << c2 << '\n'; 9 cout << "c1 + c2 = " << c1 + c2 << '\n'; 10 c1 += c2; 11 cout << "c1 = " << c1 << '\n'; 12 cout << boolalpha << (c1 == c2) << '\n'; 13 } 14 void test2() { 15 using std::cin; 16 using std::cout; 17 Complex<double> c1, c2; 18 cout << "Enter c1 and c2: "; 19 cin >> c1 >> c2; 20 cout << "c1 = " << c1 << '\n'; 21 cout << "c2 = " << c2 << '\n'; 22 const Complex<double> c3(c1); 23 cout << "c3.real = " << c3.get_real() << '\n'; 24 cout << "c3.imag = " << c3.get_imag() << '\n'; 25 } 26 int main() { 27 std::cout << "自定义类模板Complex测试1: \n"; 28 test1(); 29 std::cout << "\n自定义类模板Complex测试2: \n"; 30 test2(); 31 }
运行结果测试截图

五、实验总结
在面向对象编程实验环节,遇到了多个典型的C++编程问题。抽象类与多态方面,首先遇到抽象类实例化错误,表现为尝试直接创建Publisher对象导致编译失败,核心在于包含纯虚函数的类无法实例化,必须通过指针或引用使用。派生类重写函数时出现了函数签名不匹配问题,特别是缺少const修饰符导致无法正确重写基类虚函数,这体现了const作为函数签名组成部分的重要性。多态对象析构时发现未声明虚析构函数可能导致内存泄漏,通过基类指针删除派生类对象时,派生类特有资源无法正确释放。
模板类与运算符重载部分问题较为集中。复制构造函数最初使用非常量引用,无法绑定临时对象,改为const引用后解决了兼容性问题。赋值运算符设计上,operator+=错误地返回了值而非引用,无法支持链式调用,修正为修改当前对象后返回*this引用。输入输出运算符设计为成员函数导致语法不自然,如c << cout,改为友元函数后恢复标准语法cin >> c。最复杂的错误出现在模板类友元声明,简单的friend声明无法匹配模板参数,需要额外声明template<typename U>友元函数。
基础语法设计上,构造函数参数使用string&无法接受字符串字面值,const string&提供了更好的灵活性。排序功能实现时,比较函数需要单独定义,使用lambda表达式可以直接内联实现,代码更简洁。头文件缺少包含保护可能导致重复定义问题,成员函数设计时未考虑const对象调用需求,为不修改成员的函数添加const修饰符确保了代码的常量正确性。
这些问题从基础语法到高级特性全面涵盖了C++面向对象编程的核心难点,包括封装、继承、多态、模板、运算符重载、内存管理等关键概念,通过逐步调试和修正加深了对这些机制的理解。

浙公网安备 33010602011771号