2025年模板技术详解

模板技术详解目录 一 概念介绍 二 函数模板 2 1 概念 2 2 函数模板格式 2 3 函数模板原理 2 4 函数模板实例化 2 5 函数模板的匹配原则 三 类模板 3 1 类模板格式 3 2 类模板实例化 四 非类型模板参数 五 模板特化 5 1 概念 5 2 函数模板特化 5 3 类模板特化 六 模板编译分离

大家好,我是讯享网,很高兴认识大家。

目录

一、概念介绍

二、函数模板

2.1 概念

2.2 函数模板格式

2.3 函数模板原理

2.4 函数模板实例化

2.5 函数模板的匹配原则

三、类模板

3.1 类模板格式

3.2 类模板实例化

四、非类型模板参数

五、模板特化

5.1 概念

5.2 函数模板特化

5.3 类模板特化

六、模板编译分离

七、可变参数模板

八、模板总结


一、概念介绍

模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。

模板是创建泛型类或函数的蓝图或公式。库容器,比如迭代器和算法,它们都使用了模板技术。

泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础

二、函数模板

2.1 概念

2.2 函数模板格式

template<typename T1, typename T2,......,typename Tn> 返回值类型 函数名(参数列表){}
讯享网

注意:typename是用来定义模板参数的关键字,也可以使用class(切记:不能使用struct代替class)
 

2.3 函数模板原理

函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定的具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器。

2.4 函数模板实例化

用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化

隐式实例化: 让编译器根据实参推演模板参数的实际类型
显式实例化: 在函数名后的<>中指定模板参数的实际类型

讯享网#include<iostream> template<typename T> T Add(const T& left, const T& right) { return left + right; } int main() { int a1 = 10, a2 = 20; double d1 = 10.0, d2 = 20.0; Add(a1, a2); Add(d1, d2); //Add(a1, d1);//err /* 该语句不能通过编译 因为在编译期间,当编译器看到该实例化时,需要推演其实参类型 通过实参a1将T推演为int,通过实参d1将T推演为double类型, 但模板参数列表中只有一个T,编译器无法确定此处到底该将T确定为int 或者 double类型而报错 注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅 */ //处理方式: 1.用户自己来强制转化 2.使用显式实例化 3.设置多个模板参数 Add(a1, (int)d1); Add<int>(a1, d1);//显式实例化 return 0; }

2.5 函数模板的匹配原则

1.一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数(需显式指定)

#include <iostream> using namespace std; int Add(int left, int right) { return left + right; } template<class T1, class T2> T1 Add(T1 left, T2 right) { return left + right; } int main() { Add(1, 2);//与非模板函数匹配,编译器不需要实例化 Add<int>(1, 2);//显示指定调用编译器实例化的版本 return 0; }
讯享网#include <iostream> using namespace std; int Add(int left, int right) { return left + right; } template<class T1, class T2> T1 Add(T1 left, T2 right) { return left + right; } int main() { Add(1, 2);//与非函数模板类型完全匹配,不需要函数模板实例化 Add(1, 2.0);//模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数 return 0; }

3.模板函数不允许自动类型转换,但普通函数可以进行自动类型转换

三、类模板

3.1 类模板格式

