码迷,mamicode.com
首页 > 其他好文 > 详细

函数模板

时间:2015-06-12 11:21:40      阅读:110      评论:0      收藏:0      [点我收藏+]

标签:

在程序的世界中,有一句被奉为经典的话:

算法 + 数据结构 = 程序

这篇文章将会介绍一下 “算法” 的发展过程。即为什么会发展到 “函数模板” 这一步。同时,我们也可以了解到函数式编程的四个技巧。

内联函数

早期的算法是严格地使用内联代码来实现的,内联我们知道就是代码直接编写在程序中,那么程序员开始厌倦编写重复的代码。一些特性逐渐被添加到程序设计语言中,使得能够编写一段称之为 “子程序” 的代码来执行特定的任务。这便是函数调用的雏形。

:现在内联函数这个技巧仍然存在,对于体积小又多次使用的代码段使用内联方式会提高效率。不过如今,编译器帮助我们省去了编写重复代码的工作。

函数调用(回调)

使用调用函数这种方法提高了代码的重用性。比如,对于交换两个整型的数,我们可以编写下面的子程序:

void swap(int &first, int &second)
{
    int temp = first;
    first = second;
    second = temp;
}

那么以后每一次需要使用该功能时,就可以直接调用此函数。因此,这催生了 “函数库” 的出现,别人写好的函数(模块)直接拿来用就可以,非常方便。

函数调用方式是一种主动的直接调用方式,在程序的灵活性上稍显不足。在这基础上发展而来的便是 回调 这种方式,简而言之 回调 就是利用函数指针这一中间层作为参数传入到调用者中,在合适的时候,该指针指向的被调用者(一段代码)就会自动执行。这符合计算机世界的另一句经典名言:

计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决

下面就举个例子来看看 回调机制 在处理程序上的灵活性。比如我现在想要设计一个对整型数组排序的算法模块,简单的实现如下:

技术分享
void selectSort(int myArray[], int len)
{
    //select sort
    int temp = 0;
    int index = 0;
    for(int i = 0; i < len; i++)
    {
        temp = myArray[i];
        index = i;
        for(int j = i + 1; j < len; j++)
        {
            if(myArray[j] < temp)
            {
                index = j;
                temp = myArray[j];
            }
        }
        Swap(myArray[i], myArray[index]);
    }
}
View Code

这样做是按照从小到大的顺序排列,那如果我们想要实现从大到小的顺序排列呢?是不是需要重新再编写一个函数呢?我们可以通过 回调机制 来解决这个问题,即我们让用户来决定我们的比较逻辑,用户自定义比较逻辑函数,只需要将该函数的地址传入我们的算法模块中即可,下面是修改之后的算法:

技术分享
int myCompare(int first, int second)
{
    if(first > second)
    {
        return 1;
    }
    else
    {
        return 0;
    }
}

void selectSort(int (*compare)(int, int), int myArray[], int len)
{
    //select sort
    int temp = 0;
    int index = 0;
    for(int i = 0; i < len; i++)
    {
        temp = myArray[i];
        index = i;
        for(int j = i + 1; j < len; j++)
        {
            if(compare(myArray[j], temp))
            {
                index = j;
                temp = myArray[j];
            }
        }
        Swap(myArray[i], myArray[index]);
    }
}
View Code

这样的处理方式给模块的使用者留下了自定义的接口,显然增大了该算法模块的通用性与灵活性。

函数重载

我们还是拿上面的那个排序模块来举例,上面的函数解决了整型数组的排序问题,可以如果我们想对浮点型或字符型数组进行排序呢?是不是需要另外分别定义 selectSortDouble 以及 selecrSortChar函数呢?这显然是在做重复性的工作,一种解决的办法是使用函数重载。在C++中重载的规则如下:

只要两个函数的定义没有具有完全相同的特性,则函数名可以重载

