C++---模板进阶(非类型模板参数,模板的特化,模板分离编译)
我们都学习和使用过模板,而这篇文章我们来将一些更深入的知识。在此之前,我们在使用C++编程时可以看到模板是随处可见的,它能支持泛型编程。模板包括函数模板和类模板,我们有的人可能会说是模板函数和模板类,但严格讲这样说是错误的。而在我们实际使用中,类模板用的场景是比函数模板多的,如STL中vector,list等都是类模板,而算法中sort,find等是函数模板。
非类型模板参数
模板参数分为类型模板形参,非类型形参。
类型形参:出现在模板参数列表中,跟在class或typename关键字之后的参数类型名称。(我们之前一直使用的)
非类型形参:用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
举个例子:
我们想要创建一个大小固定,可以存储不同类型元素的数组类,我们可以这样写:
#include<iostream>
using namespace std;
#define N 10template<class T>
class Array
{
private:int _a[N];
};
int main()
{Array<int> arr1;Array<int> arr2;return 0;
}
这时实例化出来的arr1,arr2都是大小为10的定长数组。但我们假如说想改变一个数组的长度呢?例如我们想将数组长度改为20,怎么办呢?有同学说我们可以将N define为20呀。但是假如我们想要一个数组长为10,另一个长为20呢?
这时候我们的非类型模板参数就有作用了,我们可以将Array定义为下面的形式:
#include<iostream>
using namespace std;//这里的N叫做非类型模板参数,它是一个常量
template<class T, size_t N>
class Array
{
private:int _a[N];
};
int main()
{Array<int, 10> arr1;Array<int, 20> arr2;int n = 10;Array<int, n> arr3;//error 这里非类型模板参数是常量,这里会报错return 0;
}
对于非类型模板参数的使用场景,STL中的deque和array容器中都用了。deque中的非类型模板参数用来传一个一个常量来控制buff的大小。
我们下面介绍一下array容器:
C++11支持array,它的结构类似于vector,只不过vector是动态数组,而array是静态的。它不提供头插,头插,尾插,尾删,任意位置插入删除这种操作,因为它根本不存在这种说法,而且它可以直接使用operator[ ] 访问修改任意位置的数据。
array的缺陷:
我们不推荐使用array,array底层是在栈上开辟空间的,而栈空间又是很有限的,例如:在32位的linux下栈空间只有8M,所以一般开大空间时极不推荐使用array,相比之下vector就很有优势。而且在知道要开多大空间的情况下,vector 也可以通过reserve一次性开好空间,在后续的使用中还可以自动增容,而array空间是固定的,不灵活。所以我们一般不使用array。
注意:
- 非类型模板参数只允许使用整形家族(int,short,long,long long,char),而浮点型,类对象,字符串是不允许作为非类型模板参数的
- 非类型模板参数需要在编译的时候就确认结果,因为编译器在编译阶段就需要根据传入的非类型模板参数生成对应的类或函数
模板的特化
我们先举个例子,理解一下模板的特化,看下面的代码:
template<class T>
bool IsEqual(const T& left, const T& right)
{ return left == right;
}
int main()
{cout << IsEqual(1, 2) << endl;char p1[] = "hello";char p2[] = "world";cout << IsEqual(p1, p2) << endl;return 0;
}
我们实现了IsEqual函数用来判断两个参数是否相等。我们总共调用了两次,第一次比较的是两个整数,是没问题的;而第二次实际上是有问题的,我们本意是想比较两个字符串是否相等,可是我们仔细看看:我们实际上比较的p1,p2两个指针。我们可能会想先判断一下传过来的参数的类型是什么,再进行不同的操作,就像下面这样:
template<class T>
bool IsEqual(const T& left, const T& right)
{if(T == char*)//error,C++中不支持类型比较{return *left==*right;}else{...}return left == right;
}
int main()
{cout << IsEqual(1, 2) << endl;//okchar* p1 = "hello";char* p2 = "world";cout << IsEqual(p1, p2) << endl;//errreturn 0;
}
但是很可惜,C++中不支持类型比较,所以上面写的是错误的。
那么这里就需要我们的模板特化出手了,模板特化的目的就是在原模板类的基础上,针对某些类型进行特殊化处理。模板特化又分为函数模板特化和类模板特化。
函数模板的特化
- 首先必须有一个基础的函数模板
- 关键字template后面接一对空的尖括号<>
- 函数名后跟一对<>,尖括号内指定需要特化的类型
- 函数形参表必须要和函数模板的基础参数类型完全相同,如果不同,编译器可能会报一些奇怪的错误
我们上面的代码就可以改为:
#include<iostream>
using namespace std;template<class T>
bool IsEqual(const T& left, const T& right)
{return left == right;
}template<>
bool IsEqual<const char *&>(const char*& left,const char*& right)
{return strcmp(left, right)==0;
}
int main()
{cout << IsEqual(1, 2) << endl;const char* p1 = "hello";const char* p2 = "hello";cout << IsEqual(p1, p2) << endl;return 0;
}
说明:其实一般情况下,如果函数模板遇到不能处理或者处理有误的类型,可以直接将该函数直接给出,这叫做模板的匹配原则:有现成的完全匹配的,就直接调用,没有现成调用的,实例化模板生成。上面的代码就可以这样写:
#include<iostream>
using namespace std;template<class T>
bool IsEqual(const T& left, const T& right)
{return left == right;
}bool IsEqual(const char*& left,const char*& right)
{return strcmp(left, right)==0;
}
int main()
{cout << IsEqual(1, 2) << endl;const char* p1 = "hello";const char* p2 = "hello";cout << IsEqual(p1, p2) << endl;return 0;
}
对于模板匹配原则和函数模板特化,两者底层没有任何差别,如果能使用函数模板特化的时候更推荐使用函数模板特化。
类模板特化
1.全特化
就是将模板参数列表中所有参数确定化,例如:
#include<iostream>
using namespace std;template<class T1,class T2>
class A
{
public:A(){cout << "T" << endl;}
private:T1 a1;T2 a2;
};template<>
class A<int,int>
{
public:A(){cout << "int" << endl;}
private:
};int main()
{A<double, double>a1;A<int, int>a2;return 0;
}
2.偏特化
也叫半特化:即对模板参数进行一定的确定化,例如:
template<class T1>//第二个参数是int类型就会调用这个
class A<T1, int>
{
public:A(){cout << "T1,int" << endl;}
private:
};template<class T2>//第一个参数是int类型就会调用这个
class A<int, T2>
{
public:A(){cout << "int,T2" << endl;}
private:
};template<class T1,class T2>//两个参数都是指针类型就会调用这个
class A<T1*, T2*>
{
public:A(){cout << "T1*,T2*" << endl;}
private:
};template<class T1, class T2>//两个参数都是引用类型就会调用这个
class A<T1&, T2&>
{
public:A(){cout << "T1&,T2&" << endl;}
private:
};template<class T1, class T2>//第一个参数是指针类型,第二个参数是引用类型就会调用这个
class A<T1*, T2&>
{
public:A(){cout << "T1*,T2&" << endl;}
private:
};int main()
{A<double, int>a;A<int, char>b;A<double*, int*>c;A<char&, int&>d;A<double*, char&>e;return 0;
}
上面这几种特化版本都是偏特化。
偏特化并不仅仅是指特化,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本,例如我们可以限制他的类型是引用,指针之类的。
关于特化的场景我们等到哈希表的时候会给大家介绍。
模板分离编译
我们在工作的时候写的项目都会由若干个源文件共同实现,每个源文件独立编译生成目标文件,最后将所有目标我呢见链接起来形成单一的可执行文件的过程称为分离编译模式。
模板的分离编译
在前面我们模拟实现string,vector,list等STL容器的时候,都没有将声明和定义分开。我们实际上是习惯将声明放在头文件中,将定义放在.cpp文件中的。我们没有分离的原因就是C++的模板不支持分离编译。
我们以这段代码为例:
//Func.h代码:
#pragma once
#include<iostream>
using namespace std;void Print();template<class T>
void F(T a);//Func.cpp代码
#include"Func.h"void Print()
{cout << "print" << endl;
}template<class T>
void F(T a)
{cout << "F(T a)" << endl;
}//test.cpp代码
#include"Func.h"int main()
{Print();F(10);//这里编不过return 0;
}
不支持分离编译的原因:
我们都知道:程序的编译过程分为:预处理,编译,汇编,链接四个步骤
其中预处理会做的事情就是:头文件展开,宏替换,条件编译,去掉注释,在linux环境下就生成了.i文件
编译:检查语法错误后,生成汇编代码,在linux环境下生成.s文件

