引言在 C 编程中我们经常遇到这样的需求需要实现功能相同但处理数据类型不同的代码。比如一个动态数组可能需要存储int、float、string甚至自定义类型的数据。如果为每种类型都写一个类代码会大量重复维护起来也极其困难。类模板正是为了解决这个问题而生的。它允许我们编写与类型无关的通用代码在编译时再根据具体类型生成对应的类。今天我将通过自己的学习笔记系统地讲解 C 中类模板的定义、使用以及模板具体化的相关知识。第一部分类模板的基本概念一、为什么需要类模板// 问题需要存储不同类型的动态数组 // 方案1为每种类型单独写一个类代码重复 class IntArray { int* p; int size; int idx; public: void add(int item) { /* ... */ } int get(int index) { /* ... */ } }; class FloatArray { float* p; int size; int idx; public: void add(float item) { /* ... */ } float get(int index) { /* ... */ } }; class StringArray { string* p; int size; int idx; public: void add(string item) { /* ... */ } string get(int index) { /* ... */ } }; // 问题代码重复率极高维护困难二、类模板的定义语法// 类模板的基本语法 templateclass T // 或 templatetypename T class 类名 { private: T member; // 使用泛型 T 定义成员 public: 类名(T param); // 构造函数 T func(); // 成员函数返回泛型类型 };关键点templateclass T声明这是一个模板T是类型参数T可以替换为任意类型int、float、string、自定义类等类模板的定义和实现通常都放在头文件中第二部分类模板的实现——Array 容器下面我们实现一个通用的动态数组类模板Array它可以存储任意类型的数据。一、类模板的定义#include iostream #include string using namespace std; templateclass T class Array { private: T* p; // 连续空间的首地址指向堆内存 int size; // 数组的最大容量 int idx; // 当前有效数据的长度 public: // 构造函数 Array(int max_size) : size(max_size) { p new T[max_size]{ T() }; // 初始化为 T 类型的默认值 idx 0; } // 析构函数 ~Array() { delete[] p; } // 获取当前元素个数 int getLength() const { return idx; } // 获取最大容量 int getSize() const { return size; } // 添加元素 void add(T item) { if (idx size) return; // 数组已满 p[idx] item; } // 删除指定元素 void remove(T item) { for (int i 0; i idx; i) { if (p[i] item) { // 将 i 之后的所有数据向前移动 while (i idx - 1) { p[i] p[i 1]; i; } idx--; p[idx] T(); // 将最后一个位置重置为默认值 return; } } } // 获取指定索引的元素 T get(int index) const { if (index idx) { return p[index]; } return T(); // 返回默认值 } // 重载 [] 运算符可选 T operator[](int index) { return p[index]; } };二、友元运算符重载为了让Array对象能够方便地输出我们需要重载运算符。注意模板类的友元函数也需要是模板// 前置声明解决友元模板的编译问题 templateclass E class Array; templateclass E ostream operator(ostream os, ArrayE arr); templateclass T class Array { // 声明友元operator 是 Array 的友元函数 templateclass E friend ostream operator(ostream, ArrayE); private: // ... 成员变量和成员函数 }; // 友元函数实现 templateclass E ostream operator(ostream os, ArrayE arr) { os 数组长度: arr.getLength(); os , 最大容量: arr.size endl; for (int i 0; i arr.getLength(); i) { os arr.get(i) ; } os endl; return os; }三、使用示例int main() { // 创建存储 int 类型的数组 Arrayint intArr(10); intArr.add(20); intArr.add(15); intArr.add(22); intArr.add(33); // 创建存储 string 类型的数组 Arraystring strArr(5); strArr.add(Lucy); strArr.add(Mack); strArr.add(Jack); strArr.add(Disen); // 删除元素 strArr.remove(Lucy); // 输出结果 cout intArr; cout strArr; return 0; } /* 输出 数组长度: 4, 最大容量: 10 20 15 22 33 数组长度: 3, 最大容量: 5 Mack Jack Disen */第三部分模板具体化实例化一、什么是模板具体化模板具体化也称为模板实例化是指编译器根据模板和具体类型生成实际类的过程。// 类模板定义 templateclass T class Array { /* ... */ }; // 模板具体化生成具体类 Arrayint intArr(10); // 生成 Arrayint 类 Arraystring strArr(5); // 生成 Arraystring 类 Arraydouble doubleArr(8); // 生成 Arraydouble 类二、编译器的行为三、显式实例化// 方式1隐式实例化最常见 Arrayint arr1(10); // 编译器自动生成 Arrayint // 方式2显式实例化提前生成 template class Arraydouble; // 强制生成 Arraydouble // 方式3外部实例化C11 extern template class Arrayfloat; // 避免重复实例化第四部分类模板的特化一、全特化Full Specialization当某个特定类型需要特殊处理时可以对模板进行全特化。// 通用模板 templateclass T class Printer { public: void print(T value) { cout 通用版本: value endl; } }; // 全特化针对 bool 类型的特殊处理 template class Printerbool { public: void print(bool value) { cout 布尔版本: (value ? true : false) endl; } }; int main() { Printerint p1; p1.print(100); // 通用版本: 100 Printerstring p2; p2.print(hello); // 通用版本: hello Printerbool p3; p3.print(true); // 布尔版本: true return 0; }二、偏特化Partial Specialization偏特化只针对部分模板参数进行特化。// 通用模板两个参数 templateclass T1, class T2 class Pair { public: void show() { cout 通用版本: T1, T2 endl; } }; // 偏特化1两个参数类型相同 templateclass T class PairT, T { public: void show() { cout 偏特化: T, T (相同类型) endl; } }; // 偏特化2第二个参数为 int templateclass T class PairT, int { public: void show() { cout 偏特化: T, int endl; } }; int main() { Pairdouble, string p1; // 通用版本 p1.show(); Pairint, int p2; // 偏特化相同类型 p2.show(); Pairchar, int p3; // 偏特化第二个是 int p3.show(); return 0; }第五部分模板类与友元一、友元函数的模板声明当模板类需要友元函数时友元函数通常也需要是模板。// 方式1在类内定义友元函数简单 templateclass T class MyClass { private: T value; public: MyClass(T v) : value(v) {} // 在类内定义友元函数推荐 friend ostream operator(ostream os, const MyClassT obj) { os Value: obj.value; return os; } }; // 方式2声明外部模板函数为友元 templateclass E class Array; templateclass E ostream operator(ostream, ArrayE); templateclass T class Array { templateclass E friend ostream operator(ostream, ArrayE); // ... 其他成员 };第六部分类模板的注意事项一、模板代码通常放在头文件中// Array.h #ifndef ARRAY_H #define ARRAY_H templateclass T class Array { // 声明和实现都放在头文件中 }; #endif原因模板的实例化发生在编译阶段编译器需要看到完整的模板定义才能生成代码。二、类型参数的默认值// C11 允许模板参数有默认值 templateclass T int class Container { T data; public: Container() : data(T()) {} }; // 使用默认类型 Container c1; // Containerint Containerdouble c2; // Containerdouble三、非类型模板参数// 非类型模板参数编译时常量 templateclass T, int Size class StaticArray { private: T arr[Size]; // 编译时确定大小 public: int getSize() const { return Size; } T operator[](int index) { return arr[index]; } }; int main() { StaticArrayint, 10 arr1; // 大小为 10 的 int 数组 StaticArraydouble, 5 arr2; // 大小为 5 的 double 数组 cout arr1.getSize() endl; // 10 cout arr2.getSize() endl; // 5 return 0; }第七部分完整示例——增强版 Array#include iostream #include string #include stdexcept using namespace std; templateclass T class Array { private: T* data; int capacity; int length; public: // 构造函数 Array(int cap 10) : capacity(cap), length(0) { data new T[capacity]{ T() }; } // 拷贝构造函数 Array(const Array other) : capacity(other.capacity), length(other.length) { data new T[capacity]; for (int i 0; i length; i) { data[i] other.data[i]; } } // 析构函数 ~Array() { delete[] data; } // 添加元素 void add(const T item) { if (length capacity) { resize(capacity * 2); } data[length] item; } // 插入元素 void insert(int index, const T item) { if (index 0 || index length) { throw out_of_range(索引越界); } if (length capacity) { resize(capacity * 2); } for (int i length; i index; i--) { data[i] data[i - 1]; } data[index] item; length; } // 删除元素 void remove(int index) { if (index 0 || index length) { throw out_of_range(索引越界); } for (int i index; i length - 1; i) { data[i] data[i 1]; } data[--length] T(); } // 获取元素 T get(int index) const { if (index 0 || index length) { throw out_of_range(索引越界); } return data[index]; } // 重载 [] 运算符 T operator[](int index) { if (index 0 || index length) { throw out_of_range(索引越界); } return data[index]; } // 获取长度 int getLength() const { return length; } // 获取容量 int getCapacity() const { return capacity; } // 判断是否为空 bool isEmpty() const { return length 0; } // 清空 void clear() { delete[] data; data new T[capacity]{ T() }; length 0; } private: // 扩容 void resize(int newCapacity) { T* newData new T[newCapacity]{ T() }; for (int i 0; i length; i) { newData[i] data[i]; } delete[] data; data newData; capacity newCapacity; } }; // 友元输出运算符 templateclass T ostream operator(ostream os, const ArrayT arr) { os [; for (int i 0; i arr.getLength(); i) { os arr.get(i); if (i arr.getLength() - 1) os , ; } os ]; return os; } int main() { // 测试 int 类型 Arrayint intArr; intArr.add(10); intArr.add(20); intArr.add(30); intArr.insert(1, 15); cout intArr: intArr endl; cout 长度: intArr.getLength() , 容量: intArr.getCapacity() endl; // 测试 string 类型 Arraystring strArr(5); strArr.add(苹果); strArr.add(香蕉); strArr.add(橙子); strArr.remove(1); cout strArr: strArr endl; // 测试 [] 运算符 strArr[1] 葡萄; cout 修改后: strArr endl; return 0; }结果总结一、核心要点概念说明类模板与类型无关的通用类用templateclass T定义模板具体化编译器根据具体类型生成实际类的过程全特化template class ClassNameSpecificType偏特化部分模板参数特化非类型参数templateclass T, int N二、模板具体化关系类模板 ArrayT │ ├── Arrayint (具体类) ├── Arrayfloat (具体类) ├── Arraystring (具体类) └── ArrayPoint (具体类) Arrayint arr1(10); // arr1 是 Arrayint 类型的对象 Arrayint arr2(20); // arr2 也是 Arrayint 类型的对象三、使用建议模板代码放在头文件避免链接错误合理使用特化为特殊类型提供优化实现注意代码膨胀每个具体化都会生成独立代码使用非类型参数编译时常量可提升性能类模板是 C 泛型编程的核心它让我们能够编写与类型无关的通用代码。标准模板库STL中的vector、list、map等容器都是基于类模板实现的。掌握类模板不仅能写出更通用的代码还能深入理解 C 的编译期多态机制。希望这篇文章能帮助你理解类模板的定义、使用和具体化过程。