即只要函数的参数数量及类型不相同就可以实现重载,注意,函数的返回值类型并不是该函数特性的一部分。

那么我们就可以编写: void selectSort(int myArray[])void selectSort(double myArray[])void selectSort(char myArray[])

这三个函数来实现我们的模块,这么做对于 用户调用模块来说是更加方便的,但是它并没有在本质上改变重复性编写代码这一项工作。下面就引入了我们的主题——函数模板!

函数模板

在上面的排序算法中不管对什么样的输入类型,算法本身就是在做它该做的事情,与参数类型没有联系,因此我们就可以将该算法抽象出来,作为一种通用的样式,对任何数据类型都适用。这样的编程方法叫做 函数模板。其实也很好理解,就是将 编程 所在的层次更加抽象了一些,说不定在不久的将来我们可以将语言特性再抽象一步,即可以为任何语言编写通用的算法模板。

编写函数模板

这里就介绍下C++中如何编写函数模板。它是通过声明类型形参并在函数原型和定义中使用这些形参来代替特定的类型而工作的。下面给出排序算法的模板写法:

技术分享
template <typename T>
void Swap(T &first, T &second)
{
    T temp = first;
    first = second;
    second = temp;
}

template <typename T>
int myCompare(T first, T second)
{
    if(first > second)
    {
        return 1;
    }
    else
    {
        return 0;
    }
}



template <typename T>
void selectSort(int (*compare)(T, T), T myArray[], int len)
{
    //select sort
    T temp = myArray[0];
    int index = 0;
    for(int i = 0; i < len; i++)
    {
        temp = myArray[i];
        index = i;
        for(int j = i + 1; j < len; j++)
        {
            if(compare(myArray[j], temp))
            {
                index = j;
                temp = myArray[j];
            }
        }
        Swap(myArray[i], myArray[index]);
    }
}
View Code

模板定义以关键字template开始,后接模板形参表,模板形参表是用尖括号括住的一个或多个模板形参的列表,形参之间用都好分隔。

使用函数模板

使用函数模板时,编译器会推断哪个(或哪些) 模板实参绑定到模板形参。一旦编译器确定了实际的模板实参,就称它实例化了函数模板的一个实例。我们可以简单的认为编译器将实参跟模板结合在一起,生成了那个我们想要的那个函数。如我们可以这样调用:selectSort(myCompare, myArray, 6);其中myArray可以是整型、浮点型或者字符型。

通过上面的例子我们编写的算法模块从单一的功能转变成多项功能,代码重用性提高,同时通过回调给用户留下了接口,交互性以及灵活性更强。

函数模板总结

下面再总结一下编写函数模板的格式以及注意点(抄的教科书上的):

函数模板

形式

  template <typename TypeParam> Function

  template <class TypeParam> Function

更通用的形式:

  template <specifier TypeParam1, ..., specifier TypeParamn> Function

在这些形式中,TypeParam...是通用类型形参;每个specifier是关键词typenameclass;而Function是这个函数的原型或定义。

注意:

1. 单词template是一个C++关键字,规定其后所跟的是一个函数的模式,而不是一个实际的函数原型或定义。

2. 关键字typenameclass可以在一个类型形参列表中交换使用。

3. 相对于“正常的”形参(以及实参)出现在圆括号内,类型形参出现在尖括号内。

4. 和正规的函数不同,一个函数模板不能分散在多个文件中,也就是说,不能把它的原型放在一个头文件中,而把它的定义放在另一个实现文件中。函数模板必须全部放在头文件中。

5. 一个函数模板仅仅是描述根据给定的实际类型产生不同函数的一个模式。这个创造函数的过程被称为实例化。 6. 在通用形式里,每个类型形参必须在函数的形参列表中至少出现过一次。原因是编译器仅仅依靠函数调用中的实参的类型来决定绑定什么类型到类型形参。

函数模板

标签:

原文地址:http://www.cnblogs.com/Gru--/p/4570997.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!