100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > C++模板:模板简述 函数模板详细说明【C++模板】(56)

C++模板:模板简述 函数模板详细说明【C++模板】(56)

时间:2022-10-23 06:40:34

相关推荐

C++模板:模板简述 函数模板详细说明【C++模板】(56)

模板模板语义函数模板重载泛化函数模板语法模板泛化特性小结编译原理函数模板应用算法抽象快速排序算法实现模板化函数模板默认参数函数模板的特化函数模板适用场景

模板

模板语义

模板是一门语言,支持泛型的语法基础。

泛型(Generic Programming),是指在多种数据类型上皆可操作的含意。泛型编程的代表作品 STL 是一种高效、泛型、可交互操作的软件组件。

泛型编程最初诞生于 C++中,目的是为了实现 C++的 STL(标准模板库)。其语言支持机制就是模板(Templates)。

模板的精神其实很简单:类型参数化(type parameterized),即类型也是一种参数,也是一种静多态。

换句话说,把一个原本特定于某个类型的算法或类当中的类型信息抽掉,抽象出来做成模板参数 T。

函数模板

重载泛化

重载函数,虽然实现了泛化的一些特性,但是不彻底,且有二义性(ambiguous)的存在。

代码演示:

#include <iostream>using namespace std;void myswap(int & a, int & b) {int t = a;a = b;b = t;}void myswap(double & a, double & b) {double t = a;a = b;b = t;}int main() {double a = 2; double b = 3;myswap(a, b);cout << a << " " << b << endl;int aa = 2; int bb = 3;myswap(aa, bb);cout << aa<<" " << bb << endl;return 0;}

运行结果为:

如下,示例中 long 类型可以隐式的转化为 int 或是 double,编译会出现二义性,导致编译失败。

代码演示:

#include <iostream>using namespace std;void myswap(int & a, int & b) //函数重载{int t = a;a = b;b = t;}void myswap(double & a, double & b) //函数重载{double t = a;a = b;b = t;}int main() {long a = 2; long b = 3;myswap(a, b);//ambiguousreturn 0;}

编译器报错:

可以增加函数重载类型来解决上面的问题,也是一种最好的解决方案。

例如增加:

void myswap(long & a, long & b) //函数重载{long t = a;a = b;b = t;}

也可以使用模板来解决函数重载的泛化不彻底的问题。

使用模板实现彻底泛化。

函数模板

语法

//在一个函数的参数表,返回类型和函数体中使用参数化的类型。template<typename 类型参数 T1,typename/class 类型参数 T2,...>返回类型 函数模板名(函数参数列表){函数模板定义体}

模板泛化

template,既可以与函数同行,也可以将函数另起一行来书写。T 即为范化的类型。

其过程,相当于经历了两次编译,先依据实参类型,实例化函数,然后再将实例化的函数,参与编译。

代码演示:

#include <iostream>using namespace std;template<typename T>void myswap(T & a, T & b){T t = a;a = b;b = t;}int main() {long a = 2; long b = 3;myswap(a, b);cout << a << " " << b << endl;return 0;}

运行结果:

我们说重载对于泛化不彻底,模板彻底实现泛化,我们接下来进行说明:

代码演示:

#include <iostream>using namespace std;template<typename T>void myswap(T & a, T & b){T t = a;a = b;b = t;}int main() {long a = 2; long b = 3;myswap(a, b);cout << a << " " << b << endl;string s1 = "hello";string s2 = "world";myswap(s1,s2);cout << s1 << " " << s2 << endl;double d1 = 3.1;double d2 = 4.1;myswap(d1, d2);cout << d1 << " " << d2 << endl;return 0;}

运行结果:

那么就会引发我们思考:

模板是不是悄悄的把我们学习过的所有类型全部重载了一遍呢?

我们通过代码进行验证:自实现类型使用模板。

代码演示:

#include <iostream>using namespace std;class MyType{public:MyType(int x,int y):_x(x), _y(y){}int _x;int _y;};template<typename T>void myswap(T & a, T & b){T t = a;a = b;b = t;}int main() {long a = 2; long b = 3;myswap(a, b);cout << a << " " << b << endl;string s1 = "hello";string s2 = "world";myswap(s1,s2);cout << s1 << " " << s2 << endl;double d1 = 3.1;double d2 = 4.1;myswap(d1, d2);cout << d1 << " " << d2 << endl;MyType mt1(1, 2);MyType mt2(3, 4);myswap(mt1, mt2);cout << mt1._x <<" "<<mt1._y << endl;return 0;}

