类模板
基本概念
跟函数模板类似,类模板是用于创建具有相同行为接口(算法一致)但数据类型不同的类的蓝图。
核心逻辑:类的行为(成员函数、操作逻辑)与存储的数据类型无关,仅需定义一次模板,即可适配多种数据类型。
典型示例
链表类、栈类、队列类等容器类:
- 核心操作(插入、删除、检索、合并)与存储的数据类型无关
- 通过类模板可快速实现支持
int、string、自定义类型等多种数据的容器,无需重复编写类代码
类模板的定义
核心关键字
template:声明模板的关键字typename:指定类型参数(旧标准可用class,功能一致,推荐用typename更清晰)
语法格式
// 类模板定义(支持多个类型参数)
template <typename T1, typename T2> // T1、T2 为类型参数(占位符)
class node
{
public:
// 成员函数(可直接在类内实现)
node(){cout << "通用版本" << endl;}
private:
T1 a; // 用类型参数定义成员变量
T2 b;
};
关键注意事项
- 创建对象时,类型参数列表不可省略(与函数模板不同):
// 正确:显式指定类型参数 node<short, double> a; node<int, string> b; // 错误:未指定类型参数 // node c; // 编译报错,编译器无法推断类型 - 类型参数可自定义名称(如
T、Type、Data等),遵循标识符命名规则 - 类模板仅为“设计蓝图”,未实例化前不会生成具体代码
类模板的特化
当通用模板无法满足特定类型的需求时,为该类型提供专门的实现版本,称为模板特化。分为「偏特化」和「全特化」。
偏特化(部分类型参数特化)
定义
仅特化类模板中的部分类型参数,剩余参数仍为模板参数。
示例(基于上述 node 模板)
// 偏特化1:第一个参数为 string,第二个参数保留模板参数 T
template <typename T>
class node<string, T>
{
public:
node(){cout << "偏特化(第一个参数为 string)" << endl;}
private:
string a;
T b;
};
// 偏特化2:第二个参数为 string,第一个参数保留模板参数 T
template <typename T>
class node<T, string>
{
public:
node(){cout << "偏特化(第二个参数为 string)" << endl;}
private:
T a;
string b;
};
关键注意事项
- 特化版本的类声明必须指明已特化的类型(如
<T, string>) - 二义性问题:当多个偏特化版本都能匹配同一类型组合时,编译报错
报错信息:// 错误示例:两个偏特化版本都能匹配 node<string, string> int main(void) { node<string, string> n; // 编译报错,二义性 }error: ambiguous template instantiation for 'class node<std::cxx11::basic_string<char>, std::cxx11::basic_string<char>>' candidates are: template<class T1> class node<T1, std::_cxx11::basic_string<char>> template<class T2> class node<std::_cxx11::basic_string<char>, T2>
全特化(所有类型参数特化)
定义
特化类模板中的全部类型参数,无剩余模板参数。
示例
// 全特化:两个参数分别为 double 和 string
template <> // 无剩余模板参数,尖括号留空
class node<double, string>
{
public:
node(){cout << "全特化(double + string)" << endl;}
private:
double a;
string b;
};
关键注意事项
- 全特化版本的匹配优先级最高,不会与偏特化产生二义性:
node<double, string> d; // 优先匹配全特化版本,无报错 - 全特化本质是一个独立的类,仅复用了原模板的类名
模板匹配优先级
全特化版本 → 偏特化版本 → 通用模板版本
类模板的其他核心语法
类模板的静态成员
定义规则
- 类模板的静态成员属于「模板实例化后的具体类」,而非模板本身
- 必须在类外显式初始化,且需保留模板参数
示例
// 类模板定义
template <typename T>
class A
{
public:
static int count; // 静态成员声明
};
// 静态成员初始化(必须在类外)
template <typename T>
int A<T>::count = 0; // 语法:模板参数 + 类名::成员名
注意
- 不同模板实例的静态成员是独立的(如
A<int>::count和A<string>::count互不影响) - 初始化语句必须放在头文件中(与类模板方法定义规则一致)
类模板的成员方法定义
两种实现方式
方式1:类内直接实现(推荐,简单场景)
template <typename T>
class A
{
public:
void print() { cout << "类内实现" << endl; } // 直接定义
};
方式2:类外实现(复杂场景,需注意语法)
// a.h 头文件(必须放在头文件中,不能放 .cpp)
template <typename T>
class A
{
private:
T *p;
public:
A(); // 构造函数声明
~A(); // 析构函数声明
void func(); // 普通成员函数声明
};
// 类外实现构造函数
template <typename T> // 需重复模板声明
A<T>::A() // 语法:类名<T>::成员名
{
p = new T[100];
}
// 类外实现析构函数
template <typename T>
A<T>::~A()
{
delete [] p;
}
// 类外实现普通成员函数
template <typename T>
void A<T>::func()
{
cout << "类外实现" << endl;
}
关键语法规则
- 必须放在头文件中:类模板未实例化前无具体代码,
*.cpp编译时无法生成目标代码,链接时会报错 - 类外实现需重复
template <typename T>声明 - 成员名前必须加
类名<T>::限定作用域
不推荐的替代方案(仅作了解)
将模板实现的 .cpp 文件直接包含到使用模板的 main.cpp 中:
// main.cpp
#include <iostream>
#include "a.h" // 类模板声明
#include "a.cpp" // 类模板实现(不推荐,破坏文件结构)
拓展
类模板的参数默认值
C++11 及以上支持为类模板的类型参数指定默认值,类似函数默认参数。
示例
// 为 T2 指定默认值 int
template <typename T1, typename T2 = int>
class node
{
public:
node(){cout << "默认参数版本" << endl;}
private:
T1 a;
T2 b; // 未指定时默认为 int
};
// 使用场景
node<double> obj1; // 等价于 node<double, int>
node<string, double> obj2; // 显式指定第二个参数,覆盖默认值
类模板的继承
场景1:模板类继承普通类
class Base {}; // 普通类
template <typename T>
class Derived : public Base // 模板类继承普通类
{
private:
T data;
};
场景2:普通类继承模板类(需显式指定模板参数)
template <typename T>
class Base {}; // 模板类
class Derived : public Base<int> // 必须指定模板参数(如 int)
{};
场景3:模板类继承模板类
template <typename T1>
class Base {}; // 父模板类
template <typename T1, typename T2>
class Derived : public Base<T1> // 子模板类复用父模板的参数
{
private:
T2 data;
};
类模板与友元
示例1:普通友元函数
template <typename T>
class A
{
T data;
public:
A(T d) : data(d) {}
// 友元函数(非模板函数)
friend void print(A<T> &obj)
{
cout << obj.data << endl;
}
};
示例2:模板友元函数(支持所有类型实例)
template <typename T>
class A
{
T data;
public:
A(T d) : data(d) {}
// 模板友元函数
template <typename U>
friend void print(A<U> &obj);
};
// 友元函数类外实现
template <typename U>
void print(A<U> &obj)
{
cout << obj.data << endl;
}
类模板的类型萃取(Type Traits)
核心思想
通过模板特化,在编译期获取类型的属性(如是否为指针、是否为整型、是否为const类型等),实现编译期分支选择。
简单示例(判断是否为指针类型)
// 通用模板:默认不是指针
template <typename T>
struct IsPointer
{
static const bool value = false;
};
// 特化版本:当 T 为指针时
template <typename T>
struct IsPointer<T*>
{
static const bool value = true;
};
// 使用场景
cout << IsPointer<int>::value << endl; // 0(不是指针)
cout << IsPointer<int*>::value << endl; // 1(是指针)
cout << IsPointer<string*>::value << endl;// 1(是指针)
常见问题与避坑指南
编译/链接错误
| 错误类型 | 原因 | 解决方案 |
|---|---|---|
| 未定义引用 | 类模板方法放在 .cpp 文件中 |
将方法定义移至头文件,或包含 .cpp(不推荐) |
| 二义性报错 | 多个偏特化版本匹配同一类型组合 | 新增全特化版本覆盖该类型组合,或调整偏特化条件 |
| 类型推断失败 | 创建对象时未指定类型参数 | 显式指定尖括号内的类型参数 |
逻辑错误
- 误以为静态成员是模板共享:不同实例的静态成员独立,需分别初始化
- 全特化后修改通用模板:全特化是独立类,通用模板修改不影响全特化版本
- 偏特化条件重叠:避免多个偏特化版本的条件交叉覆盖
学习提示与总结
- 核心价值:类模板是泛型编程的核心,实现“一次编写,多类型复用”,提升代码复用性和可维护性
- 重点掌握:模板定义、特化(偏特化+全特化)、成员方法实现位置、匹配优先级
- 实践建议:
- 优先使用类内实现简单方法,复杂方法类外实现(放在头文件)
- 避免偏特化条件重叠,必要时用全特化解决二义性
- 结合容器类(如
vector、list)理解模板的实际应用

浙公网安备 33010602011771号