重生之小比特头像
关注
【初阶C++】模板进阶封面图

【初阶C++】模板进阶

 

目录

 ​编辑

本节学习目标

1. 非类型模板参数

⚠️非类型模板参数使用限制

2. 模板特化

2.1 函数模板特化

2.2 类模板特化

2.2.1 全特化

2.2.2 偏特化

3. 模板的分离编译

3.1 什么是分离编译

3.2 模板分离编译报错问题

✅两种解决方案

4. 模板优缺点总结

✅模板优点

❌模板缺点

📔考点总结


本节学习目标

  1. 掌握非类型模板参数,使用编译期常量作为模板参数,清楚使用限制
  2. 理解模板特化:函数模板特化、类模板全特化、类模板偏特化,解决特殊类型逻辑
  3. 理解模板分离编译问题,明白链接报错根源,掌握两种解决方案
  4. 总结模板的优点与缺陷

1. 非类型模板参数

模板参数分为两类:

  1. 类型形参:template<class T>,T代表一种数据类型。
  2. 非类型形参:编译期常量,作为模板内的常量使用。

示例:模拟静态数组,N是非类型模板参数,代表数组大小

namespace bite
{
// N为非类型模板参数,带默认值10
template<class T, size_t N = 10>
class array
{
public:
    T& operator[](size_t index){return _array[index];}
    const T& operator[](size_t index)const{return _array[index];}
    size_t size()const{return N;}
    bool empty()const{return 0 == N;}
private:
    T _array[N]; //编译期确定大小的静态数组
};
}

⚠️非类型模板参数使用限制

  1. 只能是编译期可以确定结果的常量,不能传变量。
  2. 禁止浮点数、类对象、字符串字面量作为非类型模板参数。

允许:size_t、int、指针、引用等编译期可求值的整型 / 地址常量。

2. 模板特化

基础模板实现通用逻辑,但是部分特殊类型(如指针)按照通用模板会得到错误结果。 模板特化:针对某一类特殊类型,单独定制一套实现。 分为:函数模板特化、类模板特化(全特化、偏特化)。

2.1 函数模板特化

示例:通用比较Less模板。 普通类型可以正常比较;传入指针时,通用模板比较指针地址,不是指针指向对象内容,逻辑错误。

template<class T>
bool Less(T left, T right)
{
    return left < right;
}

//假设Date类已经定义
int main()
{
    Less(1,2);          //正常,比较数值
    Date d1(2022,7,7);
    Date d2(2022,7,8);
    Less(d1,d2);        //正常,比较对象
    Date* p1 = &d1;
    Date* p2 = &d2;
    Less(p1,p2);        //错误!比较的是指针地址,不是*p1、*p2对象内容
    return 0;
}

✅函数模板特化步骤:

  1. 必须先存在基础函数模板;
  2. template<>尖括号为空;
  3. 函数名后<特化的类型>;
  4. 形参类型必须和基础模板完全匹配。
//基础模板
template<class T>
bool Less(T left, T right)
{
    return left < right;
}

//针对Date*指针特化
template<>
bool Less<Date*>(Date* left, Date* right)
{
    return *left < *right; //解引用,比较对象内容
}

实操经验: 函数模板特化可读性一般,遇到特殊类型,优先直接写普通重载函数,比特化简单好维护。

//直接重载,替代特化,推荐写法
bool Less(Date* left, Date* right)
{
    return *left < *right;
}

2.2 类模板特化

2.2.1 全特化

全特化:把模板所有参数全部指定为确定类型。

template<class T1, class T2>
class Data
{
public:
    Data(){cout<<"Data<T1, T2>" <<endl;}
private:
    T1 _d1;
    T2 _d2;
};

//全特化:T1=int,T2=char,全部确定
template<>
class Data<int, char>
{
public:
    Data(){cout<<"Data<int, char>" <<endl;}
private:
    int _d1;
    char _d2;
};

void Test()
{
    Data<int,int> d1;    //走通用模板
    Data<int,char> d2;   //走全特化版本
}
2.2.2 偏特化

