25.C++- 泛型编程之函数模板(详解)

本章学习:

1)初探函数模板

2)深入理解函数模板

3)多参函数模板

4)重载函数和函数模板


当我们想写个Swap()交换函数时,通常这样写:

void Swap(int& a, int& b)
{
    int c = a;
    a = b;
    b = c;
}

但是这个函数仅仅只能支持int类型,如果我们想实现交换double,float,string等等时,就还需要从新去构造Swap()重载函数,这样不但重复劳动,容易出错,而且还带来很大的维护和调试工作量。更糟的是,还会增加可执行文件的大小.

所以C++引入了泛型编程概念

在C++里,通过函数模板类模板来实现泛型编程(类模板在下章将讲解)

函数模板

  • 一种特殊的函数,可通过不同类型进行调用
  • 函数模板是C++中重要的代码复用方式
  • 通过template关键字来声明使用模板
  • 通过typename关键字来定义模板类型

比如:  

template <typename T>       //声明使用模板,并定义T是一个模板类型

void Swap(T& a, T& b)           //紧接着使用T
{
    T c = a;
    a = b;
    b = c;
} 

当我们使用int类型参数来调用上面的Swap()时,则T就会自动转换为int类型.

函数模板的使用

  • 分为自动调用显示调用

例如,我们写了一个Swap函数模板,然后在main()函数里写入:

int a=0;
int b=1;

Swap(a,b);                   //自动调用,编译器根据a和b的类型来推导

float c=0;
float d=1;

Swap<float>(c,d);           //显示调用,告诉编译器,调用的参数是float类型

初探函数模板

写两个函数模板,一个用来排序数组,一个用来打印数组,代码如下:

#include <iostream>
#include <string>

using namespace std;

template < typename T >
void Sort(T a[], int len)
{
       for(int i=1;i<len;i++)
       for(int j=0;j<i;j++)
       if(a[i]<a[j])
       {
               T t=a[i];
               a[i]=a[j];
               a[j]=t;
       }
}

template < typename T >
void Println(T a[], int len)
{
     for(int i=0; i<len; i++)
    {
        cout << a[i] << ", ";
    }
    cout << endl;
}

int main()
{
    int a[5] = {5, 3, 2, 4, 1};

    Sort(a, 5);            //自动调用,编译器根据a和5的类型来推导
    Println<int>(a, 5);    //显示调用,告诉编译器,调用的参数是int类型

    string s[5] = {"Java", "C++", "Pascal", "Ruby", "Basic"};

    Sort(s, 5);
    Println(s, 5); 

    return 0;
}

运行打印:

1,2,3,4,5,
Basic,C++, Java,Pascal,Ruby,

深入理解函数模板

为什么函数模板能够执行不同的类型参数?

答:

  • 其实编译器对函数模板进行了两次编译
  • 第一次编译时,首先去检查函数模板本身有没有语法错误
  • 第二次编译时,会去找调用函数模板的代码,然后通过代码的真正参数,来生成真正的函数
  • 所以函数模板,其实只是一个模具,当我们调用它时,编译器就会给我们生成真正的函数.

试验函数模板是否生成真正的函数

通过两个不同类型的函数指针指向函数模板,然后打印指针地址是否一致,代码如下:

#include <iostream>

using namespace std;

template <typename T>       
void Swap(T& a, T& b)        
{
    T c = a;
    a = b;
    b = c;
}   

int main()
{
    void (*FPii)(int&,int&);  

    FPii = Swap ;                   //函数指针FPii

    void (*FPff)(float&,float&);

    FPff = Swap ;                  //函数指针FPff

    cout<<reinterpret_cast<void *>(FPii)<<endl;
    cout<<reinterpret_cast<void *>(FPff)<<endl;
   //cout<<reinterpret_cast<void *>(Swap)<<endl;
              //编译该行会出错,因为Swap()只是个模板,并不是一个真正函数
    return 0;
}

运行打印:

0x41ba98
0x41ba70

可以发现两个不同类型的函数指针,指向同一个函数模板,打印的地址却都不一样,显然编译器默默帮我们生成了两个不同的真正函数

多参数函数模板

在我们之前小节学的函数模板都是单参数的, 其实函数模板可以定义任意多个不同的类型参数,例如:

template <typename T1,typename T2,typename T3>       
T1 Add(T2 a,T3 b)
{
    return static_cast<T1>(a+b);      
}

