模板函数的使用

1. 模板的简单介绍

  我们已经写过有C语言版、C++版的顺序表了。但是这只是对单一的确定的类型起作用的方法,但是对于我们想使用一个顺序表,但不用在任何时候都去编写一个专属于这个类型的文件,无论是C或者C++,那么我们怎么办呢?

  运气的是,在C++的编译器里给我们提供了“模板”这个东西,使得代码复用率提高,使我们可以不用为了表示某一种类型就去专门编写一个专属此类型的文件。而且,如果去看C++里面的源码的话,可以发现,汗多东西都是用模板来写的。例如:顺序表(vector)、string类…

  模板分为两种模板:函数模板、类模板。

  函数模板:代表了一个函数家族,该函数与类型无关,在使用时被参数化,根据实参类型产生函数的 特定类型版本。

  可能这样说函数模板你可能不理解或者不直观,那么我们来看一个小例子,可能这个例子我之前也举过:
  

int Add(int x,int y)
{
  return x+y;
}
char Add(char x,char y)
{
   return x+y;
}
double Add(double x,double y)
{
   return x+y;
}

int main()
{
    Add(1,2);
    Add('a','b');
    Add(1.23,4.56);
    return 0;
}

  看完了吗?觉得有什么问题吗?代码肯定没问题,那么没有什么其他的问题吗?这是函数的重载,但是我们要使用加法这个逻辑的时候,因为使用的类型的不一样,我们还要去编写一个相应类型的函数。那么,如果我们写成下面这种类型的呢,也就是今天所讲的模板:
  

template <typename T>
T Add(const T& x,const T& y)
{
   return x+y;
}

这里写图片描述

  template是写模板时的关键字,typename顾名思义就是类型名,后边的类型名T你可以定义成自己想要的字符,typename也可以用class替换,只是在某些老一点的编译器下通过不了。但切记,不能使用struct替换。

  模板只是一个模具,他不会生产出任何的产品,即不会构建任何函数,编译器用模板产生指定的类或者函数的特定类型版本,产生模板特定类型的过程称为函数模板实例化。若还是不理解,我可以介绍你一个不是办法的办法,某种程度上,我们可以认为,在存在模板的情况下,编译器会实例出任何一种合理的函数或类,所以我们在调用它的时候,编译器会自动检测它的类型,检测到正确的类型之后,会把这个函数导向他自己实例化出的函数那里,例如下图所示:
  这里写图片描述

  这张图中,在使用“int”型的数据时,实例化出“int”类型的函数,在使用“char”型的数据时,实例化出“char”类型的函数,所以模板函数的原理大概就是这样。

2.实例化

  ●模板函数在实例化的过程中一般不会转换实参以匹配已有的实例化,相反会产生新的实例。 就比如调用两次“int”型的Add函数,则会实例化出两个“int”型的Add函数,而不会在第二次调用的时候使用第一次实例化完成的函数。

  ●数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换。数组实参将当做指向其第一个元素的指针,函数实参当做指向函数类型的指针。用代码也就是下面这种表示:
  

//形参为函数时:
template<typename T>
void FunTest(T t)
{
    t();//or (*t)()
}
void fun()
{
    cout << "hah" << endl;
}

int main()
{
    FunTest(fun);
    system("pause");
    return 0;
}

//形参为数组时:
template<typename T>
void FunTest(T t)
{
    cout << *t << endl;
}

int main()
{
    int arr[] = { 1, 2, 3, 4, 5 };
    FunTest(arr);
    system("pause");
    return 0;
}

3. 模板形参的使用规则

  模板形参名字只能在模板形参之后到模板声明或定义的末尾之间使用,遵循名字屏蔽规则。你可能问我,名字屏蔽规则是什么意思呢?其实很简单,举个例子你就明白了:

typedef int T
template<typename T>
T Add(T x,T y)
{
   return x+y;
}

  这个模板在使用中,“T”是按哪种类型进行解读的呢?你也可以自己试试,不过我也可以告诉你,此时的“T”是按模板里面的类型进行解读的, 这就叫名字屏蔽规则,typedef的类型或者其他定义的类型如果和模板的形参重名了,在模板中,还是会按照模板的类型去解析、实例化函数。
  模板形参的名字在同一模板形参列表中只能使用一次。这个相必很容易理解吧。就是不能出现下面这种情况:

template<typename T,typename T>
...

  模板形参的两个形参名或多个形参名的名字必须不同。

  非模板类型参数
  非模板类型形参是模板内部定义的常量,在需要常量表达式的时候,可以使用非模板类型参数。例如数组长度。在使用数组的时候,会想把长度传进去,此时你就需要一个整型的量传这个值,模板允许你在传模板形参时,传一个确定类型的变量,如:

