模板的进阶

devtools/2024/9/24 13:22:05/

目录

非类型模板参数

C++11的静态数组容器-array 

按需实例化

模板的特化

函数模板特化

类模板特化

全特化与偏特化

模板的分离编译

总结


非类型模板参数

基本概念:模板参数类型分为类类型模板参数和非类类型模板参数

  • 类类型模板参数:跟在class 或 typename之后的形参
  • 非类类类型模板参数:用一个常量作为类模板的参数

功能:编译时合理分配大小

#include <iostream>
using namespace std;namespace bit
{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 _size;}bool empty()const{return 0 == _size;}private:T _array[N];size_t _size;};
}int main()
{bit::array<int> a1;bit::array<int, 10> a2;bit::array<int, 100> a3;return 0;
}

注意事项: 

1、浮点数、类类型的对象及字符串不允许作为非类类型模板参数

2、非类类型模板参数是在编译时传参,函数参数是在运行时传参

3、函数参数(T 对象1,T 对象2)| 模板参数<class 类型1,class 类型2>

C++11的静态数组容器-array 

array文档:<array> - C++ Reference (cplusplus.com)

#include <iostream>
#include <assert.h>
#include <array>
using namespace std;int main()
{std::array<int, 10> a1;int a2[10];//越界读,检查不出来a2[10];//越界写,抽查,局限多,很多位置查不出来(x86环境下运行a2[15]不报错,a[10] = 1报错)a2[15] = 1;//任意读写越界都能检查出来a1[10];//报错return 0;
}

优点: 可以避免数组越界问题(但实际上这些内容vector就可以做到,array没啥用)

std::vector<int> v1(10,0); 
v1[10]//v1[10]也可以检测出

缺点:会出现栈溢出问题

std::array<int,1000000> a3;//报错
std::vector<int> v2(1000000,0);//正确

按需实例化

include <iostream>
#include <array>
#include <vector>
#include <assert.h>
using namespace std;namespace bit
{template<class T, size_t N = 10>class array{public:T& operator[](size_t index){assert(index < N);size(1);//语法错误,但是不报错return _array[index];}const T& operator[](size_t index)const{assert(index < N);return _array[index];}size_t size()const{return _size;}bool empty()const{return 0 == _size;}private:T _array[N];size_t _size;};
}int main()
{bit::array<int> a1;cout<<a1.empty()<<endl;//不报错a1[1];//增加一个调用a1[1]时报错return 0;
}

运行上述代码后不会报错,即使size(1)是一个语法错误(size函数不需要传参)是因为

  1. 在编译器遇到模板时,预处理阶段后不会直接编译,而是根据模板的“蓝图” + 传入模板的参数类型等内容,将模板进行实例化后才会进行编译阶段(调试时是已经经历了四个阶段的)
  2. 在实例化类模板时会进行按需实例化,即调用哪个成员函数就实例化哪个(调用empty就仅实例化该函数)

没有报错的本质就是没有调用operator[],所以operator[]中的错误不会被检查出来,只有调用才会细致检查语法错误

  • 当然编译器还是会大体框架进行检查,比如少了}多个}之类的错误

模板的特化

基本概念:在原模板类的基础上,针对特殊类型所进行特殊化处理

        通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到

一些错误的结果,需要特殊处理,比如:实现了一个专门用来进行小于比较的函数模板:

#include <iostream>
using namespace std;class Date
{
public:friend ostream& operator<<(ostream& _cout, const Date& d);Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){}bool operator<(const Date& d)const{return (_year < d._year) ||(_year == d._year && _month < d._month) ||(_year == d._year && _month == d._month && _day < d._day);}bool operator>(const Date& d)const{return (_year > d._year) ||(_year == d._year && _month > d._month) ||(_year == d._year && _month == d._month && _day > d._day);}
private:int _year;int _month;int _day;
};ostream& operator<<(ostream& _cout, const Date& d)
{_cout << d._year << "-" << d._month << "-" << d._day;return _cout;
}// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{return left < right;
}int main()
{cout << Less(1, 2) << endl; // 可以比较,结果正确Date d1(2022, 7, 7);Date d2(2022, 7, 6);cout << Less(d1, d2) << endl; // 可以比较,结果正确(会调用日期类对象的<重载)Date* p1 = &d1;Date* p2 = &d2;//(传入日期类对象的指针进行比较)cout << Less(p1, p2) << endl; // 可以比较,结果错误return 0;
}

        p1指向的d1显然大于p2指向的d2对象,但是Less内部并没有比较p1和p2指向的对象内容,

