c++_函数模板及拓展详解

目录

1、函数模板

2、函数模板的调用

3、非类型模板参数

4、函数模板重载

5、函数模板特化

6、多文件实现函数模板

7、函数模板的嵌套

8、普通函数和函数模板的区别


1、函数模板

a.场景

通常,编程过程需要用到同一个函数应用于参数不同类型或参数不同数量的情况,虽然函数重载可以解决以上两种情况,但是面对函数体内函数语句一样的情况时就会显得繁琐,函数模板便是在重载函数上对函数体内重复语句的优化,实现重载函数的合并,减少作业量。

b.格式

* 1.函数模板以template开头,后边跟<>。<>里面叫模板参数列表,多个参数使用逗号,分开
* 2.<>里面必须具有一个模板参数,模板参数前面有个typename关键字,也可以用class,表示是个类型名。
* 3.如果模板参数列表里面有多个参数,那么就要使用多个typename,class template <typename T,typename Q>
* 4.模板参数列表里面表示在函数定义中要用到的“类型”或者“值”,与函数参数列表类似。
* 5.调用的时候使用指定模板实参,模板名字<模板实参>(函数参数)
* 6.模板函数根据调用传递的实参,在编译的时候确定类型。

template<class T1,typename T2>
返回值 函数名(参数){  }

案例:

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



2、函数模板的调用

a.显示类型调用:

template<class T>
T Add(const T& a, const T& b)
{
	return a + b;
}
int main()
{
	int a = 1;
	double b = 2.2;
	cout<<Add<int>(a,b)<<endl;
	cout<<Add<double>(a,b)<<endl;
}

b.隐式类型调用
 

template<class T>
T Add(const T& a, const T& b)
{
	return a + b;
}
int main()
{
	int a = 1, b = 2;
	cout << Add(a,b) << endl;
}


3、非类型模板参数

在模板参数列表里,还可以定义非类型参数,非类型参数代表的是一个值,既然非类型参数代表一个值,不是一个类型,肯定不能用typename/class关键字来修饰这个值,我们当然要用我们以往学习过的传统类型名来指定非类型参数了

当模板被实例化时,这种非类型模板参数的值,或者是用户提供的,或者是编译器自己推断的,但这些值必须都得是常量表达式,因为模板实例化发生在编译阶段

非类型模板参数的一些限制 :
(1). 浮点数、类对象以及字符串是不允许作为非类型模板参数的(只能为整形)。
(2). 非类型的模板参数必须在编译期就能确认结果。

 

案例:

#include<iostream>
using namespace std;
template<int a,int b>
int add1()
{
	return a + b;
}
template<class T,int a,int b>
int add2(T c)
{
	return c + a + b;
}
template<unsigned L1,unsigned L2>
int charscmp(const char(&p1)[L1],const char(&p2)[L2])
{
	return strcmp(p1, p2);
}
int main()
{
	cout << add1<1, 2>() << endl;
	cout << add2<int, 1, 2>(5) << endl;
	cout << add2<int, 1, 2>(1.6) << endl;
	cout << charscmp("test2", "test") << endl;
}


4、函数模板重载

定义:函数模板重载和普通函数的重载相似,也是同一个作用域类函数名相同参数列表不同。

案例:

 template <class T1,class T2>void swap1(T2 a,T1 b)
      template <class T1,class T2>void swap1(T1 a,T2 b)
        //注意,在函数参数顺序不同的重载中,实例化的时候不可以是相同类型

所谓的函数模板的重载是指,普通函数的版本,函数模板的版本和函数模板特例化的版本可以共存,例如:
//普通函数版本
bool Compare(char* a, char* b)
{
	cout << "普通函数版本" << endl;
	return strcmp(a, b) > 0;
}
 
//函数模板版本
template<typename T>
bool Compare(T a, T b)
{
	cout << "函数模板版本" << endl;
	return a > b;
}
 
//模板特例化版本
template<>
bool Compare(char* a, char* b)
{
	cout << "模板特例化版本" << endl;
	return strcmp(a, b) > 0;
}


5、函数模板特化

a.定义:为了解决函数模板的局限性,在定义函数模板时候,直接确定好T的类型。也就是特定的类型模板。
b.格式: template<class T> 返回值  函数名(类名& 对象){ }
c.匹配:如果传入的是自定义类型,并且和特化版本匹配,会优先使用特化版本
注意:如果特化后,类型确定才可以使用自定义类型中的成员变量和方法。