汇编:将汇编代码转成二进制机器码,在linux环境下生成.o文件
链接:会把.o文件里F或Print这样没有地址的地方,用被修饰过的函数名去Func.o中的符号表找对应的地址,然后填上地址就像上图中的地址一样。然后再把目标文件合并成可执行程序。
而问题就出现在链接的时候,编译器拿着我们的函数名去符号表中找时,能找到Print()函数,但是却找不到F()函数,这是因为在编译阶段,Print()函数有定义,可以生成。但是F()函数是一个函数模板,他不能生成,因为我们不知道T是什么类型。模板其实是调用时生成的。
如何解决
一、
这种方法实际上是不可行的,就是让编译器在编译的时候去各个地方查找实例化,例如我们在Func.i中看到一个模板,我们就去Test.i中找实例化,但是这样的话,如果是一个大项目的化,有几十几百个文件,对于编译器的实现就复杂了。所以实际在链接前,各文件间是不会交互的
二、
显示指定实例化,编译器看到后就知道要把这个模板参数T实例化成什么类型。但是这样做的问题也很大:就是我换个类型就又链接不上了,那我们就只能使用一种类型就实例化一次,这样就很麻烦。
三、
最后这种方法就很粗暴,STL中用的也是这种方法,就是不分离编译,声明和定义都放在一个.h文件里。这样的话,.h文件中就包含了模板的定义,就不需要链接的时候去查找了,在编译的阶段就能直接填地址了。而我们在项目中一般把这种文件命名为后缀为.hpp的文件,也就是说它即是.h文件,又是.cpp文件。
同样我们的类模板也不支持分离编译,最好的办法也是不分离,写在同一个文件中。
我在这里还要在解释我上文中的一句话:模板其实是调用时实例化生成的。我们在一个类模板或者函数模板中,假如写出了语法错误,假如我们没有实例化运行的话,编译器是检查不出错误的,这就是因为模板是调用时实例化的,没有实例化的时候,编译器不会去检查函数模板或类模板内部的语法错误。
总结
模板优点:
1.模板复用了代码,节约资源,更快的迭代开发,有了模板才有了C++的标准模板库STL的诞生
2.增强了代码的灵活性
模板缺点:
1.模板会导致代码膨胀问题,也会导致编译时间变长
2.假如我们使用模板时出现错误,编译器提示的错误信息会非常凌乱,而且准确度不高,我们不能盲目地相信模板的报错。可能只是一点小错误,最后却报出了一大堆错误,这时候我们要优先看第一个错误。
以上就是本章的全部内容,谢谢大家!
相关文章:
C++---模板进阶(非类型模板参数,模板的特化,模板分离编译)
我们都学习和使用过模板,而这篇文章我们来将一些更深入的知识。在此之前,我们在使用C编程时可以看到模板是随处可见的,它能支持泛型编程。模板包括函数模板和类模板,我们有的人可能会说是模板函数和模板类,但严格讲这样…...
锂电池寿命预测 | Matlab基于SSA-SVR麻雀优化支持向量回归的锂离子电池剩余寿命预测
目录 预测效果基本介绍程序设计参考资料 预测效果 基本介绍 【锂电池剩余寿命RUL预测案例】 锂电池寿命预测 | Matlab基于SSA-SVR麻雀优化支持向量回归的锂离子电池剩余寿命预测(完整源码和数据) 1、提取NASA数据集的电池容量,以历史容量作…...
整理好了!2024年最常见 20 道 Kafka面试题(十)
上一篇地址:整理好了!2024年最常见 20 道 Kafka面试题(九)-CSDN博客 十九、Kafka的消费者如何实现幂等性? 在Kafka中,幂等性指的是消费者处理消息时,即使多次接收到同一条消息,也能…...
Paper Survey——3DGS-SLAM
之前博客对多个3DGS SLAM的工作进行了复现及代码解读 学习笔记之——3DGS-SLAM系列代码解读_gs slam-CSDN博客文章浏览阅读1.9k次,点赞15次,收藏45次。最近对一系列基于3D Gaussian Splatting(3DGS)SLAM的工作的源码进行了测试与…...
搜索与图论:深度优先搜索
搜索与图论:深度优先搜索 题目描述参考代码 题目描述 参考代码 #include <iostream>using namespace std;const int N 10;int n; int path[N]; bool st[N];void dfs(int u) {// u n 搜索到最后一层if (u n){for (int i 0; i < n; i) printf("%d …...
AMD显卡和英伟达显卡哪个好?
显卡是计算机中负责处理图形和视频输出的硬件设备,主要分为两种类型:AMD的A卡和NVIDIA的N卡。那么AMD显卡和英伟达显卡哪个好?怎么选? 答:不能一概而论地说哪个好,因为它们各有优势,选择应基于…...
5.31.8 学习深度特征以实现判别定位
1. 介绍 尽管没有对物体的位置提供监督,但卷积神经网络 (CNN) 各层的卷积单元实际上可以充当物体检测器。尽管卷积层具有这种出色的物体定位能力,但当使用全连接层进行分类时,这种能力就会丧失。最近,一些流行的全卷积神经网络,如 Network in Network (NIN) [13] 和 Goog…...
uniapp小程序多线程 Worker 实战【2024】
需求 最近遇到个小程序异步解码的需求,采用了WebAssembly,涉及大量的计算。由于小程序的双线程模型只有一个线程处理数据,因此智能寻求其它的解决方案。查看小程序的文档,发现小程序还提供一个异步线程的Worker方案,可…...
C语言基础——数组(2)
ʕ • ᴥ • ʔ づ♡ど 🎉 欢迎点赞支持🎉 个人主页:励志不掉头发的内向程序员; 专栏主页:C语言基础; 文章目录 前言 一、二维数组的创建 1.1 二维数组的概念 1.2二维数组的创建 二、二维数组…...
封装PHP用于发送GET和POST请求的公共方法
封装了ThinkPHP用于发送GET和POST请求的公共方法。这个方法可以放在你的公共函数文件中,或者创建一个独立的类来管理这些请求。 <?php namespace app\common\utils;use think\facade\Log; use think\exception\HttpException;class HttpRequest {/*** 发送GET请…...
MongoDB~基础知识记录
为何要学Mongodb 工作以来,使用最多、了解最多的是MySQL。但技术的发展一定是依据痛点来的,就比如我遇到的痛点,一个业务、一个平台能力、存储的一个对象,随着产品和运营的需求,不断的进行变更,每一次的变…...
DSP28335模块配置模板系列——ADC配置模板
一、配置步骤 1.使能并配置高速时钟HSPCLK、ADC校验 EALLOW;SysCtrlRegs.PCLKCR0.bit.ADCENCLK 1; EDIS;EALLOW;SysCtrlRegs.HISPCP.all ADC_MODCLK; // HSPCLK SYSCLKOUT/(2*ADC_MODCLK)ADC_cal();EDIS; 这里ADC_MODCLK3,所以HSPCLK时钟为150/625Mhz 2.配…...
字符串转换为字节数组、16进制转换为base64、base64转换为字符串数组、base64转换为16进制(微信小程序)
1、字符串转换为字节数组 // 字符串转为字节数组 function stringToByteArray(str) {var array new Uint8Array(str.length);for (var i 0; i < str.length; i) {array[i] str.charCodeAt(i);}return array; } 2、16进制转换为base64 // 16进制转换为base64 function H…...
c++中, 直接写浮点数, 是float 还是 double?
如果直接一个浮点数, 那么他默认是float还是double呢? 测试用例 #include <iostream> using namespace std;int main() {auto x 0.2;float f 0.2;double d 0.2;cout << "x Size : " << sizeof(x) << " bytes" << endl…...
C++核心编程友元的应用
文章目录 1.友元1.什么是友元2.全局函数做友元2.类做友元3.成员函数做友元 1.友元 1.什么是友元 在C中,友元(friend)是一种允许一个类或函数访问另一个类的非公有(private 或 protected)成员的机制。这种机制打破了类…...
C#,JavaScript实现浮点数格式化自动保留合适的小数位数
目标 由于浮点数有漂移问题,转成字符串时 3.6 有可能得到 3.6000000000001,总之很长的一串,通常需要截取,但按照固定长度截取不一定能使用各种情况,如果能根据数值大小保留有效位数就好了。 C#实现 我们可以在基础库里…...
Android基础-工程目录结构说明
Android工程的项目目录结构是开发Android应用时的基础,它组织和存储了应用的所有源代码、资源和配置文件。了解并熟悉这个目录结构对于提高开发效率和代码管理至关重要。下面将详细阐述Android工程的项目目录结构。 1. 工程根目录 Android工程的根目录通常包含多个…...
浅谈提示词发展现状,Prompt 自动优化是未来。
#封面手绘于本科期间,当年在知乎上写的第一篇关于 AI 的文章就用的这个封面,聊表纪念。 这次我们来聊聊 Prompt. 本来想取一个类似“提示词不存在了…”,或是“再见,Prompt 课程…”的标题,但最近很多大佬的谬赞让我感…...
揭秘智能测径仪省钱之道!每年能为每条产线省上百万!
在当今竞争激烈的市场环境下,企业们都在不断寻求提高生产效率、降低成本的方法。而智能测径仪的出现,为圆形钢材、螺纹钢等生产企业实现这一目标提供了有力的支持。 智能测径仪被广泛应用于高线、铸管、圆钢、螺纹钢、钢筋等的轧制生产线中,进…...
echaerts图例自动滚动并隐藏翻页按钮
效果图 代码 legend: {itemHeight: 14,itemWidth: 14,height: "300", //决定显示多少个// 通过 CSS 完全隐藏翻页按钮pageButtonItemGap: 0,pageButtonPosition: end,pageIconColor: transparent, // 隐藏翻页按钮pageIconInactiveColor: transparent, // 隐藏翻页按…...
未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?
编辑:陈萍萍的公主一点人工一点智能 未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战,在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...
多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...
C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...
【论文阅读28】-CNN-BiLSTM-Attention-(2024)
本文把滑坡位移序列拆开、筛优质因子,再用 CNN-BiLSTM-Attention 来动态预测每个子序列,最后重构出总位移,预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵(S…...
破解路内监管盲区:免布线低位视频桩重塑停车管理新标准
城市路内停车管理常因行道树遮挡、高位设备盲区等问题,导致车牌识别率低、逃费率高,传统模式在复杂路段束手无策。免布线低位视频桩凭借超低视角部署与智能算法,正成为破局关键。该设备安装于车位侧方0.5-0.7米高度,直接规避树枝遮…...
什么是VR全景技术
VR全景技术,全称为虚拟现实全景技术,是通过计算机图像模拟生成三维空间中的虚拟世界,使用户能够在该虚拟世界中进行全方位、无死角的观察和交互的技术。VR全景技术模拟人在真实空间中的视觉体验,结合图文、3D、音视频等多媒体元素…...
go 里面的指针
指针 在 Go 中,指针(pointer)是一个变量的内存地址,就像 C 语言那样: a : 10 p : &a // p 是一个指向 a 的指针 fmt.Println(*p) // 输出 10,通过指针解引用• &a 表示获取变量 a 的地址 p 表示…...
基于单片机的宠物屋智能系统设计与实现(论文+源码)
本设计基于单片机的宠物屋智能系统核心是实现对宠物生活环境及状态的智能管理。系统以单片机为中枢,连接红外测温传感器,可实时精准捕捉宠物体温变化,以便及时发现健康异常;水位检测传感器时刻监测饮用水余量,防止宠物…...
