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

文章目录

  • 模板进阶
    • 非类型模板参数
      • 类型模板参数与非类型模板参数的不同
    • 模板的特化
      • 全特化
      • 偏特化
      • 全特化与偏特化的区别
    • 模板的分离编译
      • 解决办法
    • 总结

模板进阶

非类型模板参数

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

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

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

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

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

#include<iostream>
using namespace std;
template<class T = int,int N = 20>//T为类型模板参数,N为非类型模板参数
//一般来说 类型模板参数后面跟的都是类型,而一般非类型模板参数后面一般为整型常量
//(包括int/char/short/long/long long等)/* bool 类型也可以作为非类型模板参数的类型,bool也属于整型家族 */
class Array{T _a[N];
};void test_1(){Array<int,10> _a1;Array<double,20> _a2;}int main()
{test_1();return 0;
}


模板的特化

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

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


全特化

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

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

template<class T>
struct Less{bool operator()(const T&a,const T&b){return a<b;}
};void test_2(){int a = 10,b = 20;Less<int> lessfunc;cout<<lessfunc(a,b)<<endl;}

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

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


void test_2(){int a = 10,b = 20;Less<int*> lessfunc1;cout<<lessfunc1(&a,&b)<<endl;
}

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

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

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

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

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

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

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

  • 在进行模板特化时必须存在一个原有的模板;

    模板的特化是在原有的模板中进行一种类似重载的操作;

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

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

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

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

template<class T>bool operator()(const T&a,const T&b){return a<b;}template<>bool operator()(const int *a,const int *b){//根据特定的类型对其特化需要的操作return *a<*b;}
↑函数模板特化↑


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

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

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


偏特化

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

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

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

//原模板
template<class T>
struct Less{bool operator()(const T&a,const T&b){return a<b;}
};//偏特化处理
template<class T>//对于全特化而言,<>内不需要模板参数,而偏特化时需要显示原模板参数
struct Less<T*>{bool operator()(const T*a,const T*b)const{return *a<*b;}
};void test_3(){int a = 10;int b = 20;cout<<Less<int>()(a,b)<<endl;cout<<Less<int*>()(&a,&b)<<endl;}

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

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


全特化与偏特化的区别

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

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

全特化与偏特化的区别;

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

如:


template<class T1,class T2>
class print{void Print(T1 a,T2 b){cout<<"T1,T2"<<endl;cout<<"原模板"<<endl;}
};template<class T1>
class print<T1>{void Print(T1 a,int* b){cout<<"T1,int*"<<endl;cout<<"偏特化"<<endl;}
};template<class T1,class T2>
class print<T1,T2>{void Print(T1 a,T2* b){cout<<"T1,T2*"<<endl;cout<<"偏特化"<<endl;}
}
template<class T1>
class print<T1>{void Print(T1* a,char* b){cout<<"T1*,char*"<<endl;cout<<"偏特化"<<endl;}
};

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

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

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


模板的分离编译

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

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

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

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

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

  • 预处理

    在预处理阶段中,一般会进行以下操作:

    1. 头文件展开
    2. 宏替换
    3. 条件编译
    4. 去注释等

    同时在该阶段,原来的.c\.cpp被翻译过后将会生成一个.i后缀文件;

    同时在该次翻译过后,代码一样也为c/C++;

  • 编译

    该该阶段中,.i文件将会被翻译成.s后缀的汇编代码;

  • 汇编

    在该阶段中将会把.s后缀的文件翻译成.o的可重定向二进制目标文件;

  • 链接

    在该过程中,将会把多个.o可重定向二进制目标文件进行链接,最后生成.exe可执行程序;

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

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

  • Func.h

    #pragma oncetemplate<class T>
    void Add(const T& a, const T& b);//函数模板声明void func(int a,int b);//函数声明
  • Func.cpp

    #include<iostream>#include"Func.h"using namespace std;template<class T>
    void Add(const T& a, const T& b){cout<<a+b<<endl;
    }void func(int a,int b){cout<<a+b<<endl;
    }
    
  • main.cpp

    #include<iostream>#include"Func.h"using namespace std;int main()
    {func(10,20);Add(10,20);Add(5.5,2,2);return 0;
    }
    

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

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

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

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

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

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

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

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

/*
*main.cpp
*/#include<iostream>//在该演示中不便于展开该头文件#pragma oncetemplate<class T>
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<iostream>//在该演示中不便于展开该头文件#pragma oncetemplate<class T>
void Add(const T& a, const T& b);//函数模板声明void func(int a,int b);//函数声明using namespace std;template<class T>
void Add(const T& a, const T& b){cout<<a+b<<endl;
}void func(int a,int b){cout<<a+b<<endl;
}

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

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

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

同理func()函数也如此;

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

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

解决办法

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

  • 对模板进行显式实例化

    当在模板定义的文件中进行模板的显式实例化即可解决一部分问题;

    当然只是一部分的问题;

    #include<iostream>#include"Func.h"using namespace std;template<class T>
    void Add(const T& a, const T& b){cout<<a+b<<endl;
    }template
    void Add(const int& a ,const int& b);template
    void Add<double>(const double& a ,const double& b);
    //以上两种皆可void func(int a,int b){cout<<a+b<<endl;
    }
    

    由于进行了显式实例化,在调用的过程中会根据变量的类型优先匹配该文件内的对应的声明
    template void Add<double>(const double& a ,const double& b);

    根据该声明实例化一份对应类型的函数;


  • 分离声明定义时不采用分离编译

    即不将声明与定义分离问两个文件;


总结