运行结果:

我们可以看到,不只是系统提供的数据类型可以实现模板泛化,自定义的类也可以实现模板泛化。

所以模板解决了所有类型的操作,不管是基础类型还是自定义类型。

模板的原理:

其实模板的使用本来应该是如下的使用方式:

代码演示:

#include <iostream>using namespace std;class MyType{public:MyType(int x,int y):_x(x), _y(y){}int _x;int _y;};template<typename T>void myswap(T & a, T & b){T t = a;a = b;b = t;}int main() {long a = 2; long b = 3;myswap<long>(a, b);cout << a << " " << b << endl;string s1 = "hello";string s2 = "world";myswap<string>(s1,s2);cout << s1 << " " << s2 << endl;double d1 = 3.1;double d2 = 4.1;myswap<double>(d1, d2);cout << d1 << " " << d2 << endl;MyType mt1(1, 2);MyType mt2(3, 4);myswap<MyType>(mt1, mt2);cout << mt1._x <<" "<<mt1._y << endl;return 0;}

运行结果:

只不过现在类型推导足够强大了,所以

myswap<long>(a, b);myswap<string>(s1,s2);myswap<double>(d1, d2);myswap<MyType>(mt1, mt2);

就省略成了:

myswap(a, b);myswap(s1,s2);myswap(d1, d2);myswap(mt1, mt2);

上面代码的过程,相当于经历了两次编译,先依据实参类型,实例化函数。然后再将实例化的函数,参与编译。

函数模板myswap

–>实例化为

模板函数myswap < int >

–>模板函数的调用myswap < int >( 1 , 2 )

一般来说模板经历两个过程:

编译器编译检查模板的语法是否正确,语法通过时。当发生某一具体调用时,根据特定类型(类型由我们明确的告诉编译器或者进行自动类型推导)然后产生特定类型的模板函数版本用来调用。

什么叫做类型参数化呢?

在模板中可以设置默认类型:

代码演示:

template<typename T = int>void myswap(T & a, T & b){T t = a;a = b;b = t;}

template<typename T = int>

一般情况下不给默认值。

myswap<long>(a, b);

也不写类型。

因为类型推导已经强大到不需要写。

那么也就是把类型当作参数传递给函数模板,然后函数模板实例化为模板函数进行调用。

特性小结

先实例化,再调用。

严格匹配,不存在隐式转化。

尺有所短,寸有所长。

编译原理

编译器遇到模板方法定义时,会进行语法检查,但是并不编译模板。编译器无法编译模板定义,因为它不知道使用什么类型。比如 T a,b; 在不知晓 T 具体类型时,是无法分配内存,更无从谈编译 a = b;

T 获取类型的过程,称为模板的实例化,函数模板通过具体类型产生不同的函数;编译器会对函数模板进行两次编译:在声明的地方对模板代码本身进行编译(类型检查),在调用的地方对参数替换后的代码进行编译(代码生成)。

那么模板既然这么好,模板是不是就没有缺陷了呢?

编译器并不是把函数模板处理成能够处理任意类的函数。比如,自定义类型,如何处理呢?

代码演示:

#include <iostream>using namespace std;class MyType{public:MyType(int x,int y):_x(x), _y(y){}int _x;int _y;};template<typename T = int>void myswap(T & a, T & b){T t = a;a = b;b = t;}int main() {int i = 3;double j = 1.2;myswap(i, j);return 0;}

编译器报错:

那么我们可以不可以传递两个类型来实现模板呢?

代码演示:

#include <iostream>using namespace std;class MyType{public:MyType(int x,int y):_x(x), _y(y){}int _x;int _y;};template<typename T1 , typename T2>void myswap(T1 & a, T2 & b){T t = a;//类型应该使用 T1 呢?还是使用T2呢?a = b;b = t;}//实现比较的函数T Max(T1 & a, T2 & b)//返回类型应该使用 T1 呢?还是使用T2呢?{}int main() {int i = 3;double j = 1.2;myswap(i, j);return 0;}

结论是不可以,详细原因在代码中已经给出。

模板参数做返回类型需要编译器支持类型推导,需要确定返回类型来解决。

函数模板应用

算法抽象

数据类型的泛化,使的模板特别适合于作算法的抽象。

STL 中的 algorithm 就是典型的代表。

比如,算法 sort 就是支持类型泛化的代表排序算法。

