泛型编程即以一种独立于任何特定类型的方式编写代码。可以实现算法和数据结构的分离。简单来说就是你写的代码不局限于类型(这句话往后面看会越来越清晰)。模板就是泛型编程的基础。
看一个东西之前先分析一下为什么会出现它。为什么会出现函数模板呢?来看看这个例子;我如果想要交换两个 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
void myswap(T &a, T &b)
{
T c;
c = a;
a = b;
b = c;
cout << "hello...." << endl;
}
如果要交换两个 int 型变量调用的时候就这么写:myswap
如果要交换两个 double 型变量:myswap
同理如果想交换其他类型写法类似。
从上面这个例子也可以看出,函数模板适用于:函数功能相同但是参数不同的情况
函数模板的语法很简单,在函数定义前面加一句:template
template 关键字就是告诉编译器 我要使用模板了。
template
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
int max(T a, T b)
{
cout << "template 函数调用" << endl;
return 0;
}
int x, y;
第五:因为模板函数不能隐式转换,但是普通函数可以。
下面贴完整程序:
# include
using namespace std;
int max(int a, int b)
{
cout << "普通函数调用" << endl;
return 0;
}
template
int max(T a, T b)
{
cout << "template 函数调用" << endl;
return 0;
}
template
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 (a, b);
return 0;
}
在 win10 vs2017平台下,输出结果如下:
普通函数调用
template 函数调用
template 函数调用
普通函数调用
temppate 函数调用
template 函数调用
请按任意键继续. . .
另外如果一个函数的两个参数类型不一致的情况,如下将一个数组打印的程序:
template
int myPrint(T * arr, T2 size)
{
for (int i = 0; i < size; i++)
{
cout << arr[i] << " ";
}
cout << endl;
return 0;
}
myPrint(arr0, size);
通过如下程序来寻找c++编译器是如何处理函数模板的。
# include
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++函数模板及实现原理