模板的优缺点

  • 优点

    1. 模板采用了泛型编程,可以进行代码的复用从而可以高效的进行开发;
    2. 模板增强了代码的灵活性;
  • 缺点

    1. 模板的使用将会造成代码膨胀;

      即在原来的编译过程中只需要进行语法的判断,而对于模板而言则多增加了一个步骤为模板的实例化;

      不同的类型实例化出的代码也不同从而导致代码膨胀,同时也导致了编译时间变长;

    2. 当使用模板进行开发时,若是出现模板的编译错误,则大量的错误信息使在开发过程中不能精确找到错误位置;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/144980.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RK3568平台 GPIO子系统框架

一.gpio 子系统简介 gpio 子系统顾名思义&#xff0c;就是用于初始化 GPIO 并且提供相应的 API 函数&#xff0c;比如设置 GPIO为输入输出&#xff0c;读取 GPIO 的值等。gpio 子系统的主要目的就是方便驱动开发者使用 gpio&#xff0c;驱动 开发者在设备树中添加 gpio 相关信…

互联网Java工程师面试题·Java 面试篇·第五弹

目录 79、适配器模式和装饰器模式有什么区别&#xff1f; 80、适配器模式和代理模式之前有什么不同&#xff1f; 81、什么是模板方法模式&#xff1f; 82、什么时候使用访问者模式&#xff1f; 83、什么时候使用组合模式&#xff1f; 84、继承和组合之间有什么不同&#…

Lake Formation 和 IAM 之间的区别与联系

IAM 和 Lake Formation 都是 AWS 上的权限管理服务,且默认都是自动开启并生效的,只是如果你没有特别配置过它们,可能感觉不到它们的存在,特别是Lake Formation(后文简写为 LF),通常情况下都是“透明”的,但它确实在每次请求时进行了权限检查。本文会详细介绍一下两者之…

【LeetCode力扣】234 快慢指针 | 反转链表 | 还原链表

目录 1、题目介绍 2、解题思路 2.1、暴力破解法 2.2、快慢指针反转链表 1、题目介绍 原题链接&#xff1a; 234. 回文链表 - 力扣&#xff08;LeetCode&#xff09; 示例 1&#xff1a; 输入&#xff1a;head [1,2,2,1]输出&#xff1a;true 示例 2&#xff1a; 输入&am…

解决使用WebTestClient访问接口报[185c31bb] 500 Server Error for HTTP GET “/**“

解决使用WebTestClient访问接口报[185c31bb] 500 Server Error for HTTP GET "/**" 问题发现问题解决 问题发现 WebTestClient 是 Spring WebFlux 框架中提供的用于测试 Web 请求的客户端工具。它可以不用启动服务器&#xff0c;模拟发送 HTTP 请求并验证服务器的响…

2023最新UI酒桌喝酒游戏小程序源码 娱乐小程序源码 带流量主

2023最新UI酒桌喝酒游戏小程序源码 娱乐小程序源码 带流量主 修改增加了广告位&#xff0c;根据文档直接替换&#xff0c;原版本没有广告位 直接上传源码到开发者端即可 通过后改广告代码&#xff0c;然后关闭广告展示提交&#xff0c;通过后打开即可 无广告引流 流量主版…

Jupyter Notebook 设置黑色背景主题

Jupyter Notebook 设置黑色背景主题 # 包安装 pip install jupyterthemes -i https://mirrors.aliyun.com/pypi/simple pip install --upgrade jupyterthemes # 查看可用主题 jt -l # monokai暗背景&#xff0c;-f(字体) -fs(字体大小) -cellw(占屏比或宽度) -ofs(输出段的字…

Spark内核调度

目录 一、DAG &#xff08;1&#xff09;概念 &#xff08;2&#xff09;Job和Action关系 &#xff08;3&#xff09;DAG的宽窄依赖关系和阶段划分 二、Spark内存迭代计算 三、spark的并行度 &#xff08;1&#xff09;并行度设置 &#xff08;2&#xff09;集群中如何规划并…

HTML+CSS+JS+Django 实现前后端分离的科学计算器、利率计算器(附全部代码在gitcode链接)

&#x1f9ee;前后端分离计算器 &#x1f4da;git仓库链接和代码规范链接&#x1f4bc;PSP表格&#x1f387;成品展示&#x1f3c6;&#x1f3c6;科学计算器&#xff1a;1. 默认界面与页面切换2. 四则运算、取余、括号3. 清零Clear 回退Back4. 错误提示 Error5. 读取历史记录Hi…

HTTP响应

HTTP响应分为四个部分&#xff1a; 首行&#xff1a;HTTP/1.1&#xff08;首行&#xff09; 200&#xff08;状态码&#xff09; OK&#xff08;状态码描述&#xff09;header&#xff1a;空行&#xff1a;表示header的结束标记body&#xff1a;正文 HTTP状态码&#xff1a;…

C#实现数据导出任一Word图表的通用呈现方法及一些体会

疲惫的修改 应人才测评产品的需求&#xff0c;导出测评报告是其中一个重要的环节&#xff0c;报告的文件类型也多种多样&#xff0c;其中WORD输出也扮演了一个重要的角色。 实现方法比较简单&#xff0c;结合分析结果数据&#xff0c;通过WORD模板文件进行替换输出。在实现的…

BadNets: Identifying Vulnerabilities in the Machine Learning Model Supply Chain

BadNets: Identifying Vulnerabilities in the Machine Learning Model Supply Chain----《BadNets:识别机器学习模型供应链中的漏洞》 背景&#xff1a; 许多用户将训练过程外包给云计算&#xff0c;或者依赖于经过训练的模型&#xff0c;然后根据特定的任务对模型进行微调。这…