而比较的是p1和p2指针的地址,因而无法达到预期而错误

函数模板特化

注意事项:

  • 1、必须要现有一个基础的函数模板
  • 2、template后接一个空的尖括号<>
  • 3、函数名后跟一对尖括号,尖括号中指定需要特化的类型
  • 4、函数形参顺序必须要和模板函数的基础参数类型完全相同,若不同则编译器可能报错
#include <iostream>
using namespace std;class Date
{
public:friend ostream& operator<<(ostream& _cout, const Date& d);Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){}bool operator<(const Date& d)const{return (_year < d._year) ||(_year == d._year && _month < d._month) ||(_year == d._year && _month == d._month && _day < d._day);}bool operator>(const Date& d)const{return (_year > d._year) ||(_year == d._year && _month > d._month) ||(_year == d._year && _month == d._month && _day > d._day);}
private:int _year;int _month;int _day;
};ostream& operator<<(ostream& _cout, const Date& d)
{_cout << d._year << "-" << d._month << "-" << d._day;return _cout;
}//函数模板
template<class T>
bool Less(T left, T right)
{cout << "bool Less(T left, T right)" << endl;return left < right;
}特化(但是该特化还是会出错)
//template<>
//bool Less(Date* left, Date* right)
//{
//	cout << "bool Less(T* left, T* right)" << endl;
//	return *left < *right;
//}//不会出错但是不符合特化定义
template<class T>
bool Less(T* left, T* right)
{cout << "bool Less(T* left, T* right)" << endl;return *left < *right;
}int main()
{cout << Less(1, 2) << endl; // 可以比较,结果正确Date d1(2022, 7, 7);Date d2(2022, 7, 8);cout << Less(d1, d2) << endl; // 可以比较,结果正确Date* p1 = new Date(2022, 7, 7);Date* p2 = new Date(2022, 7, 8);cout << Less(p1, p2) << endl; // 可以比较,结果错误int* p3 = new int(3);int* p4 = new int(4);cout << Less(p3, p4) << endl; //可以比较,结果错误return 0;
}

~~依然是哪个类型更匹配就用哪个 ~~

结论:函数模板不建议特化do且函数模板特化的使用场景少

类模板特化

全特化与偏特化

全特化:将模板参数列表中的所有参数都确定化

偏/半特化:将模板参数列表中的部分参数确定化

  • 偏特化又分为:“部分特化” 和“参数更进一步的限制”
#include <iostream>
using namespace std;//Data类模板
template<class T1,class T2>
class Data
{
public:Data(){cout << "Data<T1,T2>" << endl;}
private:T1 _d1;T2 _d2;
};//全特化
template<>
class Data<int,char>
{
public:Data(){cout << "Data<int,char>" << endl;}
};//偏/半特化(部分特化)
template<class T1>
class Data<T1, char>
{
public:Data(){cout << "Data <T1,char>" << endl;}
};//偏/半特化(对参数进一步限制)
template<class T1,class T2>
class Data<T1*, T2*>
{
public:Data(){cout << "Data <T1*,T2*>" << endl;}
};//偏/半特化(对参数进一步限制)
template<class T1, class T2>
class Data<T1&, T2*>
{
public:Data(){cout << "Data <T1&,T2*>" << endl;}
};int main()
{Data<int, int> d1;Data<int, char> d2;Data<char, char> d3;Data<char*, char*> d4;Data<int*, char*> d5;Data<int*, string*> d6;Data<int&, string*> d7;return 0;
}

