函数模板总结

函数模板

基本范例认识函数模板

(1)背景:
就以两个数相加来说,如果两个数是整型,那么我们可能要写一个函数像下面这样,

 int add(int i,int j)
 {
    
    
        return i+j;
 } 

而如果两个数是浮点类型,那么函数可能要这样写,

float add(float i,float j)
{
    
    
      return i+j;
}

(2)解决办法:那么有没有什么办法可以优化一下,有,为了减少代码的冗余性,于是函数模板就出现了。
(3)关于函数模板的理解:
3.1可以理解为一个函数家族
3.2也可以理解为一个公式
(4)函数模板语法介绍:

template<模板参数列表> 
函数返回类型 函数名(模板参数列表 形参名)
{
    
    
	 //......
 }

例如:

 template<typename T>
T sub(T a, T b)
{
    
    
	return a + b;
}	

4.1模板的定义是以template关键字开头
4.2类型模板前面用typename来修饰,所以,遇到typename就知道其后面跟的是一个类型,并且typename可以被class取代,但是这里的class并不代表类的意思,

template<class T>
T sub(T a, T b)
{
    
    
	return a + b;
}
//可以用class是由于历史原因,但是,还是尽量使用typename,并且,不能用struct。

4.3类型模板参数T,代表一个类型,也就是说还可以用其它标识符,比如,U等等。
4.4T这个名字可以被换成其它任何标识符,对程序员没有影响,用T只是一种习惯。
(5)范例演示

namespace _namesp1
{
    
    
	//template<typename T>
	template<class T>
	T sub(T a, T b)
	{
    
    
		return a + b;
	}
}
int sum = _namesp1::sub(1, 2); //根据1和2推断出T为int类型
cout << sum << endl;

double sum1 = _namesp1::sub(1.2, 3.4); //根据1.2和3.4可以推断出T为double类型
cout << sum1 << endl;

_namesp1::sub(1, 2.5);//不可以,矛盾了。编译器无法推断出T到底是int类型还是double类型。

函数模板实例化

实例化:编译时,用具体的“类型”代替类型模板参数类型的过程就叫实例化(也有人叫代码生成器)
obj文件编译后即可产生,查看obj文件,可以看到具体的实例化以后的函数。
vs开发环境中可以借助 dumpbin工具查看obj文件,这样就可以看到实际的函数原型。(common object file format–同用对象文件格式)

例如,当我们写下如下几行代码时,编译器会实例化出两个函数版本,函数版本可以查看目标文件。

_namesp1::sub(1, 2);
_namesp1::sub(1.1, 2.2);

//以下是查看obj文件显示的具体内容:
实例化以后的函数名为:sub和sub,,并不叫sub了。

//编译器根据实际的情况生成的函数如下所示:

int __cdecl sub<int>(int,int)
double __cdecl sub<double>(double,double)

实例化以后,函数名包括三部分,
(1)模板名
(2)<>尖括号
(3)以及<>尖括号里的具体类型
在编译期间,编译器会查看函数模板的函数体部分,以确定是否可以产生函数模板的实例化过程。
总结:所以在写函数模板这类代码时,编译器会做两件事情,
第一件:在实例化之前,编译器会检测语法是否正确,比如是否漏写了分号等等。
第二件:在实例化期间,编译器也会进行检查,查看实例化是否有效。

//注意:函数模板的实例化过程,是从模板产生一个不同的实体,并不是产生一个可以处理任何类型的单一实体,,起码现在不行,或许以后可以。

模板函数的类型推断

namespace _namesp3
{
    
    
	template<typename T,typename U>
	auto add(T i, U j)
	{
    
    
		cout << "auto add(T i, U j)" << endl;
		return i + j;
	}
	auto add(int i,int j)
	{
    
    
		cout << "auto add(int i,int j)" << endl;
		return i + j;
	}
}
  • 自动推断
cout << _namesp3::add(1, 8) << endl;//根据1和8的值自动推断T和U为int类型
  • 显示指定
(2)显示指定
cout << _namesp3::add<int,int>(1, 18) << endl;//显示指定T和U为int类型
cout << _namesp3::add<double,double>(1, 8) << endl;//T和U为double类型
cout << _namesp3::add<int,double>(1, 8) << endl;
  • 空模板参数推断
(3)空模板参数推断要求编译器调用函数模板而非普通函数。
cout << _namesp3::add<>(1, 8) << endl; 
<>代表空类型模板参数,如果没有<>,那么就会调用非类型模板参数,这和编译器的调用优先级有关,如果我们想调用函数模板实例化的函数版本,那么可以加上<>

