浅谈c++的模板机制
泛型编程
泛型编程即以一种独立于任何特定类型的方式编写代码。可以实现算法和数据结构的分离。简单来说就是你写的代码不局限于类型(这句话往后面看会越来越清晰)。模板就是泛型编程的基础。
函数模板
为什么会出现函数模板
看一个东西之前先分析一下为什么会出现它。为什么会出现函数模板呢?来看看这个例子;我如果想要交换两个 int 型变量的值,该怎么做呢?很容易就可以写出如下代码:
void myswap1(int &a, int &b)
{
int c = 0;
c = a;
a = b;
b = c;
}
这样 当这个函数被调用的时候 两个形参就会交换了。 如果我又要交换 两个 double 型变量,怎么办呢?同理:
void myswap2(double &a, double &b)
{
double c = 0;
c = a;
a = b;
b = c;
}
这样同样可以交换两个double型数据,但是你不觉得这样很“麻烦”吗?我如果又想交换两个char型变量怎么做呢? 再重新写一个 myswap3(char &a, char &b)? 其实有一种简单方法,就是使用函数模板。
初识函数模板
首先将上面的例子用函数模板来表示
template <typename T>
void myswap(T &a, T &b)
{
T c;
c = a;
a = b;
b = c;
cout << "hello...." << endl;
}
如果要交换两个 int 型变量调用的时候就这么写:myswap <int> (x, y); // x,y 为 int型
如果要交换两个 double 型变量:myswap<double> (a, b); // a, b 为 double 型
同理如果想交换其他类型写法类似。
从上面这个例子也可以看出,函数模板适用于:函数功能相同但是参数不同的情况
函数模板语法
函数模板的语法很简单,在函数定义前面加一句:template <typename T >
template 关键字就是告诉编译器 我要使用模板了。
<typename T> : T 是一种数据类型,不要随便报错,在下面的第一个函数里面可以将类型不确定变量使用 T 来定义。从上面那个 myswap() 函数可以看出。
template <typename T>
void myswap(T &a, T &b)
{
T c;
c = a;
a = b;
b = c;
cout << "hello...." << endl;
}
第一种调用方式:显示类型调用。在函数名和括号之间加一对 尖括号,里面写上 形参的数据类型。
第二种调用方式:自动类型推导。 这种方式不推荐使用。方法是:myswap(x, y);
函数模板偶遇普通函数(重载)
首先明确一点:一个程序里面同时出现函数模板和普通函数,并且函数名相同 是正确的。
既然是正确的,那么什么时候调用的是函数模板,什么时候调用的是普通函数呢?
假设有以下实例:
int max(int a, int b)
{
cout << "普通函数调用" << endl;
return 0;
}
template <typename T>
int max(T a, T b)
{
cout << "template 函数调用" << endl;
return 0;
}
int x, y;
- 第一:从函数模板的调用规则可知:max<int, int> (x, y); 调用的肯定是函数模板。
- 第二:如果直接写max(x,y);调用的是普通函数。这说明普通函数的优先级比函数模板的优先级高
- 第三:如果可以产生一个更好的匹配时,调用函数模板。这句话的意思是: 对于上面的程序,如果你写一个:max(1.0, 2.0); 那么调用的是函数模板。因为函数模板能产生一个更好的匹配。什么是更好的匹配?对于上面的普通函数两个形参都是int型,因此最好调用时传递的实参应该是两个int型的,如果传两个double型的变量普通函数max最想要的。对于上面的函数模板呢?它要求的是两个形参的类型一致。另外:这个是不是和函数模板的自动类型推导有点类似呢?
- 第四:如果想直接调用函数模板可以使用空参数列表的方式。对于上面的例子就是:max<>(x, y); 调用的是函数模板。
第五:因为模板函数不能隐式转换,但是普通函数可以。
下面贴完整程序:
# include <iostream> using namespace std; int max(int a, int b) { cout << "普通函数调用" << endl; return 0; } template <typename T> int max(T a, T b) { cout << "template 函数调用" << endl; return 0; } template <typename T> int max(T a, T b, T c) { cout << "temppate 函数调用" << endl; return 0; } int main(void) { int a = 10; int b = 20; max(a, b); // 当普通函数和模板函数重载时,优先调用普通函数 max<>(a, b); // 如果想直接调用函数模板可以使用空参数列表的方式 max(1.0, 2.0); // 如果函数模板可以产生一个更好的匹配,那么选择函数模板 max('a', a); // 因为模板函数不能隐式转换,但是普通函数可以,因此此处调用的是普通函数 max(1.0, 2.0, 3.0); max <int>(a, b); return 0; }
在 win10 vs2017平台下,输出结果如下:
普通函数调用
template 函数调用
template 函数调用
普通函数调用
temppate 函数调用
template 函数调用
请按任意键继续. . .
另外如果一个函数的两个参数类型不一致的情况,如下将一个数组打印的程序:
template <typename T, typename T2>
int myPrint(T * arr, T2 size)
{
for (int i = 0; i < size; i++)
{
cout << arr[i] << " ";
}
cout << endl;
return 0;
}
myPrint<char, int>(arr0, size);
模板机制的实质
通过如下程序来寻找c++编译器是如何处理函数模板的。
# include <iostream>
using namespace std;
void myswap(int &a, int &b)
{
int c = 0;
c = a;
a = b;
b = c;
}
void myswap2(char &a, char &b)
{
int c = 0;
c = a;
a = b;
b = c;
}
int main(void)
{
{
int x = 10, y = 20;
myswap(x, y); // 第一次调用myswap函数
cout << "x = " << x << " y = " << y << endl;
}
{
char a = 'a', b = 'b';
myswap2(a, b); // 第二次调用myswap函数
cout << "a = " << a << " b = " << b << endl;
}
system("pause");
return 0;
}
在DOS下使用:
g++ -s 1.cpp -o 1.s
命令将上面的1.cpp反汇编,生成一个文件名为1.s的汇编程序。如下图
在汇编程序中找到main函数, 然后再main函数里面找到myswap关键字
可以看见一共有两次调用了myswap函数,和源程序一样。接下来寻找这两次调用的函数体
可见这两次调用的函数体并不是同一个。也就是调用的不同类型有几次就生成几个不同的函数体。
编译器并不是把函数模板处理成能够处理任意类的函数;编译器是根据函数模板调用时的具体类型产生不同的函数;
编译器会对函数模板进行两次编译:在声明的地方对模板代码本身进行编译(这次只进行简单纠错,替换),在调用的时候还会进行一次编译,这次会产生具体的函数体,并调用之。
下一篇文章:类模板
参考资料
传智c/c++视频
C++函数模板及实现原理