C++进阶:第14章 代码重用与设计模式

学习如何通过组合、私有继承和类模板等高级技术,更智能、更高效地重用你的代码。

📦

包含 (has-a)

像搭乐高一样,将一个类的对象作为另一个类的成员,构建“拥有”关系。

🔒

私有继承 (has-a)

一种更隐秘的“拥有”关系,继承实现细节,但不继承接口。

🛠️

类模板 (泛型)

创建“万能模具”,用一份代码处理不同数据类型,实现终极代码重用。

第一课时:代码重用的模式

环节一:包含:模拟“拥有”(Has-A)关系

最直观的代码重用方式:一个类“拥有”另一个类的对象作为成员。

🚗 汽车与引擎

一个 `Car` 类不需要从 `Engine` 类继承,因为“汽车不是一种引擎”。相反,`Car` 类中应该包含一个 `Engine` 对象,这代表了“汽车拥有一个引擎” (has-a) 的关系。

🎓 学生与成绩

同样,一个 `Student` 类拥有一个姓名 (`string`) 和一组分数 (`valarray`)。我们在 `Student` 类的构造函数中,通过初始化列表来创建这些被“包含”的对象。

代码示例:Student 类的包含关系

#include <iostream> #include <string> #include <valarray> // 用于存储成绩的模板类 using namespace std; class Student { private: string name; // has-a 关系 1: 拥有一个 string 对象 valarray<double> scores; // has-a 关系 2: 拥有一个 valarray 对象 public: // 构造函数需要通过初始化列表来初始化被包含的对象 Student(const string & s, int n) : name(s), scores(n) {} void Show() const { cout << "姓名: " << name << endl; if (scores.size() > 0) { cout << "成绩数量: " << scores.size() << endl; } } }; int main() { Student ada("Ada Lovelace", 5); ada.Show(); return 0; }

环节二&三:私有继承——隐秘的“拥有”

私有继承是实现 has-a 关系的另一种方式,它继承实现,但不继承接口。

互动:选择正确的重用方式

我们要设计一个 `Dog` 类,它需要使用 `Tail` 类的摇摆功能。应该如何设计?

私有继承的本质

基类的 `public` 和 `protected` 成员都成为派生类的 `private` 成员。这意味着,从外部看,派生类对象不能调用基类的任何方法,完美隐藏了基类的接口。

`using` 声明:恢复访问权限

如果希望在私有继承后,有选择地让基类的某个方法在派生类中重新变为 `public`,可以使用 `using Base::method;` 声明。

第二课时:泛型代码重用——类模板

环节四&五:万能容器——类模板

一个栈的推入(push)、弹出(pop)逻辑,无论对整数还是字符串都完全一样。类模板就是为此而生!

万能栈(Stack)模板演示

代码示例:Stack 类模板

template <typename T> // 使用泛型 T class Stack { private: enum { MAX = 5 }; // 为了演示方便,容量设为5 T items[MAX]; // 存储 T 类型元素的数组 int top; public: Stack() : top(0) {} bool isfull() const { return top == MAX; } bool isempty() const { return top == 0; } bool push(const T & item); bool pop(T & item); }; // 模板方法的定义 template <typename T> bool Stack<T>::push(const T & item) { if (top < MAX) { items[top++] = item; return true; } return false; } // main 函数中使用 int main() { Stack<int> int_stack; int_stack.push(10); Stack<string> str_stack; str_stack.push("Hello"); return 0; }

环节六:总结与编程挑战

你已经掌握了C++中强大的代码重用技术!现在来试试这些挑战任务吧。

练习 1:Student 类的重用模式

任务:

  1. 定义一个 `Student_C` 类,使用 **包含** 方式(将 `string` 和 `valarray` 作为私有成员)实现。
  2. 定义一个 `Student_P` 类,使用 **私有继承** 方式从 `string` 类继承,并 **包含** `valarray` 作为成员。
  3. 确保两个类都能通过构造函数初始化姓名和分数数组,并提供一个公有方法来显示姓名。
点击查看参考答案
#include <iostream> #include <string> #include <valarray> using namespace std; // 1. 包含模式 (Has-A) class Student_C { private: string name; valarray<double> scores; public: Student_C(const string & s, int n) : name(s), scores(n) {} void ShowName() const { cout << "包含模式 Student: " << name << endl; } }; // 2. 私有继承模式 (Has-A) class Student_P : private string { private: valarray<double> scores; public: Student_P(const string & s, int n) : string(s), scores(n) {} void ShowName() const { // string的接口在内部是私有的, 但可访问 // (const string &)*this 将当前对象转换为对基类部分的引用 cout << "私有继承 Student: " << (const string &)*this << endl; } }; int main() { Student_C c("Alice", 3); Student_P p("Bob", 3); c.ShowName(); p.ShowName(); return 0; }
练习 2:栈模板

任务:

编写一个完整的类模板 `Stack`,使其可以存储任何类型的数据,并具有 `push` 和 `pop` 方法。使用一个固定长度为10的数组作为内部存储。

点击查看参考答案
#include <iostream> #include <string> using namespace std; template <typename T> class Stack { private: enum { MAX = 10 }; T items[MAX]; int top; public: Stack() : top(0) {} bool isfull() const { return top == MAX; } bool isempty() const { return top == 0; } bool push(const T & item); bool pop(T & item); }; template <typename T> bool Stack<T>::push(const T & item) { if (!isfull()) { items[top++] = item; return true; } return false; } template <typename T> bool Stack<T>::pop(T & item) { if (!isempty()) { item = items[--top]; return true; } return false; } int main() { Stack<int> int_stack; cout << "--- 整数栈测试 ---" << endl; for (int i = 1; i <= 5; ++i) { if(int_stack.push(i * 10)) cout << "Push: " << i * 10 << endl; } cout << "\n--- 弹出 ---" << endl; int value; while (int_stack.pop(value)) { cout << "Pop: " << value << endl; } return 0; }