QQ:2279557541
Email:lihn1011@163.com
博客地址:http://blog.csdn.net/lihn1987
优酷主页:http://i.youku.com/shuangzhixiaodao
首先,我们为什么要使用模板?
比如我们几乎所有的C++招聘信息中都会要求的“熟练使用STL”,STL是什么意思呢? 其实“STL”就是“Standard Template Library”的缩写,翻译过来也就是“标准模板库”的意思,其中就运用到了大量的模板操作。
由于C++是一个强类型语言,而我们有许多代码除了类型以外其他的实现几乎完全一样。
1. 模板函数的编写
我们来演示一个例子。
求两个数中的最大值。
很多公司的笔试题中都有一道题,实现max(a,b)这个宏。那这里我们就来用模板实现一下。
template<typename T>
const T max(const T& t1, const T& t2)
{
return (t1 >t2)?t1:t2;
}
这个函数的目的是求两个值t1和t2中的最大值。而类型是由T来确定的。只要在函数前面声明一个template<typename 类型1,typename 类型2...typename 类型n>。那么其后面的函数就会把声明的参数当做一个类型来处理。这里说明一下,typename由于历史原因其实是可以用class来代替的。
2. 模板函数的使用
#include "stdafx.h"
#include <iostream>
#include <string>
template<typename T>
const T max(const T& t1, const T& t2)
{
return (t1 >t2)?t1:t2;
}
int main()
{
{
int t1 = 10,t2 = 20;
std::cout << max<int>(t1,t2) << std::endl;
}
{
double t1 = 10.1,t2 = 20.2;
std::cout << max<double>(t1,t2) << std::endl;
}
{
char t1 ='a', t2 ='b';
std::cout << max<char>(t1,t2) << std::endl;
}
{
std::string t1 ="aaa", t2 = "bbb";
std::cout << max<std::string>(t1,t2) << std::endl;
}
system("pause");
}
输出为
20
20.2
b
Bbb
看这一段代码,模板函数的标准使用方式是
函数名称<模板参数1,模板参数2...>(函数参数...)
3. 模板的实现原理
看完上一段代码,我们来解释一下模板的实现原理。
比如我们调用了max<int>(t1,t2)其实是将int类型带入到了模板中,然后生成了一个函数,样子为
const int max(const int& t1, const int& t2)
{
return (t1 >t2)?t1:t2;
}
根据你调用类型不同生成不同的函数。而这几个函数其实是不相同的,我们看下反汇编的结果。
代码为
#include "stdafx.h"
#include <iostream>
#include <string>
template<typename T>
const T max(const T& t1, const T& t2)
{
return (t1 >t2)?t1:t2;
}
int main()
{
int i1 = 1,i2 = 2;
double d1 = 1.0,d2 = 2.0;
std::string s1 ="1", s2 ="2";
max<int>(i1,i2);
max<double>(d1,d2);
max<std::string>(s1,s2);
system("pause");
}
反汇编的结果为
可以看到调用的max的函数地址,一个是0181393h一个是01814bah,是不同的函数地址。
4. 模板函数的推导
template<typename T>
const T max(const T& t1, const T& t2)
{
return (t1 >t2)?t1:t2;
}
模板函数在调用时时需要确定模板参数的,模板参数有两种方式指定
一种为显示的指定,如
max<int>(1, 2);
一种为隐形的指定,如
max(1,2);
这里的推导需要说明一下,由于模板函数的参数是T类型的,而模板参数也是T类型,则隐形的将这里传入的参数是int类型的,这里就推导T的类型为int型了。
因此在使用模板函数的时候,只要参数能够自动推导正确,我们是不需要进行显示指定模板参数类型的。
5. 模板函数的重载和特化
#include <iostream>
#include <string>
const int max(int t1,int t2)
{
std::cout << "这是非模板函数"<<std::endl;
return (t1 >t2) ? t1 :t2;
}
template<typename T>
const T max(const T& t1,const T& t2)
{
std::cout << "这是泛模板函数" << std::endl;
return (t1 >t2) ? t1 :t2;
}
int main()
{
int i1 = 1,i2 = 2;
max(i1,i2);
max<>(i1,i2);
system("pause");
}
输出为
这是非模板函数
这是泛模板函数
这里对max的实现有两种。一种是普通的max函数,参数为int类型。另一种是模板类型。其实是对max进行了重载。而在重载的时候有个顺序,就是优先判断普通函数是否能够处理这个参数的函数,如果能的话,则优先调用。
我们再看一下函数的另一种“重载”方式,就是模板的特化。
#include "stdafx.h"
#include <iostream>
template<typename T>
const T max(const T& t1,const T& t2)
{
std::cout << "这是泛模板函数" << std::endl;
return (t1 >t2) ? t1 :t2;
}
template<>
const int max(const int& t1,const int& t2)
{
std::cout << "这是特化模板函数" << std::endl;
return (t1 >t2) ? t1 :t2;
}
int main()
{
int i1 = 1,i2 = 2;
double d1 = 1,d2 = 2;
max(i1,i2);
max(d1,d2);
system("pause");
}
输出为
这是特化模板函数
这是泛模板函数
首先说下特化的语法,其实就是在函数前面加一个template<>,然后将本来不明确的类型,进行明确的定义,也就是对原来模板函数对某种特定的类型进行特殊的处理。
其实这里的特化其实和刚才的重载没有什么太大的区别,貌似都是和模板函数的重载一样是对函数某个类型的参数特殊处理。
然后,调用规则同重载一样,优先判断模板的特化函数有没有能够处理这种这种,如果有,则优先调用特化,否则则都要用原来的模板函数。
6. 特化真的这么简单?
#include "stdafx.h"
#include <iostream>
template<typename T>
void func(T*)
{
std::cout << "func T*";
}
template<typename T>
void func(T)
{
std::cout << "func T";
}
int main()
{
char* tmp = NULL;
func(tmp);
system("pause");
}
这会调用哪个函数呢??????
如果你尝试着运行一下。。。
没错!就是void func(T*)
这里可能有些迷惑~我们发现我们传入一个char*的参数,两个模板都能够匹配。
分别为func<char>(char*)和func<char*>(char*)两种,而为何选择了第1种呢?有些书上有种说法是:选择更加特化的,或者叫更加特殊的。我看了以后一头雾水。
我是这样理解的,推导中模板匹配的越简单的也就是上面推倒的尖括号中的部分也简单的,越线被匹配到。
7. 模板函数的重载和特化同时存在
void func(T)
{
std::cout << "func T" << std::endl;
}
template<>
void func(int)
{
std::cout << "func T*"<<std::endl;
}
void func(int)
{
std::cout << "func" << std::endl;
}
int main()
{
int tmp = 0;
func(tmp);
system("pause");
}
这种情况比较特殊,名为func的普通函数,模板函数,模板特化函数都存在的情况,其是会报错和,还是有优先顺序呢?
实际情况是,这种方式是允许的,其匹配优先级为 普通函数>特化函数>原始模板函数