C++_模板进阶_非类型模板参数_模板特化_分离编译

一、非类型模板参数

模板参数,分为类型形参和非类型形参。

类型形参就是在模板中跟在typename和class之后的参数类型名称,非类型形参就是用一个常量作为类模板或者函数模板的一个参数,在类模板和函数模板中,可以将该参数当作常量来使用。

例如,现在要实现一个静态栈:

using namespace std;
#include <iostream>
#define N 10template <typename T>
class Stack
{//......
private:T _a[N];int _top;
};int main()
{Stack<int> st1;Stack<int> st2;return 0;
}

问题是,这个栈的大小只有10,因为先前就在宏定义中将N定义为了10。所以不能根据需求对栈进行实例化。

那么现在就可以用到非类型模板参数了,可以这样来实现栈:

using namespace std;
#include <iostream>template <typename T,size_t N>
class Stack
{//......
private:T _a[N];int _top;
};int main()
{Stack<int,10> st1;Stack<int,100> st2;return 0;
}

从监视1窗口可以看到,st1和st2大小分别为10和100:

除此之外,应该注意浮点数、类对象以及字符串是不允许作为非类型模板参数的;非类型的模板参数必须在编译期就能确认结果。

二、模板的特化

先来看几个例子。

定义一个函数模板如下:

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

测试1:

int main()
{int num0 = 0;int num1 = 1;bool res = Less(num0,num1);return 0;
}

可以正常运行。

测试2:

int main()
{double num0 = 0;double num1 = 1;bool res = Less(num0, num1);return 0;
}

可以正常运行。

测试3,这次让指针进行比较:

int main()
{double num0 = 0;double num1 = 1;double* pnum0 = &num0;double* pnum1 = &num1;bool res = Less(pnum0, pnum1);return 0;
}

 

根据监视1窗口的结果,可以看到返回值为false。事实上,num0的值是小于num1的值的,返回值为true才对。但是这里是对指针进行大小比较,这是一种没有意义的比较。

因此就要用特化来处理。

特化即在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。

模板特化中分为函数模板特化与类模板特化。

函数模板特化的步骤:

1. 必须要先有一个基础的函数模板
2. 关键字template后面接一对空的尖括号<>
3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。 

针对Less模板,当参数为指针时,应该这样特化:

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

这次调用,就会发现返回值正常了:

不过应注意,一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出。 

比如,刚才对double*类型进行特化,其实完全可以写成如下代码:

bool Less(double* left, double* right)
{return *left < *right;
}

该种实现简单明了,代码的可读性高,容易书写,因为对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化。

上述内容是函数模板的特化,接下来还有类模板的特化。

类模板的特化分为全特化和偏特化。

全特化即是将模板参数列表中所有的参数都确定化,比如:

template<class T1,class T2>
class Data
{
public:Data(){cout << "template<class T1,class T2>" << endl;}
private:T1 t1;T2 t2;
};template<>
class Data<int, double>
{
public:Data(){cout << "template<>" << endl;}
private:int t1;int t2;
};

偏特化又分为部分特化和参数的进一步限制,部分特化如下:

template<class T1, class T2>
class Data
{
public:Data() { cout << "Data<T1, T2>" << endl; }
private:T1 _d1;T2 _d2;
};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;
};

这是将两个参数特化为引用:

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

三、模板的分离编译

一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。

首先明确,模板要尽量避免分离编译!!!

这是因为,C++标准明确表示,当一个模板不被用到的时侯它就不该被实例化出来。

比如:

//a.cpp
#include "a.h"
template <class T>
bool Less(T left, T right)
{return left < right;
}//a.h
template <class T>
bool Less(T left, T right);//test.cpp
#include "a.h"
int main()
{int num0 = 0;int num1 = 1;Less(num0,num1);
}

 当调试的时候,会发现编译器会有如下提示:

先前已提到,当一个模板不被用到的时侯它就不该被实例化出来。

虽然说,在test.cpp中用到了Less函数,但是a.cpp中并没有用到cpp函数,也就是说它没有被实例化。

造成这一结果的原因是,.h文件里的代码将会被扩展到包含它的.cpp文件里,然后编译器编译该.cpp文件为一个.obj文件,在本例中,a.h的代码被扩展到a.cpp中,test.cpp和a.cpp都会被编译器生成为.obj文件。

正常情况下,编译器会将test.cpp中的Less看作外部链接,也就是会在其它的.obj文件中查找Less函数的二进制地址,去调用。

