100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > 泛型编程之函数模板和类模板

泛型编程之函数模板和类模板

时间:2020-06-11 12:19:38

相关推荐

泛型编程之函数模板和类模板

1. 函数模板

C++一种编程思想称为泛型编程,主要利用的技术就是模板

C++提供两种模板机制:函数模板和类模板。这里介绍函数模板,类模板在该专题下的另外篇文章中。

函数模板作用:建立一个通用函数,其函数返回值类型和形参类型可以不具体指定,用一个虚拟的类来表达。

1.1 函数模板的定义

函数模板不是一个实在的函数,编译器不能为其生成可执行代码。定义函数模板后只是一个对函数功能框架的描述,当它具体执行时,将根据传递的实际参数决定其功能。

template<class T>T max(T a, T b){return a + b;}

但是,T可能只是该类型的一部分,例如,如果我们在max函数里面形参声明为const引用

template<typename T>T max (T const& a, T const& b){return b < a ? a : b;}// T是int

在推导的过程中的类型转换需要注意:

当通过引用声明形参时,即使是平凡的转换也不适用于类型推导。使用相同模板参数T声明的两个形参必须完全匹配。

当通过值类型声明形参时,仅支持平凡的退化(decay)转换: const和volatile限定符将被忽略;引用转换为引用的类型;原始数据和函数转换为相应的指针类型。使用相同模板参数T声明的两个形参,其退化类型必须匹配。

template<typename T>T max (T a, T b);…int const c = 42;max(i, c); // OK: T is deduced as intmax(c, c); // OK: T is deduced as intint& ir = i;max(i, ir); // OK: T is deduced as intint arr[4];max(&i, arr); // OK: T is deduced as int*max(4, 7.2); // ERROR: T can be deduced as int or doublestd::string s;max("hello", s); //ERROR: T can be deduced as char const[6] or std::string

可以通过以下方法解决:

1.强制转换参数,使他们都匹配

2.明确指定(或限定)T的类型,以阻止编译器尝试进行类型推导。

3.指明参数可能有多种不同的类型

max(static_cast<double>(4), 7.2); // OKmax<double>(4, 7.2); // OK

1.2 函数模板的实现原理

编译器并不是把函数模板处理成能够处理任意类的函数

编译器从函数模板通过具体类型来产生不同的函数

编译器会对函数模板进行两次编译

(1)在声明的位置对模板代码进行编译

(2)在调用的位置对参数替换后的代码进行编译

#include<iostream>using namespace std;intmyswap(int a, int b){cout << "调用模板函数!!!" << endl;return 0;}double myswap(double a, double b){cout << "调用模板函数!!!" << endl;return 0;}char myswap(char a, char b){cout << "调用模板函数!!!" << endl;return 0;}int main(){myswap(1,1); myswap('a','b');myswap(2.0,3.0); }

#include<iostream>using namespace std;template<typename T>T myswap(T a, T b){std::operator<<(std::cout, "\350\260\203\347\224\250\346\250\241\346\235\277\345\207\275\346\225\260!!!").operator<<(std::endl);return 0;}/* First instantiated from: insights.cpp:13 */#ifdef INSIGHTS_USE_TEMPLATEtemplate<>int myswap<int>(int a, int b){std::operator<<(std::cout, "\350\260\203\347\224\250\346\250\241\346\235\277\345\207\275\346\225\260!!!").operator<<(std::endl);return 0;}#endif/* First instantiated from: insights.cpp:14 */#ifdef INSIGHTS_USE_TEMPLATEtemplate<>char myswap<char>(char a, char b){std::operator<<(std::cout, "\350\260\203\347\224\250\346\250\241\346\235\277\345\207\275\346\225\260!!!").operator<<(std::endl);return 0;}#endif/* First instantiated from: insights.cpp:15 */#ifdef INSIGHTS_USE_TEMPLATEtemplate<>double myswap<double>(double a, double b){std::operator<<(std::cout, "\350\260\203\347\224\250\346\250\241\346\235\277\345\207\275\346\225\260!!!").operator<<(std::endl);return 0;}#endifint main(){myswap(1, 1);myswap('a', 'b');myswap(2.0, 3.0);return 0;}

1.3 函数模板的重载

所谓的函数模板的重载是指,普通函数的版本函数模板的版本函数模板特例化的版本可以共存,例如:

//普通函数版本bool Compare(char* a, char* b){cout << "普通函数版本" << endl;return strcmp(a, b) > 0;}//函数模板版本template<typename T>bool Compare(T a, T b){cout << "函数模板版本" << endl;return a > b;}//模板特例化版本template<>bool Compare(char* a, char* b){cout << "模板特例化版本" << endl;return strcmp(a, b) > 0;}

调用的顺序:普通函数版本>模板特例化的版本>模板版本,并且调用时要满足精确匹配的规则。例如:

#include<iostream>using namespace std;//普通函数版本bool Compare(char* a, char* b){cout << "普通函数版本" << endl;return strcmp(a, b) > 0;}//函数模板版本template<typename T>bool Compare(T a, T b){cout << "函数模板版本" << endl;return a > b;}//模板特例化版本template<>bool Compare(char* a, char* b){cout << "模板特例化版本" << endl;return strcmp(a, b) > 0;}int main(){Compare("hello", "world");return 0;}

