C++类模板实战:从零构建可配置的SmartArray容器

📅 2026/7/14 19:00:02 👁️ 阅读次数 📝 编程学习
C++类模板实战:从零构建可配置的SmartArray容器

1. 项目概述:为什么我们需要一个完整的类模板实践项目?

如果你写过一段时间的C++,尤其是接触过标准库(STL),那你一定对std::vectorstd::map这些容器不陌生。它们最神奇的地方在于,你可以用同一个vector类,去装intstring,甚至是你自定义的Student对象。这种“一套代码,适配多种类型”的能力,其核心就是类模板。很多朋友在初学模板时,往往停留在“知道有这么个语法”的层面,照着书上的例子敲一个template <typename T> class Box { T data; };就觉得会了。但一到实际项目里,要自己设计一个通用的数据结构、一个类型安全的容器,或者一个可配置的策略类时,就立刻懵了:声明和实现到底放哪?特化怎么写?编译报错像天书怎么办?

这正是我启动这个“C++类模板实现与应用完整实践项目”的初衷。它不是一个简单的语法演示,而是一个从零到一,手把手带你构建一个具备工业级雏形的通用工具库的实战过程。我们将以实现一个简化版的、但功能完整的“智能数组”(SmartArray)为核心,它要能动态管理内存、支持迭代器、允许自定义元素类型,甚至能通过策略模板参数来定制内存分配和边界检查行为。通过这个项目,你会彻底打通类模板从语法、设计、实现到调试的任督二脉,理解为什么模板是C++泛型编程的基石,以及如何让它成为你手中解决复杂问题的利器。

2. 核心设计思路:构建一个高可配置的SmartArray类模板

一个玩具般的模板类和一个真正有用的模板类,差距往往在设计思路上。我们的SmartArray目标不仅仅是包装一个原生数组,而是要成为一个安全、高效、可扩展的通用容器。

2.1 需求分析与功能定位

首先,我们得明确这个SmartArray要解决什么问题,以及它和std::vector的区别在哪里。std::vector非常强大,但有时我们可能需要更轻量、或行为略有不同的容器。我们的SmartArray定位如下:

  1. 基础功能:像vector一样,支持动态扩容、随机访问(operator[])、获取大小(size)、容量(capacity)。
  2. 安全性增强:默认提供可选的边界检查(特别是在调试模式下),避免数组越界这种常见错误。
  3. 可定制性:允许用户通过模板参数注入“策略”(Policy),来改变内存分配(例如使用内存池)或边界检查的行为。
  4. 迭代器支持:提供标准的begin()end()方法,支持基于范围的for循环(for (auto& elem : arr)),这是现代C++容器的标配。
  5. 异常安全:保证在内存分配失败等异常情况下,对象自身状态是一致的。

这个设计的关键在于“可配置性”。我们将使用策略模板参数模板特化来实现它。这使得SmartArray不是一个死板的容器,而是一个框架,用户可以根据需要组合不同的行为。

2.2 类模板的骨架与模板参数设计

基于以上思路,我们初步设计出SmartArray的模板声明。这里会引入多个模板参数,这是类模板进阶应用的体现。