然而,模板函数的代码其实并不能直接编译成二进制代码,其中要有一个“实例化”的过程!!!

所以,为避免这种情况发生,可以采取以下两种方法:

1.将声明和定义放在同一个.h或.hpp文件中;

如:

//a.h
template<class T>
T Add(T left, T right);template<class T>
T Add(T left, T right)
{return left + right;
}//test.cpp
#include "a.h"int main()
{int num0 = 0;int num1 = 1;int res = Add(num0,num1);
}

 2.模板定义的位置显式实例化,但是不推荐这种用法。

//a.cpp
template<class T>
T Add(T left, T right)
{return left + right;
}template
int Add<int>(int, int);//a.h
template<class T>
T Add(T left, T right);//test.cpp
#include "a.h"
int main()
{int num0 = 0;int num1 = 1;int res = Add(num0,num1);
}

 

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

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

相关文章

【JUC系列-01】深入理解JMM内存模型的底层实现原理

一&#xff0c;深入理解JMM内存模型 1&#xff0c;什么是可见性 在谈jmm的内存模型之前&#xff0c;先了解一下并发并发编程的三大特性&#xff0c;分别是&#xff1a;可见性&#xff0c;原子性&#xff0c;有序性。可见性指的就是当一个线程修改某个变量的值之后&#xff0c…

卷积神经网络全解:(AlexNet/VGG/ GoogLeNet/LeNet/ResNet/卷积/激活/池化/全连接)、现代卷积神经网络、经典卷积神经网络

CNN&#xff0c;卷积神经网络&#xff0c;Convolution Neural Network 卷积计算公式&#xff1a;N &#xff08;W-F2p&#xff09;/s1 这个公式每次都得看看&#xff0c;不能忘 1 经典网络 按照时间顺序 1.1 LeNet LeNet是 Yann LeCun在1998年提出&#xff0c;用于解决手…

LeetCode 88. Merge Sorted Array【数组,双指针】简单

本文属于「征服LeetCode」系列文章之一&#xff0c;这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁&#xff0c;本系列将至少持续到刷完所有无锁题之日为止&#xff1b;由于LeetCode还在不断地创建新题&#xff0c;本系列的终止日期可能是永远。在这一系列刷题文章…

Lua 数据结构

一、Lua 中的数据结构 Lua 中并没有像 java、kotlin 语言有专门的数组、列表、集合等数据结构的类&#xff0c;而只提供 table 类型&#xff0c;但他很强大而且也很灵活&#xff0c;而且也在很多场景中天然的就解决了如何对接和使用的问题&#xff08;例如模块的引入&#xff…

MYSQL完全卸载、安装与账号创建、权限控制

一、卸载mysql CentOS 卸载 MySQL 1. 查看安装情况 使用以下命令查看当前安装mysql情况&#xff0c;查找以前是否装有mysql rpm -qa|grep -i mysql这里显示我安装的 MySQL 服务有有&#xff1a; 2. 停止 mysql 服务、删除之前安装的 mysql 删除命令&#xff1a;rpm -e –n…

Git checkout 某个版本到指定文件夹下

文章目录 场景说明方案一&#xff1a;git archive 最简单省事方案二&#xff1a;git show 最灵活, 但文件较多时麻烦方案三&#xff1a;git --work-tree 有bug 场景说明 我不想checkout到覆盖本地工作区的文件&#xff0c; 而是想把该版本checkout到另外一个文件夹下&#xff…

C++笔记之条件变量(Condition Variable)与cv.wait 和 cv.wait_for的使用

C笔记之条件变量&#xff08;Condition Variable&#xff09;与cv.wait 和 cv.wait_for的使用 参考博客&#xff1a;C笔记之各种sleep方法总结 code review! 文章目录 C笔记之条件变量&#xff08;Condition Variable&#xff09;与cv.wait 和 cv.wait_for的使用1.条件变量&…

v8引擎编译全过程

环境vs2019 cmd 命令行需要设置成为代理模式 set http_proxyhttp://127.0.0.1:10809 set https_proxyhttp://127.0.0.1:10809 这个必须带上&#xff0c;不然报错&#xff0c;告诉编译器win系统的模式 set DEPOT_TOOLS_WIN_TOOLCHAIN0 源码 GitHub: GitHub - v8/v8: The…

Eclipse如何设置快捷键

在eclopse设置注释行和取消注释行 // 打开eclipse&#xff0c;依次打开&#xff1a;Window -> Preferences -> General -> Key&#xff0c;