template<typename T,int N>
void show(T arr[],int length)
{
   for(size_t i=0;i<length;i++)
       cout<<arr[i]<<" "; 
   cout<<endl;
}

当然,编译器还允许我们这样调用函数:

template<typename T,int N>
void show(T (&arr)[N])
{
   for(size_t i=0;i<length;i++)
       cout<<arr[i]<<" "; 
   cout<<endl;
}

  这样的话是不是感觉更直观了呢?

模板函数的几条注意事项

1、模板形参表使用<>括起来
2、和函数参数表一样,跟多个参数时必须用逗号隔开,类型可以相同也可以不相同
3、定义模板函数时模板形参表不能为空
4、模板形参可以是类型形参,也可以是非类型新参,类型形参跟在class和typename后
5、模板类型形参可作为类型说明符用在模板中的任何地方,与内置类型或自定义类型 使用方法完全相同,可用于指定函数形参类型、返回值、局部变量和强制类型转换
6、模板形参表中,class和typename具有相同的含义,可以互换,使用typename更加直观。 但关键字typename是作为C++标准加入到C++中的,旧的编译器可能不支持。

4. 模板函数重载

1、一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。
2、对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调动非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么将选择模板。
3、显式指定一个空的模板实参列表,该语法告诉编译器只有模板才能来匹配这个调用,而且所有的模板参数都应该根据实参演绎出来。
4、模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。

代码表示如下:

//1
int Max(const int& left, const int & right) 
{     
   return left>right? left:right; 
}
//2
template<typename T> 
T Max(const T& left, const T& right) 
{
   return left>right? left:right; 
}
//3
template<typename T> 
T Max(const T& a, const T& b, const T& c) 
{    
   return Max(Max(a, b), c); 
}

  假如现在调用的函数为Max(10 , 20);那么调用的函数是1函数,而不是从2模板函数里面实例化出来一个,因为编译器已经检测到了存在一个合适的函数,而不用用函数模板来实例化一个合适的函数。

指针的传法

  需要注意的是,我们在传指针的时候,编译器可能会跟我们开一个小玩笑。下面这种情况大家认为是传的什么呢?

template<typename T>
void FunTest(const T temp)
{
   cout<<typeid(temp).name()<<endl;
   cout<<"hah"<<endl;
}
int main()
{
   int a=10;
   int* p=&a;
   FunTest(p);
   return 0;
}

  我们不管函数体内实现的是什么,现在我们只关心传进去的temp到底是什么?是什么类型的?我们可以使用“typeid”这个函数。我们对于这种形式的传参,恐怕都会认为传进去的会是一个const int* 类型的指针把,实际上呢?不是的,恰恰相反,传进去的是一个int const *类型的指针,二者的区别我们就不多说了。如果上面的typeid显示的不够完整,那么我们可以给temp和 *temp分别赋值,是不是temp的操作实现不了呢?那么我们还能好好的比较一下字符串吗?这就要说一说函数模板的特例化了。

模板函数特化

  有时候并不总是能够写出对所有可能被实例化的类型都合适的模板,在某些情况下,通用模板定 义对于某个类型可能是完全错误的,或者不能编译,或者做一些错误的事情。

模板函数特化格式:

1、关键字template后面接一对空的尖括号<>
2、函数名后接模板名和一对尖括号,尖括号中指定这个特化定义的模板形参
4、函数体
  下面的代码我们就用字符串比较大小的逻辑实现:

template<typename T>
int compare(T left, T right)
{
    if (left < right)
        return 1;
    else
        return -1;
}

template<>
int compare<const char*>(const char*const left,const char*const right)
{
    return strcmp(left, right);
}

注意

  在写模板函数实例化的函数的时候,必须先有一个常规的模板函数。在写这篇博客的时候,我开始就是没写常规模板函数,编译器报的错误怎么都理解不了。
  特化的声明必须与特定的模板相匹配。就上面的代码,我们在main函数中调用该函数模板的话我们应该这么调用:

int main()
{
    const char* const p = "aaaaaaaaa";
    const char* const q = "bbbbbbbbb";
    compare(p, q);
    return 0;
}

小结:

  模板的使用在C++的编程的时候,可以提高代码很高的复用率,让我们的工程量大大减少。不过需要注意的是,我们在编写模板时,要注意关键字的使用方法。模板形参的传法,以及了解模板函数的重载的实现原理,以及编译器对重载函数的调用方法;遵循模板的使用规则,深刻了解模板中指针传加进去时的解读方式,时T const ,而不是const T ;还有在模板函数特化的时候我们注意要写常规模板。

文中若有不正确不准确的措辞及逻辑,请在评论区指出。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值