注意:

  • 工程中一般都将返回值参数作为第一个模板类型
  • 如果返回值参数作为了模板类型,则必须需要指定返回值模板类型.因为编译器无法推导出返回值类型
  • 可以从左向右部分指定类型参数 

接下来开始试验多参数函数模板

#include <iostream>

using namespace std;

template<typename T1,typename T2,typename T3>       
T1 Add(T2 a,T3 b)
{
       return static_cast<T1>(a+b);      
}

int main()
{
       // int a = add(1,1.5);       //该行编译出错,没有指定返回值类型

       int a = Add<int>(1,1.5);
       cout<<a<<endl;                  //2

       float b = Add<float,int,float>(1,1.5);
       cout<<b<<endl;                  //2.5

       return 0;
}

运行打印:

2
2.5

重载函数模板

函数模板可以像普通函数一样被重载

  • 函数模板不接受隐式转换
  • 当有函数模板,以及普通重载函数时,编译器会优先考虑普通函数
  • 如果普通函数的参数无法匹配,编译器会尝试进行隐式转换,若转换成功,便调用普通函数
  • 若转换失败,编译器便调用函数模板
  • 可以通过空模板实参列表来限定编译器只匹配函数模板

接下来开始试验重载函数模板

#include <iostream>

using namespace std;

template <typename T>       
T Max(T a,T b)
{
       cout<<"T Max(T a,T b)"<<endl;
       return a > b ? a : b;
}

int Max(int a,int b)
{
       cout<<"int Max(int a,int b)"<<endl;
       return a > b ? a : b;
} 

int main()
{ 
       int a=0;
       int b=1;

       cout<<"a:b="<<Max(a,b) <<endl ;            //调用普通函数 Max(int,int)
       cout<<"a:b="<<Max<>(a,b)<<endl;            //通过模板参数表 调用 函数模板 Max(int,int)

       cout<<"1.5:2.0="<<Max(1.5,2.0)<<endl;   
               //由于两个参数默认都是double,所以无法隐式转换,则调用函数模板 Max(double,double)

       cout<<"'a',100="<< Max('a',100)<<endl;          
               //由于100是int型,所以char型可以进行隐式转换,从而调用普通函数 Max(int,int)
      return 0;
}

运行打印:

int Max(int a,int b)
a:b=1

T Max(T a,T b)
a:b=1

T Max(T a,T b)
1.5:2.0=2

int Max(int a,int b)
'a',100=100

未完待续,接下来下章来学习类模板

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏用户2442861的专栏

剑指offer 33 把数组排成最小的数

转载请注明出处:http://blog.csdn.net/ns_code/article/details/28128551

1142
来自专栏吾爱乐享

软件测试之学习shell编程函数的使用

1304
来自专栏IT可乐

深入理解计算机系统(3.5)------特殊的算术操作指令

  在上一篇博客 算术和逻辑操作 我们介绍了如下图几种常用的算术逻辑指令,但是大家发现没,这几种指令如果在 IA32 上只能操作32位寄存器,比如我用乘法指令I...

2017
来自专栏JetpropelledSnake

Python入门之函数的介绍/定义/定义类型/函数调用/Return

 本篇目录:     一、 函数的介绍     二、 函数的定义     三、 定义函数的三种类型     四、 函数调用的阶段     五、 Return返回...

3765
来自专栏GopherCoder

Python 强化训练:第五篇

1573
来自专栏pangguoming

ES6的Promise

相信凡是写过javascript的童鞋也一定都写过回调方法(callback),简单说回调方法就是将一个方法func2作为参数传入另一个方法func1中,当fu...

2783
来自专栏大前端_Web

详解javascript作用域和闭包

版权声明:本文为吴孔云博客原创文章,转载请注明出处并带上链接,谢谢。 https://blog.csdn.net/wkyseo/article/deta...

1184
来自专栏python3

python字典

所不同的是列表的索引只是从0开始的有序整数,不可重复;而字典的索引实际上在字典里应该叫键,虽然字典中的键和列表中的索引一样是不可重复的,但键是无序的,也就是说字...

1182
来自专栏企鹅号快讯

Python中的while循环

原创第13篇~while循环 阅读本文大概15分钟。 文章‍结构: while定义 普通while练习 while和input函数 while 和 else w...

3966
来自专栏C/C++基础

C++11 Unicode支持

在C++98中,为了支持Unicode字符,使用wchar_t类型来表示“宽字符”,但并没有严格规定位宽,而是让wchar_t的宽度由编译器实现,因此不同的编译...

3673

扫码关注云+社区

领取腾讯云代金券