代码随想录训练营-回溯03

用于记录为期60天的算法提升过程&#xff0c;今天是第27天 &#x1f578;️代码随想录训练营-回溯&#x1f578;️ 39. &#x1f338;组合总和&#x1f338;思路&#xff1a;代码 40. &#x1f338;组合总和 II&#x1f338;思路代码&#xff1a; 131. 分割回文串 39. &#x…

solidwords(6)

从右视图开始&#xff0c;分上下两部分 标题 这里的薄壁要留意一下怎么算的&#xff08;单向&#xff1a;默认向内&#xff1b;如果想向外记得选反向&#xff09;

企业Web安全治理的十三个要点

因为今天刚汇报了23年H1的工作内容&#xff0c;H1的内容和之前在CSDN发布帖子&#xff0c;但是经过了整理后的。基本上是比较全面和精练的。所以这里再列举一下相关情况&#xff0c;即安全治理的几个要点&#xff1a; 其实基本上的企业Web安全治理内容我总结为如下&#xff1a…

【Spring专题】Spring之Bean的生命周期源码解析——阶段二(二)(IOC之属性填充/依赖注入)

目录 前言阅读准备阅读指引阅读建议 课程内容一、依赖注入方式&#xff08;前置知识&#xff09;1.1 手动注入1.2 自动注入1.2.1 XML的autowire自动注入1.2.1.1 byType&#xff1a;按照类型进行注入1.2.1.2 byName&#xff1a;按照名称进行注入1.2.1.3 constructor&#xff1a;…

idea 新建servlet 访问提示404 WebServlet注解找不到包 报错

检查访问路径是否设置正确 如果设置为name “/testServlet”&#xff0c;则会404 WebServlet注解报错找不到包 检查是否引入了tomcat依赖包

线性代数的学习和整理8: 方阵和行列式相关(草稿-----未完成)

1.4.1 方阵 矩阵里&#xff0c;行数列数的矩阵叫做方阵方阵有很多很好的特殊属性 1.4.2 行列式 行列式是方阵的一种特殊运算如果矩阵行数列数相等&#xff0c;那么这个矩阵是方阵。行列数的计算方式和矩阵的不同只有方阵才有行列式行列式其实是&#xff0c;矩阵变化的一个面…

超越函数界限:探索JavaScript函数的无限可能

&#x1f3ac; 岸边的风&#xff1a;个人主页 &#x1f525; 个人专栏 :《 VUE 》 《 javaScript 》 ⛺️ 生活的理想&#xff0c;就是为了理想的生活 ! 目录 &#x1f4da; 前言 &#x1f4d8; 1. 函数的基本概念 &#x1f4df; 1.1 函数的定义和调用 &#x1f4df; 1.2 …

动态内存管理

目录 为什么要用动态内存开辟 动态内存有关函数 void* malloc (size_t size); void free (void* ptr); void* calloc (size_t num, size_t size); void* realloc (void* ptr, size_t size); C/C程序的内存开辟 柔性数组 特点&#xff1a; 柔性数组的使用&#xff1a; 为什么要用…

【nodejs】用Node.js实现简单的壁纸网站爬虫

1. 简介 在这个博客中&#xff0c;我们将学习如何使用Node.js编写一个简单的爬虫来从壁纸网站获取图片并将其下载到本地。我们将使用Axios和Cheerio库来处理HTTP请求和HTML解析。 2. 设置项目 首先&#xff0c;确保你已经安装了Node.js环境。然后&#xff0c;我们将创建一个…

搜索旋转排序数组

整数数组 nums 按升序排列&#xff0c;数组中的值 互不相同 。 在传递给函数之前&#xff0c;nums 在预先未知的某个下标 k&#xff08;0 < k < nums.length&#xff09;上进行了 旋转&#xff0c;使数组变为 [nums[k], nums[k1], …, nums[n-1], nums[0], nums[1], …, …

学习笔记|基于Delay实现的LED闪烁|u16是什么|a--和--a的区别|STC32G单片机视频开发教程(冲哥)|第六集(上):实现LED闪烁

文章目录 摘要软件更新什么是闪烁Tips:u16是什么? 语法分析&#xff1a;验证代码Tips&#xff1a;a--和--a的区别&#xff08;--ms 的用法&#xff09;测试代码&#xff1a; 摘要 1.基于Delay实现的LED闪烁 2.函数的使用 3,新建文件&#xff0c;使用模块化编程 软件更新 打…