// SmartArray.hpp #ifndef SMART_ARRAY_HPP #define SMART_ARRAY_HPP #include <cstddef> // for size_t #include <memory> // for std::allocator, std::allocator_traits // 默认的内存分配策略:使用标准分配器 template <typename T> struct DefaultAllocatorPolicy { using allocator_type = std::allocator<T>; // 后续可以在这里封装更复杂的分配/释放接口 }; // 默认的边界检查策略:发布模式无检查,调试模式可启用检查 struct NoBoundsChecking { static void check(size_t index, size_t size) { /* 默认无操作 */ } }; #ifdef _DEBUG struct DebugBoundsChecking { static void check(size_t index, size_t size) { if (index >= size) { throw std::out_of_range("SmartArray index out of range!"); } } }; #endif // 主模板声明 template < typename T, // 元素类型 typename AllocPolicy = DefaultAllocatorPolicy<T>, // 内存分配策略,默认为标准分配器 typename BoundsCheckPolicy = NoBoundsChecking // 边界检查策略,默认无检查 > class SmartArray { public: // 类型别名,增强代码可读性 using value_type = T; using allocator_type = typename AllocPolicy::allocator_type; using size_type = size_t; using difference_type = std::ptrdiff_t; using reference = value_type&; using const_reference = const value_type&; using pointer = typename std::allocator_traits<allocator_type>::pointer; using const_pointer = typename std::allocator_traits<allocator_type>::const_pointer; // 迭代器(简化版指针封装) using iterator = pointer; using const_iterator = const_pointer; // 构造函数、析构函数、拷贝控制成员 SmartArray(); explicit SmartArray(size_type count, const T& value = T()); SmartArray(const SmartArray& other); SmartArray(SmartArray&& other) noexcept; ~SmartArray(); SmartArray& operator=(const SmartArray& other); SmartArray& operator=(SmartArray&& other) noexcept; // 容量相关 size_type size() const noexcept; size_type capacity() const noexcept; bool empty() const noexcept; void reserve(size_type new_cap); void shrink_to_fit(); // 元素访问(核心!这里会用到边界检查策略) reference operator[](size_type pos); const_reference operator[](size_type pos) const; reference at(size_type pos); const_reference at(size_type pos) const; reference front(); const_reference front() const; reference back(); const_reference back() const; pointer data() noexcept; const_pointer data() const noexcept; // 迭代器 iterator begin() noexcept; const_iterator begin() const noexcept; const_iterator cbegin() const noexcept; iterator end() noexcept; const_iterator end() const noexcept; const_iterator cend() const noexcept; // 修改器 void clear() noexcept; iterator insert(const_iterator pos, const T& value); iterator erase(const_iterator pos); void push_back(const T& value); void pop_back(); void resize(size_type count, const T& value = T()); // 交换 void swap(SmartArray& other) noexcept; private: pointer m_data = nullptr; // 指向动态数组的指针 size_type m_size = 0; // 当前元素数量 size_type m_capacity = 0; // 当前分配的内存容量(以元素个数计) allocator_type m_allocator; // 内存分配器实例 // 内部辅助函数 void reallocate(size_type new_capacity); void destroy_elements(pointer start, pointer end); }; // 非成员函数:交换、比较运算符等 template <typename T, typename A, typename B> void swap(SmartArray<T, A, B>& lhs, SmartArray<T, A, B>& rhs) noexcept; // 特化比较运算符等(后续实现) // template <typename T, typename A, typename B> bool operator==(...); #endif // SMART_ARRAY_HPP

设计要点解析

  1. 多模板参数T是元素类型,AllocPolicyBoundsCheckPolicy是策略类。策略作为模板参数,是一种编译期多态(静态多态),比运行时通过虚函数实现策略模式效率更高,因为所有调用在编译期就确定了。
  2. 策略类设计:策略类通常是包含静态方法或类型的空类(无状态)。例如NoBoundsChecking::check是空函数,编译器优化后会完全消失,实现零开销抽象。DebugBoundsChecking则会在调试时抛出异常。
  3. 使用std::allocator_traits:这是现代C++内存管理的推荐方式。它提供了一个统一的接口来访问分配器的各种类型(如pointer)和操作,即使分配器本身没有定义pointer类型(例如原始指针),allocator_traits也能提供合理的默认值。这大大增强了代码对自定义分配器的兼容性。
  4. 成员函数设计:我们尽可能模仿std::vector的接口,这符合C++标准库的设计哲学(一致性原则),降低了用户的学习和使用成本。

3. 实现细节解析:模板的声明与定义为何必须在一起?

这是类模板新手遇到的第一个“坑”。如果你尝试像普通类一样,把声明放在.hpp,定义放在.cpp,链接时会报“未定义的引用”错误。其根本原因在于模板的实例化机制

3.1 模板实例化:编译器的视角

编译器在处理模板时,它并不知道你要用SmartArray<int>还是SmartArray<std::string>。模板本身只是一份“蓝图”。只有当代码中真正使用了SmartArray<int> arr;这样的具体类型时,编译器才会拿着int这个“实参”去替换模板参数T,生成一份实实在在的SmartArray<int>类的代码,这个过程叫做实例化

实例化发生在编译阶段。问题是,编译器在编译main.cpp(使用了SmartArray<int>)时,它需要看到SmartArray类所有成员函数(针对int类型)的完整定义,才能生成机器码。如果你把成员函数的定义单独放在一个.cpp文件里并编译成目标文件(.o),那么main.cpp的编译单元就看不到这些定义,自然无法实例化。链接器在最后阶段,也找不到SmartArray<int>::push_back这个函数的实体,于是报错。