案例:

//模板特化:就是在实例化模板时,对特定类型的实参进行特殊处理,即实例化一个特殊的实例版本,
//当以特化定义时的形参使用模板时,将调用特化版本,模板特化分为全特化和偏特化;
//1. 函数模板的特化,只能全特化;
 
//泛型版本
template <class T> int compare(const T &v1, const T &v2)
{
  if(v1 < v2) return -1;
  if(v2 > v1) return 1;
  return 0;
}
 
//对于该函数模板,当实参为两个char指针时,比较的是指针的大小,而不是指针指向内容的大小,此时就需要为该函数模板定义一个特化版本,即特殊处理的版本:
 
//为实参类型 const char * 提供特化版本
template <> int compare<const char *>(const char * const &v1, const char * const &v2)
{
  return strcmp(v1, v2);
}
  a: template <> //空模板形参表
  b: compare<const char *> //模板名字后指定特化时的模板形参即const char *类型,就是说在以实参类型 const char * 调用函数时,将产生该模板的特化版本,而不是泛型版本,也可以为其他指针类型定义特化版本如int *.
  c: (const char * const &v1, const char * const &v2)//可以理解为: const char * const &v1, 去掉const修饰符,实际类型是:char *&v1,也就是v1是一个引用,一个指向char型指针的引用,即指针的引用,加上const修饰符,v1就是一个指向const char 型指针的 const引用,对v1的操作就是对指针本身的操作,操作方式与指针一致,比如*v1,是正确的;//注意这里的const char *, 由于形参是一个指向指针的const引用,所以调用特化版本时的实参指针类型(并非存储的数据的类型)可以为const也可以为非const,但是由于这里形参指针指向的数据类型为const char *(强调存储的数据是const),所以实参指针所指向的数据类型也必须为const,否则类型不匹配;
  
//特化版本 (int *)
template <> int compare<const int *>(const int * const &v1, const int * const &v2)//v1 和 v2 是指向const 整形变量的const引用;
{
  if(*v1 < *v2) return -1;//像指针一样操作,可以理解v1,v2就是指针,因为它是指针的引用;
  if(*v2 > *v1) return 1;
}
 
 2. 与其他函数声明一样,应该在一个头文件中包含模板特化的声明,在使用特化模板的源文件中包含该头文件;
 注意,函数模板调用时的实参与模板形参不进行常规转换,特化与泛型版本都不进行常规转换,类型必须完全一致,非函数模板在实参调用时进行常规转换;普通函数和函数模板调用时的实参与模板形参都进行两种转换:
 (1). const转换:接受const引用或者const指针的函数,可以分别以非const对象的引用或者指针来调用,无需产生新实例,如果函数接受非引用类型或者非指针类型,形参类型和实参类型忽略const,无论传递const还是非const对象给非引用类型的函数,都使用相同的实例;
 (2). 数组或函数指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规转换,数组转换为指向实参第一个元素的指针,函数实参当做指向函数类型的指针;注意:函数模板中,模板形参表中的非类型形参遵循常规转换原则。
 
//例子:
//16.6.1.h
 
#include <stdio.h>
#include <string.h>
#include <iostream>
 
//泛型版本
template <typename T> int compare(const T &v1, const T &v2)
{
  std::cout << "template <typename T>" << std::endl;
  if(v1 < v2) return -1;
  if(v2 < v1) return 1;
  return 0;
}
 
//为实参类型 const char * 提供特化版本
//template <> int compare(const char * const &v1, const char * const &v2) //省略了函数名后边的显示模板实参,因为可以从函数形参表推断出来,本定义与下边的定义都是正确的;
template <> int compare<const char *>(const char * const &v1, const char * const &v2)
{
  std::cout << "template <> int compare<const char *>" << std::endl;
  return strcmp(v1, v2);
}
 
//为实参类型 char * 提供特化版本
//template <> int compare(char * const &v1, char * const &v2)
template <> int compare<char *>(char * const &v1, char * const &v2)
{
  std::cout << "template <> int compare<char *>" << std::endl;
  return strcmp(v1, v2);
}
 
