• 『C++ - 模板』之模板进阶


    模板进阶

    非类型模板参数

    类型模板参数与非类型模板参数的不同

    • 类型模板参数
    • 非类型模板参数

    类型模板参数一般用来设置模板的类型;

    而非类型模板参数默认为整形常量;

    同时作为模板参数它们都可以进行定义缺省值;(同时,由于是整型常量,所以只能作为模板参数而不能再其他地方再进行赋值,因为左值不能被修改,而N为整形常量为左值)

    #include
    using namespace std;
    template//T为类型模板参数,N为非类型模板参数
    //一般来说 类型模板参数后面跟的都是类型,而一般非类型模板参数后面一般为整型常量
    //(包括int/char/short/long/long long等)
    
        /* bool 类型也可以作为非类型模板参数的类型,bool也属于整型家族 */
    class Array{
      T _a[N];
    };
    
    void test_1(){
    
      Array _a1;
      Array _a2;
    
    }
    
    int main()
    {
    	test_1();
      return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24


    模板的特化

    模板的特化分为两种,分为类模板的特化以及函数模板的特化(函数模板暂不支持偏特化)

    同时还可以分为全特化偏特化(部分特化);


    全特化

    特化一般至在原有泛型编程的基础上,对某些类型或者参数进行特殊的处理;

    假设有一个仿函数的函数模板,该函数模板可以对同个类型进行较小比较;

    
    
    template
    struct Less{
      bool operator()(const T&a,const T&b){
        return a lessfunc;
      cout<
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    运行test_2函数后所得的结果为1;

    但是若是传的数据类型为指针类型,则讲出现不一样的答案;

    
    void test_2(){
      int a = 10,b = 20;
      Less lessfunc1;
      cout<
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    由于在在C++中指针也可以进行比较,所以在这里也进行了比较,但是打印出的结果为0;

    原因是虽然进行了比较但是比较只是指针中的单纯比较,并不是我们需要的结果;

    而在C++中却出现了模板特化的语法;

    //原模版
    template
    struct Less{
      bool operator()(const T&a,const T&b){
        return a//语法中模板特化不需要定义模板参数
    struct Less{//而在此处时应该声明需要特化的类型
      bool operator()(const int *a,const int *b){//根据特定的类型对其特化需要的操作
        return *a<*b;
      }
    };
    
    void test_2(){
      int a = 10,b = 20;
      Less lessfunc;
     //cout< lessfunc1;
      cout<

运行上面这段程序将可以得出预期的结果1;

在模板的特化中需要注意特化的语法;

同时在模板的特化时需要注意:

同时模板的特化和函数的重载有一定的区别,在一个类中,相应的类与其模板可以同时存在;

且相应的函数模板也可以与其相应的函数声明同时存在;

只不过一个属于模板,一个属于声明;(模板只有在调用的时候根据模板参数的类型去实例化相应的类/函数)

上段代码演示的为类模板特化;

template
  bool operator()(const T&a,const T&b){
    return a
  bool operator()(const int *a,const int *b){//根据特定的类型对其特化需要的操作
    return *a<*b;
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
↑函数模板特化↑


在大多数情况下,函数模板特化的使用较少;

因为在大多情况下函数可以进行重载(模板与声明可以同时存在,构成重载);

同时应该注意,函数模板暂不支持偏特化;


偏特化

对于模板的特化除了全特化以外还有偏特化;

全特化类似于重载,但是对于偏特化而言,只是在原有的模板基础上增添(进一步进行)了类型的限制;

即可以对一定类型的模板参数进行特殊处理;

//原模板
template
struct Less{
  bool operator()(const T&a,const T&b){
    return a//对于全特化而言,<>内不需要模板参数,而偏特化时需要显示原模板参数
struct Less{
  bool operator()(const T*a,const T*b)const{
    return *a<*b;
  }
};

void test_3(){
  int a = 10;
  int b = 20;

  cout<()(a,b)<()(&a,&b)<

该处对test_3函数进行调用时得出的结果都未true;

与全特化处理而言,偏特化处理更适合处理某一类型的特殊类型,在这里演示了统一对指针进行处理;


全特化与偏特化的区别

全特化与偏特化的命名方式与特化的模板参数数量无关;

只与特化的模板参数的规则有关;

全特化与偏特化的区别;

这个规则是指是否完全限制所有模板参数以致达到全特化的效果;

如:


template
class print{
	void Print(T1 a,T2 b){
  		cout<<"T1,T2"<
class print{
	void Print(T1 a,int* b){
  		cout<<"T1,int*"<
class print{
	void Print(T1 a,T2* b){
 		cout<<"T1,T2*"<
class print{
		void Print(T1* a,char* b){
  		cout<<"T1*,char*"<

如上段代码而言,这里的特化行为并没有指定所有的模板参数为某个单独的类型,而是限制了部分模板参数使其为某一类的类型或者说是某一种单独类型;

所以在这里可以将偏特化分为三种表现形式:

  1. 部分模板参数指定类型进行特化( 例: < T1 , T2 > 特化为 < T1 , int* > );
  2. 所有模板参数将其特化为该类型的限制( 例: < T1 , T2 > 特化为 < T1* , T2& > );
  3. 以上两种的集合( 例: < T1 , T2 > 特化为 < T1* , double* > )


模板的分离编译

对于模板来说是不支持分离编译的,即分文件进行声明与定义;

但是在同文件种是可以进行声明定义分离的;

为什么在多文件的情况下不支持声明与定义分离?

首先我们要了解c/C++程序的翻译过程;

一个程序的翻译过程一般包括四步:

假设有三个文件Func.h文件用于声明,Func.cpp用于定义,main.cpp用于测试;

三个文件内的代码分别为:

假设运行上面这段程序将会出现链接失败;

连接失败的原因即为模板进行了分离编译;

从该图可以看出程序翻译的过程;

其实在最初的预处理中的展开头文件就能了解到问题的所在;

首先模板是一个未经过实例化的存在;

在这个程序中,func()函数可以被直接进行调用的原因是因为即使声明定义分离,函数的定义也仍然存在主体;

但是对于模板来说是一个不存在实体即没有被实例化的存在;

在首先的预处理阶段,头文件展开,此时main.cpp文件内与Func.cpp文件内的内容大概如下;

/*
*main.cpp
*/

#include//在该演示中不便于展开该头文件

#pragma once

template
void Add(const T& a, const T& b);//函数模板声明

void func(int a,int b);//函数声明

using namespace std;

int main()
{
    func(10,20);
    
    Add(10,20);
    Add(5.5,2,2);
    
    return 0;
}
/*
*Func.cpp
*/

#include//在该演示中不便于展开该头文件

#pragma once

template
void Add(const T& a, const T& b);//函数模板声明

void func(int a,int b);//函数声明

using namespace std;

template
void Add(const T& a, const T& b){
    cout<

从上面两个文件可以看出,为什么在编译过程中并没有报错而最后在链接的过程中才进行报错;

在编译过程中,main.i文件内由于头文件的展开,已经存在了模板;

意思当Add()函数调用时,找到了对应的声明;

同理func()函数也如此;

而函数真正的调用在链接阶段,链接阶段将会通过对应声明的函数的地址找到函数的定义从而进行调用;

但是在这里实际中Add()函数模板并未进行实例化,所以才导致的链接错误;

解决办法

那有什么办法可以解决对于模板的分离编译吗?



总结

模板的优缺点

  • 相关阅读:
    Error: Cannot find module ‘timers/promises‘
    devops-2:Jenkins的使用及Pipeline语法讲解
    [案例] java项目故障诊断和性能调优(Linux版)
    ruoyi 前后端分离 增加手机号登录
    Gradle 设置全局镜像源
    【DL论文精读笔记】Object Detection in 20 Years: A Survey目标检测综述
    Kotlin协程:MutableSharedFlow的实现原理
    循环神经网络
    二叉树简介
    Seata 源码篇之AT模式启动流程 - 下 - 04
  • 原文地址:https://blog.csdn.net/2202_75303754/article/details/133973846