3.2 解决方案:将定义与声明一同放在头文件

因此,类模板的通用做法是将成员函数的定义也放在头文件中,通常就紧跟在类声明之后。这样,任何包含该头文件的源文件,在需要实例化模板时,都能看到完整的定义。

我们的SmartArray实现将采用这种方式。下面我们实现几个关键成员函数,看看定义怎么写,以及策略如何被集成。

// 接在SmartArray类声明之后,仍在SmartArray.hpp文件中 // 默认构造函数 template <typename T, typename AllocPolicy, typename BoundsCheckPolicy> SmartArray<T, AllocPolicy, BoundsCheckPolicy>::SmartArray() : m_data(nullptr), m_size(0), m_capacity(0), m_allocator() {} // 带数量和初始值的构造函数 template <typename T, typename AllocPolicy, typename BoundsCheckPolicy> SmartArray<T, AllocPolicy, BoundsCheckPolicy>::SmartArray(size_type count, const T& value) : m_data(nullptr), m_size(0), m_capacity(0), m_allocator() { if (count > 0) { reserve(count); for (size_type i = 0; i < count; ++i) { // 使用allocator_traits来构造对象,异常安全 std::allocator_traits<allocator_type>::construct(m_allocator, &m_data[i], value); } m_size = count; } } // 析构函数 template <typename T, typename AllocPolicy, typename BoundsCheckPolicy> SmartArray<T, AllocPolicy, BoundsCheckPolicy>::~SmartArray() { clear(); // 释放内存 if (m_data) { std::allocator_traits<allocator_type>::deallocate(m_allocator, m_data, m_capacity); m_data = nullptr; m_capacity = 0; } } // 元素访问 - operator[] (使用边界检查策略) template <typename T, typename AllocPolicy, typename BoundsCheckPolicy> typename SmartArray<T, AllocPolicy, BoundsCheckPolicy>::reference SmartArray<T, AllocPolicy, BoundsCheckPolicy>::operator[](size_type pos) { // 调用策略类的静态检查方法 BoundsCheckPolicy::check(pos, m_size); return m_data[pos]; } template <typename T, typename AllocPolicy, typename BoundsCheckPolicy> typename SmartArray<T, AllocPolicy, BoundsCheckPolicy>::const_reference SmartArray<T, AllocPolicy, BoundsCheckPolicy>::operator[](size_type pos) const { BoundsCheckPolicy::check(pos, m_size); return m_data[pos]; } // 元素访问 - at (强制进行边界检查,即使默认策略是NoBoundsChecking) template <typename T, typename AllocPolicy, typename BoundsCheckPolicy> typename SmartArray<T, AllocPolicy, BoundsCheckPolicy>::reference SmartArray<T, AllocPolicy, BoundsCheckPolicy>::at(size_type pos) { if (pos >= m_size) { throw std::out_of_range("SmartArray::at index out of range"); } return m_data[pos]; } // push_back 实现,展示内存管理和对象构造 template <typename T, typename AllocPolicy, typename BoundsCheckPolicy> void SmartArray<T, AllocPolicy, BoundsCheckPolicy>::push_back(const T& value) { if (m_size >= m_capacity) { // 扩容策略:容量为0则分配1,否则翻倍。这是vector的常见策略。 size_type new_capacity = (m_capacity == 0) ? 1 : m_capacity * 2; // 注意:这里需要处理重新分配内存时,原有元素的移动或拷贝(异常安全) // 为了简化,我们先实现一个简单的reallocate,实际项目需考虑强异常安全保证。 reallocate(new_capacity); } // 在末尾构造新元素 std::allocator_traits<allocator_type>::construct(m_allocator, &m_data[m_size], value); ++m_size; } // 内部重新分配函数 template <typename T, typename AllocPolicy, typename BoundsCheckPolicy> void SmartArray<T, AllocPolicy, BoundsCheckPolicy>::reallocate(size_type new_capacity) { // 1. 分配新内存 pointer new_data = std::allocator_traits<allocator_type>::allocate(m_allocator, new_capacity); // 2. 将旧元素移动或拷贝到新内存(此处以移动为例,假设T是noexcept move constructible) for (size_type i = 0; i < m_size; ++i) { std::allocator_traits<allocator_type>::construct(m_allocator, &new_data[i], std::move_if_noexcept(m_data[i])); } // 3. 销毁并释放旧内存 destroy_elements(m_data, m_data + m_size); std::allocator_traits<allocator_type>::deallocate(m_allocator, m_data, m_capacity); // 4. 更新指针和容量 m_data = new_data; m_capacity = new_capacity; } // 销毁指定范围内的元素 template <typename T, typename AllocPolicy, typename BoundsCheckPolicy> void SmartArray<T, AllocPolicy, BoundsCheckPolicy>::destroy_elements(pointer start, pointer end) { for (pointer p = start; p != end; ++p) { std::allocator_traits<allocator_type>::destroy(m_allocator, p); } }

