类模板

基本概念

跟函数模板类似,类模板是用于创建具有相同行为接口(算法一致)但数据类型不同的类的蓝图。

核心逻辑:类的行为(成员函数、操作逻辑)与存储的数据类型无关,仅需定义一次模板,即可适配多种数据类型。

典型示例

链表类、栈类、队列类等容器类:

  • 核心操作(插入、删除、检索、合并)与存储的数据类型无关
  • 通过类模板可快速实现支持 intstring、自定义类型等多种数据的容器,无需重复编写类代码

类模板的定义

核心关键字

  • template:声明模板的关键字
  • typename:指定类型参数(旧标准可用 class,功能一致,推荐用 typename 更清晰)

语法格式

// 类模板定义(支持多个类型参数)
template <typename T1, typename T2>  // T1、T2 为类型参数(占位符)
class node
{
public:
    // 成员函数(可直接在类内实现)
    node(){cout << "通用版本" << endl;}
private:
    T1 a;  // 用类型参数定义成员变量
    T2 b;
};

关键注意事项

  1. 创建对象时,类型参数列表不可省略(与函数模板不同):
    // 正确:显式指定类型参数
    node<short, double> a;  
    node<int, string> b;
    
    // 错误:未指定类型参数
    // node c;  // 编译报错,编译器无法推断类型
    
  2. 类型参数可自定义名称(如 TTypeData 等),遵循标识符命名规则
  3. 类模板仅为“设计蓝图”,未实例化前不会生成具体代码
类模板实例化流程示意图(模板 → 传入类型 → 生成具体类 → 创建对象)

类模板的特化

当通用模板无法满足特定类型的需求时,为该类型提供专门的实现版本,称为模板特化。分为「偏特化」和「全特化」。

偏特化(部分类型参数特化)

定义

仅特化类模板中的部分类型参数,剩余参数仍为模板参数。

示例(基于上述 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;
};

关键注意事项

  1. 特化版本的类声明必须指明已特化的类型(如 <T, string>
  2. 二义性问题:当多个偏特化版本都能匹配同一类型组合时,编译报错
    // 错误示例:两个偏特化版本都能匹配 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;
};

关键注意事项

  1. 全特化版本的匹配优先级最高,不会与偏特化产生二义性:
    node<double, string> d;  // 优先匹配全特化版本,无报错
    
  2. 全特化本质是一个独立的类,仅复用了原模板的类名

模板匹配优先级

全特化版本 → 偏特化版本 → 通用模板版本

类模板的其他核心语法

类模板的静态成员

定义规则

  • 类模板的静态成员属于「模板实例化后的具体类」,而非模板本身
  • 必须在类外显式初始化,且需保留模板参数

示例

// 类模板定义
template <typename T>
class A
{
public:
    static int count;  // 静态成员声明
};

// 静态成员初始化(必须在类外)
template <typename T>
int A<T>::count = 0;  // 语法:模板参数 + 类名::成员名

注意

  • 不同模板实例的静态成员是独立的(如 A<int>::countA<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;
}

关键语法规则

  1. 必须放在头文件中:类模板未实例化前无具体代码,*.cpp 编译时无法生成目标代码,链接时会报错
  2. 类外实现需重复 template <typename T> 声明
  3. 成员名前必须加 类名<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(不推荐)
二义性报错 多个偏特化版本匹配同一类型组合 新增全特化版本覆盖该类型组合,或调整偏特化条件
类型推断失败 创建对象时未指定类型参数 显式指定尖括号内的类型参数

逻辑错误

  1. 误以为静态成员是模板共享:不同实例的静态成员独立,需分别初始化
  2. 全特化后修改通用模板:全特化是独立类,通用模板修改不影响全特化版本
  3. 偏特化条件重叠:避免多个偏特化版本的条件交叉覆盖

学习提示与总结

  1. 核心价值:类模板是泛型编程的核心,实现“一次编写,多类型复用”,提升代码复用性和可维护性
  2. 重点掌握:模板定义、特化(偏特化+全特化)、成员方法实现位置、匹配优先级
  3. 实践建议
    • 优先使用类内实现简单方法,复杂方法类外实现(放在头文件)
    • 避免偏特化条件重叠,必要时用全特化解决二义性
    • 结合容器类(如 vectorlist)理解模板的实际应用

posted @ 2025-12-27 08:46  Jaklin  阅读(2)  评论(0)    收藏  举报