偏特化不是只特化一部分参数,包含两种形式:

  1. 部分参数特化:只确定一部分模板参数,剩下保留模板参数;
  2. 参数进一步限制:对模板参数施加类型约束(例如限定为指针、引用)。
template<class T1, class T2>
class Data
{
public:
    Data(){cout<<"Data<T1, T2>"<<endl;}
private:
    T1 _d1;
    T2 _d2;
};

//①部分特化:第二个参数固定为int,T1仍然是模板参数
template <class T1>
class Data<T1, int>
{
public:
    Data(){cout<<"Data<T1, int>"<<endl;}
private:
    T1 _d1;
    int _d2;
};

//②参数条件限制:两个参数限定为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public:
    Data(){cout<<"Data<T1*, T2*>"<<endl;}
private:
    T1 _d1;
    T2 _d2;
};

类模板特化实战场景:sort排序,容器存指针,需要比较指针指向对象而不是指针地址。

template<class T>
struct Less
{
    bool operator()(const T& x, const T& y) const
    {
        return x < y;
    }
};

//对指针类型特化
template<>
struct Less<Date*>
{
    bool operator()(Date* x, Date* y) const
    {
        return *x < *y;
    }
};

3. 模板的分离编译

3.1 什么是分离编译

C/C++ 工程:多个.h头文件、.cpp源文件;每个 cpp单独编译生成 obj 目标文件,最后链接器把所有 obj 合并成可执行程序。

3.2 模板分离编译报错问题

错误写法:模板声明放.h,模板实现定义放在.cpp。

// a.h
template<class T>
T Add(const T& left, const T& right);

// a.cpp
template<class T>
T Add(const T& left, const T& right)
{
    return left + right;
}

// main.cpp
#include "a.h"
int main()
{
    Add(1,2);
    Add(1.0,2.0);
    return 0;
}

编译过程分析:

  1. a.cpp单独编译,本文件看不到Add<int>、Add<double>实例调用,不会生成实例化的机器代码。
  2. main.cpp看到模板声明,编译通过;但是链接的时候找不到Add<int>、Add<double>的 obj 实现,报无法解析的外部符号链接错误。

模板是编译期实例化:只有看到完整模板定义,编译器才会生成对应类型实例代码。

✅两种解决方案

  1. 【工程推荐】声明和实现全部放在同一个头文件,后缀一般写.hpp。 .hpp头文件把模板声明 + 实现写在一起,#include时编译器看到完整模板定义,可以按需实例化。
  2. 显式实例化(极少用,不推荐),提前手动实例化需要的类型,扩展性差。

4. 模板优缺点总结

✅模板优点

  1. 代码复用,一套逻辑适配多种类型,STL 标准库大量基于模板实现。
  2. 代码灵活性高,编译期实例化,运行没有额外开销。

❌模板缺点

  1. 代码膨胀:不同类型都会实例生成一份代码,可执行文件体积变大;编译时间增加。
  2. 编译报错信息冗长晦涩,报错定位困难。

📔考点总结

  1. 非类型模板参数:编译期常量;不能用浮点数、对象、字符串,只能编译期确定的值。
  2. 模板特化:针对特殊类型定制实现。
    • 函数模板特化:优先考虑普通重载替代特化;
    • 类模板:全特化(所有参数确定);偏特化(部分参数固定 / 参数施加约束,如指针)。
  3. 分离编译大坑:模板不要把头文件声明和 cpp 实现分开放;统一放到.hpp;否则链接报无法解析外部符号。
  4. 模板优点:复用、灵活;缺点:代码膨胀、报错信息难懂。

面试简答 Q:非类型模板参数可以传 double 吗?

A:不可以,只接受编译期整型常量,浮点数、对象、字符串不允许。

Q:模板为什么不能头声明、cpp 放实现?

A:cpp 编译单元看不到调用的类型,不会实例化生成函数代码,链接找不到符号报错;建议全部写在 hpp 头文件。

Q:类模板偏特化和全特化区别?

A:全特化所有模板参数全部指定;偏特化可以只固定部分参数,或者对参数施加条件(指针、引用)。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/wuqingyigexiaoba/article/details/166141049

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--