实验五

实验任务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 }

运行测试结果截图

屏幕截图 2025-12-09 222334

问题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 必须实现哪两个函数才能通过编译?请写出其完整函数声明。

(2) 在 publisher.cpp 的 Film 类实现中,把两个成员函数实现里的 const 去掉(保持函数体不变),重新编译,报错信息是什么? 
答:(1) Book、Film、Music 都必须实现基类 Publisher 中的两个纯虚函数,完整函数声明如下
void publish() const override;    //override用于显式标记函数是重写基类虚函数的 
void use() const override;

  (2)报错信息如下所示

屏幕截图 2025-12-09 234752

这是因为去掉 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 }

运行结果测试截图

屏幕截图 2025-12-10 082728

问题1:重载运算符<<
(1)找出运算符 << 被重载了几处?分别用于什么类型?
(2)找出使用重载 << 输出对象的代码,写在下面。 
答:(1)共重载了 2 处,分别用于Book 类型和BookSale 类型,用来输出图书和图书销售记录的详细信息
  (2)第一处(输出 Book 对象):
// 在 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;
}
问题2:图书销售统计
(1)图书销售记录"按销售数量降序排序",代码是如何实现的?
(2)拓展(选答*):如果使用lambda表达式,如何实现"按销售数量降序排序"? 
答:(1)代码通过 C++ 标准库中的 std::sort 函数,结合一个自定义比较函数来实现降序排序。
自定义比较函数:
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 }

运行结果测试截图

屏幕截图 2025-12-10 092934

实验任务五

源代码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 }

运行结果测试截图

屏幕截图 2025-12-10 115311

 五、实验总结

在面向对象编程实验环节,遇到了多个典型的C++编程问题。抽象类与多态方面,首先遇到抽象类实例化错误,表现为尝试直接创建Publisher对象导致编译失败,核心在于包含纯虚函数的类无法实例化,必须通过指针或引用使用。派生类重写函数时出现了函数签名不匹配问题,特别是缺少const修饰符导致无法正确重写基类虚函数,这体现了const作为函数签名组成部分的重要性。多态对象析构时发现未声明虚析构函数可能导致内存泄漏,通过基类指针删除派生类对象时,派生类特有资源无法正确释放。

模板类与运算符重载部分问题较为集中。复制构造函数最初使用非常量引用,无法绑定临时对象,改为const引用后解决了兼容性问题。赋值运算符设计上,operator+=错误地返回了值而非引用,无法支持链式调用,修正为修改当前对象后返回*this引用。输入输出运算符设计为成员函数导致语法不自然,如c << cout,改为友元函数后恢复标准语法cin >> c。最复杂的错误出现在模板类友元声明,简单的friend声明无法匹配模板参数,需要额外声明template<typename U>友元函数。

基础语法设计上,构造函数参数使用string&无法接受字符串字面值,const string&提供了更好的灵活性。排序功能实现时,比较函数需要单独定义,使用lambda表达式可以直接内联实现,代码更简洁。头文件缺少包含保护可能导致重复定义问题,成员函数设计时未考虑const对象调用需求,为不修改成员的函数添加const修饰符确保了代码的常量正确性。

这些问题从基础语法到高级特性全面涵盖了C++面向对象编程的核心难点,包括封装、继承、多态、模板、运算符重载、内存管理等关键概念,通过逐步调试和修正加深了对这些机制的理解。

posted @ 2025-12-16 13:50  木辛梓  阅读(5)  评论(0)    收藏  举报