实现技巧与注意事项

  1. typename关键字:在模板定义内部,当引用一个依赖于模板参数的嵌套类型(如referenceallocator_type)时,必须在前面加上typename,告诉编译器这是一个类型名,而不是静态成员变量。例如typename SmartArray<T, A, B>::reference
  2. 使用std::allocator_traits:这是现代C++的最佳实践。不要直接调用m_allocator.construct()m_allocator.deallocate(),而是使用std::allocator_traits<Alloc>::construct(...)等。这保证了即使分配器没有提供某个成员函数,allocator_traits也能提供一个合理的默认实现。
  3. 异常安全push_backreallocate是异常安全的关键点。我们使用了std::move_if_noexcept,它会在T的移动构造函数声明为noexcept时优先使用移动,否则使用拷贝,以防止在移动过程中抛出异常导致数据丢失。更完善的实现还需要实现“复制并交换”(copy-and-swap)惯用法来提供强异常安全保证。
  4. 策略的集成:注意operator[]中直接调用了BoundsCheckPolicy::check(pos, m_size)。如果用户实例化时使用NoBoundsChecking,这个调用在优化后会被完全消除,实现零开销。如果使用DebugBoundsChecking,则会插入检查代码。这就是编译期多态的威力。

4. 高级特性实现:模板特化与迭代器

一个完整的容器必须支持迭代器。同时,对于某些特定类型,我们可能希望SmartArray有特殊的行为,这就需要用到模板特化

4.1 迭代器的实现

为了让SmartArray能用于基于范围的for循环和标准库算法,我们需要提供iteratorconst_iterator类型,以及begin()end()等成员函数。我们上面已经将迭代器类型定义为指针的别名,这是一种简单有效的做法(因为我们的内存是连续的)。

// 在类定义中已声明: using iterator = pointer; template <typename T, typename AllocPolicy, typename BoundsCheckPolicy> typename SmartArray<T, AllocPolicy, BoundsCheckPolicy>::iterator SmartArray<T, AllocPolicy, BoundsCheckPolicy>::begin() noexcept { return m_data; } template <typename T, typename AllocPolicy, typename BoundsCheckPolicy> typename SmartArray<T, AllocPolicy, BoundsCheckPolicy>::const_iterator SmartArray<T, AllocPolicy, BoundsCheckPolicy>::begin() const noexcept { return m_data; } // end(), cbegin(), cend() 类似实现...

现在,你就可以这样使用了:

SmartArray<int> arr = {1, 2, 3, 4, 5}; for (auto it = arr.begin(); it != arr.end(); ++it) { std::cout << *it << ' '; } // 或者更简洁的基于范围的for循环 for (const auto& elem : arr) { std::cout << elem << ' '; }

4.2 全特化:针对bool类型的优化

标准库的std::vector<bool>是一个著名的特例,它进行了空间优化,每个bool值只占一个比特位。我们也来尝试为SmartArray<bool>实现一个简单的全特化版本,展示特化的语法和用途。