函数模板重载

四:函数模板重载(函数名一样,但函数返回类型或者参数类型,或者函数参数个数不一样)
4.1:函数(函数模板)名字相同,但是参数个数或者类型不同
4.2:函数模板和函数可以同时存在,此时可以把函数看成重载。
4.3:编译器会优先调用普通函数,而非函数模板。

特化

关于特化的理解,先来看一个例子说明,什么是特化,为什么需要特化。
函数模板的特化:

template <class T>
T max(const T t1, const T t2)
{
    
    
  return t1 < t2 ? t2 : t1;
}

如上已有的模板定义可能在针对一个指针类型的参数时,工作将可能会不正常,具体到字符串指针类型时,可能就需要下面的特例化模板定义:

template < >
const char* max(const char* t1,const char* t2)
{
    
    
  return (strcmp(t1,t2) < 0) ? t2 : t1;
}

这样才能使max(“aaa”, “bbb”);的调用更如人意(通常没有人想比较两个常量字符串存储的地址的大小)
总结:

  1. 模板的特化是在已有的通用模板不再适用于一些特殊的类型参数时,而针对这些特殊的类型参数专门实现的模板。
  2. 模板的偏特化是指需要根据模板的部分参数进行特化。
  3. 函数调用匹配的规则是:先精确匹配类型参数,然后匹配函数模板,最后通过参数隐式类型转换进行匹配
    4.函数模板无法偏特化,但是可以用函数模板重载或者函数重载来解决
  • 泛化
namespace _namesp5
{
    
    
	template <typename T,typename U>
	void add(T i, U j)
	{
    
    
		cout << "泛化版本" << endl;
     	cout << i << endl;
		cout << j << endl;
	}
}
//普通的方式就是泛化版本,即,常规化,大众化。
  • 全特化
	//T = double U = double
	template<>
	//void add<double,double>(double i, double j)
	void add(int i, double j)
	{
    
    
		cout << "函数模板全特化" << endl;
		cout << i + j << endl;
	}
  • 偏特化
	//偏特化编译失败
	//template<typename T>
	//void add<T,int>(T i, int j)
	//{
    
    
	//	cout << "函数模板偏特化" << endl;
	//  cout << i + j << endl;
	//}

//函数模板没有偏特化

总结:函数模板的泛化,特化,一般是写在.h文件中,并且是先有泛化版本,然后才能根据泛化版本进行特定的特化版本。

函数模板缺省参数

namespace _namesp6
{
    
    
	void add1(int i, int j)
	{
    
    
		cout << i + j << endl;
	}
	typedef void(*func1)(int, int);
	template<typename T,typename U,typename K = func1>
	void sub(T a, U b, K func1 = add1)
	{
    
    
		func1(a, b);
	}
	void add2(double i, double j)
	{
    
    
		cout << i + j << endl;
	}
	typedef void(*func2)(double, double);
	template<typename K = func2,typename T, typename U>
	void sub1(T a, U b, K gg = add2)
	{
    
    
		gg(a, b);
	}
}

缺省参数:
6.1可以指定函数模板的类型参数
6.2如果类型参数的第一个参数为默认类型,则,后面的所有参数不必也为默认类型,这与类模板不同。

_namesp6::sub(1, 2);
_namesp6::sub1(1.5, 2.7);

非类型模板参数

  • 基本概念
namespace _namesp7
{
    
    
	//template <typename T,typename U,int value = 100>
	template <typename T, typename U, typename int value = 100> //int 前面的typename画蛇添足,但是语法没毛病(typename用来修饰后面是一个类型)
	//template <typename T, typename U, class int value = 100> //class 不行
	auto add(T i,U j)
	{
    
    
		return i + j + value;
	}	
	//template <typename T,int value>
	template <typename , int >
	auto fun()
	{
    
    
		return 100;
	}
}
 cout << _namesp7::add<int,int,10>(1, 5) << endl;
 cout << _namesp7::add(1, 5) << endl;

//非类型模板参数,也就是说,除了有类型以外,还可以有非类型的参数,例如值等等。

注意:非类型模板参数的一些限制:浮点数,类类型等,这些是历史原因,或许未来会支持。非类型模板参数要求是constant的,因为编译器在编译时期就要知道函数模板相关的值。

  • 奇怪语法
    a):不管是类型参数还是非类型模板参数,如果代码中没有用到,则可以不写
cout << _namesp7::fun<int ,10>() << endl;
    b):类型前面可以增加一个typename修饰以明确标识一个类型

猜你喜欢

转载自blog.csdn.net/qq_38158479/article/details/113916321