匹配机制:有现成的就用现成的,没有现成的就用那个最合适的

注意事项:如果传入的是Data*,特化时的T*就会变成Date*而不是Data**,T*是一个整体

模板的分离编译

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

假如有以下场景,模板声明和定义分离,在头文件中进行声明,在源文件中进行定义:

//a.h文件
tempate<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;
}

 22、2:33处

注意事项:模板的声明和定义支持分离,但不支持分离在两个文件(STL库中的模板的定义和分离都是在.h文件中的)

总结

优点:

  1. 复用了代码,节省资源,更快的迭代开发,C++的标准模板库也因此而生
  2. 增强代码灵活性

缺点:

  1. 模板会导致代码膨胀问题,也会导致编译时间变长
  2. 出现模板编译错误时,错误信息十分混乱,不易定位

~over~


http://www.ppmy.cn/devtools/5515.html

相关文章

[渗透测试学习] Pov-HackTheBox

Pov-HackTheBox 信息搜集 nmap -sV -sC -v --min-rate 1000 10.10.11.251扫描结果 PORT STATE SERVICE VERSION 80/tcp open tcpwrapped |_http-title: pov.htb | http-methods: |_ Supported Methods: GET HEAD我们将域名pov.htb添加到/etc/hosts方便访问 打开后发…

R version 4.1.0 安装ggplot2,options(pkgType=“binary“) install.packages(“ggplot2“)

R version 4.1.0 安装ggplot2 R version 4.1.0 安装ggplot21. 报错信息2. 成功安装3. 参考资料 R version 4.1.0 安装ggplot2 # 直接安装报错 install.packages("ggplot2")# 安装成功 options(pkgType"binary") install.packages("ggplot2")1. …

Linux-用户命令使用

一、ID命令&#xff08;用于显示用户的详细信息&#xff09; 语法格式&#xff1a;id 用户名 如&#xff1a;id root id是语法&#xff0c;root是用户名 [rootbigdatas ~]# id root uid0(root) gid0(root) 组0(root) 二、useradd命令&#xff08;用于创建新的…

【QT教程】QT6QFuture与并发

QT6QFuture与并发 使用AI技术辅助生成 QT界面美化视频课程 QT性能优化视频课程 QT原理与源码分析视频课程 QT QML C扩展开发视频课程 免费QT视频课程 您可以看免费1000个QT技术视频 免费QT视频课程 QT统计图和QT数据可视化视频免费看 免费QT视频课程 QT性能优化视频免费看 免…

生活中的洪特规则

不知道你还记不记得高中物理所学的一个奇特的物理规则&#xff1a;洪特规则。 洪特规则是德国人弗里德里希洪特&#xff08;F.Hund&#xff09;根据大量光谱实验数据总结出的一个规律&#xff0c;它指出电子分布到能量简并的原子轨道时&#xff0c;优先以自旋相同的方式分别占…

vue 数据类型转换

在Vue中进行数据类型转换有多种方式&#xff0c;具体取决于你希望实现的转换类型和场景。 以下是一些常见的数据类型转换示例&#xff1a; 字符串转数字&#xff1a; let str "42"; let num Number(str); console.log(num); // 42数字转字符串&#xff1a; let…

Jmeter BeanShell调用Java方法加密

1、添加BeanShell前置处理器 由于请求接口时&#xff0c;会传加密参数。加密过程会在请求之前完成&#xff0c;所以需要使用前置处理器中beanshell preprocessor 2、编写BeanShell脚本 ①定义一个beashell变量&#xff1a;phoneNum&#xff0c;在Beanshell中可以直接调用Jmete…

mac 使用nvm配置nodejs

您可以按照以下步骤在Mac上安装指定版本的Node.js&#xff1a; 首先&#xff0c;请确保您已经安装了Homebrew。如果没有&#xff0c;请在终端中运行以下命令来安装Homebrew&#xff1a; /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install…