// 在SmartArray.hpp的末尾,主模板定义之后 // SmartArray 对 bool 类型的全特化 template <typename AllocPolicy, typename BoundsCheckPolicy> class SmartArray<bool, AllocPolicy, BoundsCheckPolicy> { private: using BlockType = unsigned long; // 用一个无符号整型块来存储多个bool位 static const size_t BITS_PER_BLOCK = sizeof(BlockType) * 8; BlockType* m_data = nullptr; size_t m_size = 0; size_t m_capacity_in_blocks = 0; // ... 可能也需要一个特化的分配器策略 public: // 提供类似的接口,但内部实现完全不同 class reference { // 需要一个代理类来模拟对单个位的引用 BlockType* m_block_ptr; size_t m_bit_pos; public: reference(BlockType* block, size_t pos) : m_block_ptr(block), m_bit_pos(pos) {} operator bool() const { return (*m_block_ptr >> m_bit_pos) & 1; } reference& operator=(bool x) { if (x) { *m_block_ptr |= (BlockType(1) << m_bit_pos); } else { *m_block_ptr &= ~(BlockType(1) << m_bit_pos); } return *this; } // ... 其他赋值运算符 }; SmartArray() = default; // ... 特化的构造函数、析构函数等 reference operator[](size_t pos) { size_t block_index = pos / BITS_PER_BLOCK; size_t bit_index = pos % BITS_PER_BLOCK; BoundsCheckPolicy::check(pos, m_size); return reference(&m_data[block_index], bit_index); } bool operator[](size_t pos) const { // const版本返回值,而非引用 size_t block_index = pos / BITS_PER_BLOCK; size_t bit_index = pos % BITS_PER_BLOCK; BoundsCheckPolicy::check(pos, m_size); return (m_data[block_index] >> bit_index) & 1; } void push_back(bool value) { // 需要检查位存储空间是否足够,不够则重新分配内存块 // ... } // ... 其他成员函数的特化实现 };

特化要点

  1. 语法template <> class SmartArray<bool, ...>template <>表示这是一个全特化,所有模板参数都已指定。
  2. 完全不同的实现:特化类可以与主模板毫无关系,拥有完全不同的数据成员(如BlockType*)和成员函数实现。它只需要提供一套兼容的公共接口。
  3. reference代理类:因为无法返回一个“位的引用”,我们必须创建一个代理类reference,重载operator bool()(用于读取)和operator=(用于写入),来模拟对单个位的引用行为。这是实现vector<bool>类似功能的关键。
  4. 应用场景:特化常用于优化(如bool)、为特定类型提供特殊语义、或修复主模板对某些类型不适用的问题。

4.3 部分特化与策略组合的威力

我们还可以进行部分特化,例如为所有指针类型提供一个特化版本,在析构时可能需要特殊处理(虽然通常不需要)。但更常见的进阶用法是策略的组合

用户可以通过组合不同的策略类,轻松定制SmartArray的行为:

// 使用默认分配器和调试边界检查 SmartArray<int, DefaultAllocatorPolicy<int>, DebugBoundsChecking> debugArr; // 假设我们实现了一个基于内存池的分配策略 template <typename T> struct MyPoolAllocatorPolicy { /* ... */ }; // 使用自定义内存池分配器,并关闭边界检查以获得极致性能 SmartArray<MyComplexObject, MyPoolAllocatorPolicy<MyComplexObject>, NoBoundsChecking> highPerfArr;

这种设计模式将变化点(内存分配、边界检查)封装成独立的、可替换的策略,使得SmartArray的核心算法保持稳定,同时具备了极大的灵活性。这是类模板设计的高级技巧。

5. 实战:使用SmartArray并处理编译问题

现在,让我们写一个简单的测试程序来使用我们的SmartArray,并看看可能遇到的典型编译错误及其解决方法。

// main.cpp #include "SmartArray.hpp" #include <iostream> #include <string> int main() { // 1. 基本使用 SmartArray<int> intArr; for (int i = 0; i < 10; ++i) { intArr.push_back(i * i); } std::cout << "Int array: "; for (auto val : intArr) { std::cout << val << " "; } std::cout << std::endl; // 2. 使用at()进行安全访问 try { std::cout << "Element at pos 5: " << intArr.at(5) << std::endl; std::cout << "Element at pos 20: " << intArr.at(20) << std::endl; // 这将抛出异常 } catch (const std::out_of_range& e) { std::cerr << "Out of range error: " << e.what() << std::endl; } // 3. 使用自定义策略 #ifdef _DEBUG // 在调试模式下使用边界检查 SmartArray<std::string, DefaultAllocatorPolicy<std::string>, DebugBoundsChecking> strArr; strArr.push_back("Hello"); strArr.push_back("Template"); // strArr[5] = "World"; // 如果取消注释,在Debug模式下会抛出异常 #else SmartArray<std::string> strArr; // 发布模式,无检查,性能最优 #endif // 4. 测试拷贝和移动语义(需要实现对应的构造函数和赋值运算符) SmartArray<int> arr1 = {1, 2, 3}; SmartArray<int> arr2 = arr1; // 拷贝构造 SmartArray<int> arr3 = std::move(arr1); // 移动构造,arr1现在为空 return 0; }