当在主函数中调用Compare函数比较两个常量字符串"hello"和"world"的大小时,首先会按照调用优先级,先调用普通函数版本,但此时普通函数版本的两个形参的类型为char*,但调用点的形参的类型为const char*,不满足精确匹配的规则,因此会按照调用优先级,接着调用模板特例化版本,但此时模板特例化版本的两个形参的类型为char*,但调用点的形参的类型为const char*,不满足精确匹配的规则,因此会按照调用优先级,来调用函数模板版本,此时系统会用const char*来替换T,来进行实例化,最终进行处理。因此这里我们调用的是函数模板。

1.4 函数模板的参数默认值

template <typename T1, typename T2 = int>class DefClass1 {};template <typename T1 = int, typename T2>class DefClass2 {}; // ERROR: 无法通过编译:因为模板参数的默认值没有遵循“由右往左”的规则template <typename T, int i = 0>class DefClass3 {};template <int i = 0, typename T>class DefClass4 {}; // ERROR: 无法通过编译:因为模板参数的默认值没有遵循“由右往左”的规则template <typename T1 = int, typename T2>void DefFunc1(T1 a, T2 b) {}; // OK 函数模板不用遵循“由右往左”的规则template <int i = 0, typename T>void DefFunc2(T a) {}; // OK 函数模板不用遵循“由右往左”的规则

可以看到,不按照从右往左定义默认类模板参数的模板类DefClass2和DefClass4都无法通过编译。

而对于函数模板来说,默认模板参数的位置则比较随意。

DefFunc1和DefFunc2都为第一个模板参数定义了默认参数,而第二个模板参数的默认值并没有定义,C++11编译器却认为没有问题。

函数模板的参数推导规则也并不复杂。简单地讲:如果能够从函数实参中推导出类型的话,那么默认模板参数就不会被使用,反之,默认模板参数则可能会被使用。

template <class T, class U = double>void f(T t = 0, U u = 0) {};void g(){f(1, 'c'); // f<int, char>(1, 'c')f(1);// f<int, double>(1, 0), 使用了默认模板参数doublef(); // 错误: T无法被推导出来f<int>(); // f<int, double>(0, 0), 使用了默认模板参数doublef<int, char>(); // f<int, char>(0, 0)}

定义了一个函数模板f,f同时使用了默认模板参数和默认函数参数。

可以看到,由于函数的模板参数可以由函数的实参推导而出:

在f(1)这个函数调用中,实例化出了模板函数的调用应该为f<int, double>(1, 0),其中,第二个类型参数U使用了默认的模板类型参数double,而函数实参则为默认值0。

类似地,f<int>()实例化出的模板函数第二参数类型为double,值为0。

而表达式f()由于第一类型参数T的无法推导,从而导致了编译的失败。

而通过这个例子也可以看到,默认模板参数通常是需要跟默认函数参数一起使用的。

还有一点应该注意:模板函数的默认形参值不是模板参数推导的依据。函数模板参数的选择,终究是由函数的实参推导而来的。

1.5 函数模板的泛化和特化

泛化是指模版中的类型都未定,可以支持所有的类型传入。

全特化是指在泛化之后,对类型输入的时候,指定特定类型,进行定义。

偏特化是指将指定类型或者指定数量的输入固定,进行定义。

template<class T> // 完全泛化void fun(T a){}template<class T> // 部分特化void fun(T* a){}template<>void fun<char*>(char*) // 完全特化{}template<class T, class T1> // 完全泛化void fun(T a, T1 b){}template<class T, class T1 = int> // 部分泛化void fun(T a, T1 b){}template<>void fun<char*, int>(char*, int) // 完全特化{}

重点总结

类模板和函数模板都可以被全特化;

类模板能偏特化,不能被重载;

函数模板全特化,不能被偏特化。

模板类调用优先级

对主版本模板类、全特化类、偏特化类的调用优先级从高到低进行排序是:

全特化类>偏特化类>主版本模板类

这样的优先级顺序对性能也是最好的

面试tag:

(请问函数模板和类模板有什么区别?)

除了上面所说的以外:

函数模板的实例化是由编译程序在处理函数调用时自动完成的,而类模板的实例化必须由程序员在程序中显式地指定。

即函数模板允许隐式调用和显式调用而类模板只能显式调用

与函数模板不同的是,类模板在实例化时必须显式地指明数据类型,编译器不能根据给定的数据推演出数据类型。

这期间有涉及到函数模板与模板函数,类模板与模板类的概念(类似于类与类对象的区别)。请看下面例子

注意:模板类的函数声明和实现必须都在头文件中完成,不能像普通类那样声明在.h文件中实现在.cpp文件中。

即,模板不支持分离编译 。如下是之前总结的模板为什么不支持分离编译

/wyn126/article/details/76733943

含义是:在定义模板的头文件.h时,模板的成员函数实现也必须写在头文件.h中,而不能像普通的类(class)那样,class的声明(declaration)写在.h文件中,class的定义(definition)写在.cpp文件中

类模板可以全特化和偏特化,而函数模板不能够偏特化,只能全特化。类模板如果需要一个接收指针的偏特化版本,那么就可以指针偏特化实现;而函数模板不存在偏特化。例如,在STL中需要设计的Iterator Traits,用于提取迭代器类的五种关联类型,而同时要使得算法对指针(理解为退化的迭代器)也能使用,而指针不是一个类,如何获取内部的5个类型呢?这里就用到了类模板类的指针偏特化来区分开T的指针 和T 的迭代器。

? 函数模板为什么不能偏特化

?模板函数/函数模板 模板类/类模板 区别

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