例如:我把东西从A地搬到B地我只考虑怎么搬更省力而不考虑搬的是什么东西。

快速排序算法实现模板化

将快速排序算法实现模板化。此时,无论转入的是 int 的还是 float 的类型,均是可以处理。相同逻辑的函数没有必要写两遍。

代码演示:

#include <iostream>#include <typeinfo>using namespace std;template<typename T>void quickSort(T* array, int left, int right){if (left < right){int low = left; int high = right;T pivot = array[low];while (low < high){while (array[high] >= pivot && high > low)high--;array[low] = array[high];while (array[low] <= pivot && high > low)low++;array[high] = array[low];}array[low] = pivot;quickSort(array, left, low - 1);quickSort(array, low + 1, right);}}int main(){int array[10] = {1,3,5,7,2,4,6,8,0,9 };quickSort(array, 0, 9);for (auto i : array){cout << i << endl;}}

运行结果:

那么我们对于数据类型进行修改:

代码演示:

#include <iostream>#include <typeinfo>using namespace std;template<typename T>void quickSort(T* array, int left, int right){if (left < right){int low = left; int high = right;T pivot = array[low];while (low < high){while (array[high] >= pivot && high > low)high--;array[low] = array[high];while (array[low] <= pivot && high > low)low++;array[high] = array[low];}array[low] = pivot;quickSort(array, left, low - 1);quickSort(array, low + 1, right);}}int main(){char array[10] = {'j','g','f','r' ,'d' ,'e' ,'w' ,'h' ,'j' ,'j' };quickSort(array, 0, 9);for (auto i : array){cout << i << endl;}}

运行结果为:

也可以对于字符串进行排序:

对于main函数进行修改:

int main(){string array[10] = {"sdf" ,"gfsd" ,"rewt" ,"yter" ,"ndfg" ,"asdf" ,"hgjf" ,"sdfg" ,"jrrt" ,"hike" };quickSort(array, 0, 9);for (auto i : array){cout << i << endl;}}

运行结果:

写模板时,先用基本类型实现,然后套用模板,然后进行测试。

函数模板默认参数

重在理解,类型参数化,此时的默认参数,不再是一数值,而是类型。

函数模板,在调用时,先实例化为模板函数,然后再调用。当然也可以设置默认类型的默认值。由于系统强大的自动推导能力,有时默认也没有太大的意义。

template<typename T = int>void quickSort(T * array,int left, int right)

函数模板的特化

Template Specialization,函数模板的特化,即函数模板的特殊情况,个例行为。

就是在实例化模板时,对特定类型的实参进行特殊处理,即实例化一个特殊的实例版本。

template<typename T> int compare( T &a,T &b)template<> int compare <const char*>( const char*&a, const char*&b)

当以特化定义时的形参使用模板时,将调用特化版本,模板特化分为全特化和偏特化,函数模板的特化,只能全特化;

比如,我们在比较两个数的大小时:

代码演示:

#include <iostream>#include <string.h>using namespace std;template<typename T> int compare(T& a,T& b){if (a > b) return 1;else if (a < b)return -1;else return 0;}int main(){int a = 10;int b = 20;cout << compare(a, b) << endl;string sa = "abcd";string sb = "abc";cout << compare(sa, sb) << endl;return 0;}

运行结果:

上面的代码是模板的正常使用,没有问题。

我们对于代码进行修改:

#include <iostream>#include <string.h>using namespace std;template<typename T> int compare(T& a,T& b){if (a > b) return 1;else if (a < b)return -1;else return 0;}int main(){string sa = "abcd";string sb = "abc";cout << compare(sa, sb) << endl;const char * ca = "abcd";const char * cb = "abc";cout << compare(ca, cb) << endl;return 0;}

运行结果:

我们可以看到上面的测试运行出来了不同的结果。

原因分析:

实参为两个 char * 时,比较的是指针地址的大小,而不是指针指向内容的大小。

此时就需要为该函数模板定义一个特化版本,即特殊处理的版本:

代码演示:

#include <iostream>#include <string.h>using namespace std;template<typename T> int compare(T& a, T& b){if (a > b) return 1;else if (a < b)return -1;else return 0;}//模板特化版本template<> int compare < const char* >(const char*& a, const char*& b){return strcmp(a, b);}int main(){const char * ca = "abcd";const char * cb = "abc";cout << compare(ca, cb) << endl;return 0;}

运行结果:

函数模板适用场景

函数模板,只适用于函数的参数个数相同而类型不同,且函数体相同的情况。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。