浅谈c++的模板机制(一) -- 函数模板

  • 浅谈c++的模板机制
      • 泛型编程
      • 函数模板
        • 为什么会出现函数模板
        • 初识函数模板
        • 函数模板语法
        • 函数模板偶遇普通函数(重载)
        • 模板机制的实质
      • 参考资料

浅谈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 
void myswap(T &a, T &b)
{
    T c;
    c = a;
    a = b;
    b = c;
    cout << "hello...." << endl;
}

如果要交换两个 int 型变量调用的时候就这么写:myswap (x, y); // x,y 为 int型

如果要交换两个 double 型变量:myswap (a, b); // a, b 为 double 型
同理如果想交换其他类型写法类似。

从上面这个例子也可以看出,函数模板适用于:函数功能相同但是参数不同的情况

函数模板语法

函数模板的语法很简单,在函数定义前面加一句:template

template 关键字就是告诉编译器 我要使用模板了。

: T 是一种数据类型,不要随便报错,在下面的第一个函数里面可以将类型不确定变量使用 T 来定义。从上面那个 myswap() 函数可以看出。

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;
  • 第一:从函数模板的调用规则可知:max (x, y); 调用的肯定是函数模板。
  • 第二:如果直接写max(x,y);调用的是普通函数。这说明普通函数的优先级比函数模板的优先级高
  • 第三:如果可以产生一个更好的匹配时,调用函数模板。这句话的意思是: 对于上面的程序,如果你写一个:max(1.0, 2.0); 那么调用的是函数模板。因为函数模板能产生一个更好的匹配。什么是更好的匹配?对于上面的普通函数两个形参都是int型,因此最好调用时传递的实参应该是两个int型的,如果传两个double型的变量普通函数max最想要的。对于上面的函数模板呢?它要求的是两个形参的类型一致。另外:这个是不是和函数模板的自动类型推导有点类似呢?
  • 第四:如果想直接调用函数模板可以使用空参数列表的方式。对于上面的例子就是:max<>(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的汇编程序。如下图
浅谈c++的模板机制(一) -- 函数模板_第1张图片
在汇编程序中找到main函数, 然后再main函数里面找到myswap关键字浅谈c++的模板机制(一) -- 函数模板_第2张图片
可以看见一共有两次调用了myswap函数,和源程序一样。接下来寻找这两次调用的函数体
浅谈c++的模板机制(一) -- 函数模板_第3张图片
浅谈c++的模板机制(一) -- 函数模板_第4张图片
可见这两次调用的函数体并不是同一个。也就是调用的不同类型有几次就生成几个不同的函数体。

编译器并不是把函数模板处理成能够处理任意类的函数;编译器是根据函数模板调用时的具体类型产生不同的函数;
编译器会对函数模板进行两次编译:在声明的地方对模板代码本身进行编译(这次只进行简单纠错,替换),在调用的时候还会进行一次编译,这次会产生具体的函数体,并调用之。

下一篇文章:类模板

参考资料

传智c/c++视频
C++函数模板及实现原理

你可能感兴趣的:(c/c++)