5.1 常见编译与链接错误排查

  1. “undefined reference toSmartArray<int>::push_back(int const&)

    • 原因:最可能的原因是你错误地将成员函数定义放在了.cpp文件里并单独编译。
    • 解决:确保所有模板成员函数的定义都在头文件(.hpp)中,并且被所有使用该模板的源文件包含。
  2. “特化声明与主模板不匹配”

    • 原因:进行全特化或部分特化时,模板参数列表的数量或模式与主模板不一致。
    • 解决:仔细核对特化声明的template<...>部分。全特化是template<>,部分特化的参数数量需与主模板相同,但部分参数被具体化。
  3. 依赖模板参数的嵌套类型导致的编译错误

    • 场景:在模板定义内使用T::value_typeAllocPolicy::pointer时。
    • 错误:编译器可能将其误认为是静态成员变量。
    • 解决:在前面加上typename关键字,如typename T::value_type
  4. 模板实例化导致的代码膨胀

    • 现象:项目中有SmartArray<int>SmartArray<double>SmartArray<std::string>等多种实例化,编译后的二进制文件较大。
    • 分析与缓解:这是模板的固有特点。编译器会为每一种用到的类型生成一份独立的代码。缓解方法是:
      • 将非类型相关的操作提取到非模板基类中。
      • 使用外部模板显式实例化(extern template),将公共实例化放在一个源文件中,减少重复编译。但这增加了管理复杂度,在大型项目中权衡使用。

5.2 调试技巧:让模板错误信息更可读

模板的编译错误信息往往又长又晦涩。例如,如果你传递了一个没有拷贝构造函数的类型给push_back,错误信息可能包含几十行模板展开内容。

  • 使用静态断言(static_assert)进行友好提示:在模板代码中提前检查类型约束。

    template <typename T, typename AllocPolicy, typename BoundsCheckPolicy> void SmartArray<T, AllocPolicy, BoundsCheckPolicy>::push_back(const T& value) { static_assert(std::is_copy_constructible<T>::value, "SmartArray::push_back requires T to be copy constructible"); // ... 原有实现 }

    这样,当用户误用不合适的类型时,会先看到这句清晰的错误信息,而不是一堆模板内部错误。

  • 借助IDE和编译器:现代IDE(如CLion、Visual Studio)和编译器(GCC/Clang)对模板错误的定位和简化已经做得越来越好。仔细阅读错误信息的第一行和最后几行,通常核心问题就在那里。

6. 项目总结与扩展思考

通过这个完整的SmartArray项目实践,我们深入走过了类模板的设计、实现、特化和应用的全过程。你不再只是模板语法的使用者,而是能根据需求设计出灵活、高效、安全的模板组件的开发者。

回顾一下核心收获:

  1. 理解模板实例化:明白了为什么模板的定义必须放在头文件,这是深入使用C++模板的基础。
  2. 掌握策略模式与编译期多态:通过模板参数注入策略,实现了零开销的抽象,这是编写高性能泛型代码的关键技巧。
  3. 实现标准兼容的接口:模仿std::vector设计接口,提供了迭代器支持,使得我们的容器能无缝融入C++生态系统。
  4. 应用模板特化:针对bool类型实现全特化,了解了如何为特定类型提供完全不同的优化实现。
  5. 直面模板的复杂性:处理了依赖类型名、异常安全、移动语义等细节,并学习了如何调试晦涩的模板错误。

这个SmartArray还可以继续扩展:

  • 实现更完善的异常安全:为insert,erase,reallocate实现强异常安全保证。
  • 添加分配器支持:完整实现allocator_type的传播、get_allocator()方法等,使其完全符合标准库的分配器感知容器要求。
  • 支持初始化列表:实现std::initializer_list构造函数,支持像SmartArray<int> arr = {1,2,3};这样的初始化。
  • 实现更多算法:可以为SmartArray提供sort,find等成员函数,或者提供兼容标准库算法的迭代器。

模板是C++最强大也最复杂的特性之一。这个项目就像一把钥匙,帮你打开了泛型编程的大门。接下来的路,就是在更多的实际项目中,反复运用和深化这些概念,最终让模板成为你思维的一部分,写出既通用又高效的C++代码。