//16.6.1.cpp
#include <iostream>
#include "16.6.1.h"
using namespace std;
int main()
{
  cout << compare(1, 2) << endl;  //根据实参类型进行实参推断,将为该调用实例化int compare(int, int)
  char a[] = {"abc"}; //一个普通字符数组,不是指针,形参为引用时,数组大小成为形参的一部分,数组不转换为指针,类型不匹配;
  const char b[] = {"abc"}; //一个常量字符数组,不是指针,类型不匹配;
  char *p = "ddd"; //一个非const指针,指向非const数据,但是特化版本的形参类型是一个指向const数据的const引用,强调了指针指向的数据类型是const,也就是说实参指针指向的数据类型必须是const即指针存储的数据必须是const的,但这里不是因此类型不匹配;
  char * const pc = "ddd"; //一个const指针,指向非const数据,类型不匹配,原因同上,和指针是否是const没关系,和指针存储的数据类型有关;
  const char * const pc = "ddd"; //一个const指针,指向const数据,满足特化版本的形参(一指向const数据的const引用),类型匹配;
  const char * pc = "ddd"; //一个非const指针,指向const数据,类型匹配,原因同上;
 
  //为实参类型 const char * 提供特化版本
  const char *pa = "abc"; // 或者 const char * const pa = "abc"; 与指针指向数据类型是const还是非const有关,而与指针是const还是非const没关系,因为,特化版本的形参类型是一个指向指针的cosnt引用,因此不会改变指针的值,所以指针本身是const还是非cosnt没有关系,但是由于特化版本形参的引用指向的指针所指向的数据类型是const,所以不能使用一个指向非const数据的指针调用特化版本,因为数据类型不匹配;
  const char *pb = "bbd";
  cout << compare(pa, pb) << endl; // 根据实参类型为该调用实例化特化版本int compare(const * const &v1, const * const &v2), 函数模板调用时的实参与模板形参不进行常规转换;由于编译器对特化版本不进行实参与形参的常规转换,所以调用的实参类型必须与特化版本的声明完全一致,否则将从泛型版本进行实例化,或者函数匹配错误;由于compare声明的形参都是const char *即char *型指针存储的是const数据,所以不能传递一个存储了非const数据的char *型指针(尽管此时的cosnt char *形参不会改变实参指针指向的值),也不能传递一个const数组名字(此时数组名不会转换为指针),必须传递一个指向const数据的指针,即代码中的 const char *pa,类型必须完全匹配;
 
  //为实参类型 char * 提供特化版本
  char *pc = "ccc";
  char *pd = "ddd";                                                                           
  cout << compare(pc, pd) << endl;
  return 0;
 
  //char * 与 const char * 是两个不同的数据类型(前者存储的数据是常量与后者存储的数据是非常量),虽然可以将类型 char * 通过常规转换,转换成 const char *,但是作为模板实参,在模板实参推断时,不会把函数调用时的实参类型 char * 转换为模板形参类型const char *,所以必须提供两个特化版本。
 }
 
  运行结果:
  template <typename T>
  -1
  template <> int compare<const char *>
  -1
  template <> int compare<char *>
  -1 


6、多文件实现函数模板

a.定义:在定义函数或者类的时候,会.h和cpp文件,定义和申明分开。
b.错误原因及解决方案:

出现连接错误的原因:函数模板是在调用时确定的版本,而调用时.h中没有函数实现,出现连接错误,找不到函数体。
     如果分开后,编译会出现连接错误。
     第一种办法:在main中#include <xxx.h>  和 #include <xx.cpp>
     第二种办法:模板的定义和申明都放在.h头文件中。



7、函数模板的嵌套

定义:在函数模板中调用另外一个函数模板

案例:

template<class A, class B> void kkaka(A a, B b)
{
 a.kakaka<B>(b);
}
//在g++中,上面的声明会报错,原因在于a的kakaka函数被显式调用了,如果改为
template<class A, class B> void kkaka(A a, B b)
{
 a.kakaka(b);
}


8、普通函数和函数模板的区别


    a.函数只可以有一种数据类型相匹配。模板有多种类型
    b.隐式推导优先使用普通函数,只有普通函数不匹配才使用函数模板
    c.函数模板只有在调用时,才会构建函数,而普通函数是在编译时。
    d.普通函数调用时候可以发生自动类型转换,而函数模板不行。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值