template<class T1, class T2, ..., class Tn> class 类模板名 { //类内成员定义 };

注意:类模板中函数放在类外进行定义时,需要加模板参数列表

3.2 类模板实例化

类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。

//vector类名,vector<int>才是类型 Vector<int> s1; Vector<double> s2;

四、非类型模板参数

模板参数分类类型形参与非类型形参。
类型形参:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类型形参:就是用一个常量作为模板的一个参数,在模板中可将该参数当成常量来使用

template<class T, size_t N = 10> class array { public: //…… private: T _array[N]; size_t _size; };

注意:
1. 浮点数、类对象以及字符串不允许作为非类型模板参数
2. 非类型的模板参数必须在编译期就能确认结果

五、模板特化

5.1 概念

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理。例如下面的代码。

#include<iostream> using namespace std; class Date { public: Date() :_year(0), _month(0), _day(0) {} Date(int year, int month, int day) :_year(year), _month(month), _day(day) {} bool operator<(const Date& d)const { if ((_year < d._year) || (_year == d._year && _month < d._month) || (_year == d._year && _month == d._month && _day < d._day)) return true; return false; } private: int _year; int _month; int _day; }; 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, 8); cout << Less(d1, d2) << endl; // 可以比较,结果正确 Date* p1 = &d1; Date* p2 = &d2; cout << Less(p1, p2) << endl; // 可以比较,结果错误 //这里本意比较Date类型数据的大小,实际比较的却是指针地址的大小 return 0; }

此时,就需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化与类模板特化。

5.2 函数模板特化

1. 必须要先有一个基础的函数模板
2. 关键字template后面接一对空的尖括号<>
3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
4. 函数形参表: 必须要和模板函数的基础参数类型完全相同

#include<iostream> using std::cout; using std::endl; class Date { public: Date() :_year(0), _month(0), _day(0) {} Date(int year, int month, int day) :_year(year), _month(month), _day(day) {} bool operator<(const Date& d)const { if ((_year < d._year) || (_year == d._year && _month < d._month) || (_year == d._year && _month == d._month && _day < d._day)) return true; return false; } private: int _year; int _month; int _day; }; template<class T> bool Less(T left, T right) { return left < right; } //对Less函数模板进行特化 template<> bool Less<Date*>(Date* left, Date* right) { 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 = &d1; Date* p2 = &d2; cout << Less(p1, p2) << endl;//调用特化之后的版本,不进行模板生成 return 0; }

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

//该种实现简单明了,代码的可读性高,容易书写 //因为对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化 bool Less(Date* left, Date* right) { return *left < *right; }

5.3 类模板特化

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

偏特化: 任何针对模版参数进一步进行条件限制设计的特化版本。
偏特化有以下两种表现方式:
(1)部分特化:将模板参数类表中的一部分参数特化
(2)参数更进一步的限制:偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本

#include<iostream> using std::cout; using std::endl; template<class T1, class T2> class Data { public: Data() { cout << "Data<T1, T2>" << endl; } }; template<>//全特化 class Data<int, char> { public: Data() { cout << "Data<int, char>" << endl; } }; template <class T1>//偏特化 class Data<T1, int> { public: Data() { cout << "Data<T1, int>" << 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<T1, int> Data<int, char> d2;//Data<int, char> Data<double, double>d3;//Data<T1, T2> Data<int*, int*> d4;//Data<T1*, T2*> Data<double*, char*>d5;//Data<T1*, T2*> Data<double&, char&>d6;//Data<T1&, T2&> return 0; }

六、模板编译分离

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

解决方法:

七、可变参数模板

C++11的新特性可变参数模板能创建可以接受可变参数的函数模板和类模板

//Args是一个模板参数包,args是一个函数形参参数包 template <class ...Args> void ShowList(Args... args) {}

上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为"参数包",它里面包含了0到N(N>=0)个模版参数。

我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。

递归函数方式展开参数包

当递归调用ShowList函数模板时,若传入的参数包中参数的个数为0,那么就会匹配到这个无参的递归终止函数,这样就结束了递归

#include <iostream> #include <string> using namespace std; //template <class T> void ShowList()// 递归终止函数 { cout << endl; } template <class T, class ...Args> void ShowList(const T& value, Args... args) { cout << "ShowList("<<value<<", " << sizeof...(args) << "参数包)" << endl; ShowList(args...); } int main() { ShowList(1, 'A', std::string("sort")); return 0; }

在递归调用过程中,若传入的参数包中参数的个数为1,那么就会匹配到这个递归终止函数,这样也就结束了递归。但需要注意,这里的递归调用函数需要写成函数模板,因为并不知道最后一个参数是什么类型的。

但该方法有一个弊端,在调用ShowList函数时至少传入一个参数,否则就会报错。因为此时无论是调用递归终止函数还是展开函数,都需要至少传入一个参数

//递归终止函数 template<class T> void ShowListArg(const T& t) { cout << t << endl; } //展开函数 template<class T, class ...Args> void ShowListArg(T value, Args... args) { cout << value << " "; //打印传入的若干参数中的第一个参数 ShowList(args...); //将剩下参数继续向下传 } //供外部调用的函数 template<class ...Args> void ShowList(Args... args) { ShowListArg(args...); } 

逗号表达式展开参数包

虽然不能用不同类型的参数去初始化一个整型数组,但可以借助逗号表达式。

  • 逗号表达式会从左到右依次计算各个表达式,并且将最后一个表达式的值作为返回值进行返回
  • 将逗号表达式的最后一个表达式设置为一个整型值,确保逗号表达式返回的是一个整型值
  • 将处理参数包中参数的动作封装成一个函数,将该函数的调用作为逗号表达式的第一个表达式

这样一来,在执行逗号表达式时就会先调用处理函数处理对应的参数,然后再将逗号表达式中的最后一个整型值作为返回值来初始化整型数组

//处理参数包中的每个参数 template<class T> void PrintArg(const T& t) { cout << t << " "; } //展开函数 template<class ...Args> void ShowList(Args... args) { int arr[] = { (PrintArg(args), 0)... }; //列表初始化+逗号表达式 cout << endl; } 

可变参数的省略号需要加在逗号表达式外面,表示需要将逗号表达式展开。若将省略号加在args的后面,那么参数包将会被展开后全部传入PrintArg函数。代码中的{(PrintArg(args), 0)...}将会展开成{(PrintArg(arg1), 0), (PrintArg(arg2), 0), (PrintArg(arg3), 0), etc...}

实际上也可以不用逗号表达式,因为这里的问题就是初始化整型数组时必须用整数,那可以将处理函数的返回值设置为整型,然后用这个返回值去初始化整型数组也是可以的

#include <iostream> #include <string> using namespace std; template<class T> int PrintArg(const T& x)//处理每个参数的函数 { cout << x << " "; return 0; } template <class ...Args> void ShowList(Args... args) { int a[] = { PrintArg(args)... }; cout << endl; } int main() { ShowList(1); ShowList(1, 'A'); ShowList(1, 'A', std::string("sort")); return 0; } 

八、模板总结

优点:
1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
2. 增强了代码的灵活性
缺陷:
1. 模板会导致代码膨胀问题,也会导致编译时间变长
2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误

小讯
上一篇 2025-04-05 23:48
下一篇 2025-01-16 19:51

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/110168.html