C++模板一:函数模板与模板函数

一、模板的意义

模板的意义:一种特殊的函数可用不同类型进行调用,看起来和普通函数很相似,区别是类型可被参数化。

注意:
1.只用写一套代码实现逻辑,实例化的类型调用时可指定,编译器会从原模版实例化出来。
2.函数模板不进行编译,模板函数才是要被编译器所编译的。
3.一般来说,模板代码不能在一个文件定义,另一个文件使用。模板代码调用之前,一定要看到模板定义的地方,这样模板才能够正常实例化。

模板类型参数:可使用typename定义,也可以使用class定义。
语法规则: template 关键字用于声明开始进行泛型编程,typename 关键字用于声明泛指类型。
模板实现原理: 在函数调用点用,编译器用用户指定的类型,从原模版实例化一份函数代码出来。
在这里插入图片描述
为什么需要参数化呢?我们瞧一瞧一个简单的例子:

int sum(int a, int b)
{
	return a + b;
}

a,b的作用是接收实参的值,a,b是对实参的值进行参数化,这就是参数化。但是它的类型一直是固定的int类型,但是我们有了模板之后,对其类型也可以参数化了,方便我们使用不同类型。

二、函数模板与模板函数

我们直接来看实例:
实例1:比较两个数的大小函数模板实现

bool compare(int a, int b)
{
	return a > b;
}

这里我们比较两个数的大小,只能比较整型类型的数,使用范围有限,因此我们使用模板改进一下。
函数模板:

template<typename T>//定义一个模板参数列表 class也可以替换typename
bool compare(T a, T b)//compare 是一个函数模板
{
	cout << "template compare" << endl;
	return a > b;
}
int main()
{
	//函数的调用点
	compare<int>(10, 20);
	compare<double>(10.5, 20.5);
	return 0;
}

实例化的过程:在函数调用点用,编译器用用户指定的类型,从原模版实例化一份函数代码出来。但我们实际使用时并不用关注它。
模板函数:用模板实现的函数。 下面两个即模板函数。

//实例化的函数模板,即模板函数
bool compare<int>(int a, int b)
{
	return a > b;
}
bool compare<double>(double a, double b)
{
	return a > b;
}

实现成功:我们借用函数模板可以很方便的使用不同类型的模板函数。
在这里插入图片描述
实例2:函数模板实参推演:可以根据用户传入的实参的类型来推导出模板类型参数的具体类型。

compare(10, 20);//函数模板实参推演

       这里我们并没有向函数传入实参,因此会进行函数模板的实参推演,根据用户传入的实参的类型来推导出模板类型参数的具体类型。compare的实参是20和30,通过实参推导出来的类型是整型,因此a与b的类型为整型。
但是如果我们使用下面语句:

compare(30, 40.5);

       第一个实参会推演出整型,第二个实参会推演出double类型,编译器则会报错。并不能够成功推导出类型。我们可以在创建一个函数模板,a与b各自使用各自的函数模板,为其指定类型也是可以的,不过有可能会造成数据丢失。

compare<int>(30, 40.5);//指定类型

三、模板特例化

模板特例化:特殊的实例化。针对某些类型,以来编译器自己针对这些类型的模板实例化已经不满足代码的逻辑要求了,我们就针对该该类型提供自己的特例化版本。不是编译器提供的,而是用户提供的。
还是上面的函数模板,我们来看这个例子:

compare("aaa,=", "ccc");

虽然可以比较成功,但是我们注意一下:我们定义时候没有指定参数列表,会进行函数模板实参推演。推导出来形参T是const char * 类型,得到的实例化模板函数为:

bool compare<const char*>(const char* a, const char* b)
{
	return a > b;
}

这里在比较两个字符串的时候使用a>b,只是比较两个字符串地址谁大谁小,没有意义。我们应该比较为它们在ASCII表中的顺序大小。实际应该这样比较:

bool compare<const char*>(const char* a, const char* b)
{
	return strcmp(a, b) > 0;//字符串操作函数来比较
}

产生了问题:对于某些类型来说,依赖编译器默认实例化的模板代码,代码处理逻辑是有错误的。此时就需要函数模板的特例化。

我们针对compare函数模板,提供const char * 类型的特例化版本:

template<typename T>//定义一个模板参数列表 class也可以替换typename
bool compare(T a, T b)//compare 是一个函数模板
{
	cout << "template compare" << endl;
	return a > b;
}
//针对compare函数模板,提供const char*类型的特例化版本
template<>
bool compare<const char*>(const char *a, const char *b)
{
	cout << "compare<const char*>" <<endl;
	return strcmp(a, b) > 0;
}

int main()
{
	//函数的调用点
	compare<int>(10, 20);
	compare<double>(10.5, 20.5);

	compare(10, 20);
	compare<int>(30, 40.5);
	compare("aaa,=", "ccc");

	return 0;
}

其他都是从函数模板实例化的,字符串类型的比较调用的是我们特例化的版本。
在这里插入图片描述

四、模板函数、模板特例化、非模板函数的重载关系

当然,根据上面的例子。我们也可以自己实现一个普通的compare函数:

//普通函数(非模板函数)
bool compare(const char *a, const char *b)
{
	cout << "normal compare" <<endl;
	return strcmp(a,b) > 0;
}

那么当模板函数、模板特例化、非模板函数它们同时出现会发生什么呢?

template<typename T>//定义一个模板参数列表 class也可以替换typename
bool compare(T a, T b)//compare 是一个函数模板
{
	cout << "template compare" << endl;
	return a > b;
}
//针对compare函数模板,提供const char*类型的特例化版本
template<>
bool compare<const char*>(const char *a, const char *b)
{
	cout << "compare<const char*>" <<endl;
	return strcmp(a, b) > 0;
}
//普通函数(非模板函数)
bool compare(const char *a, const char *b)
{
	cout << "normal compare" <<endl;
	return strcmp(a,b) > 0;
}

案例1:优先调用普通函数,没有的话才去找函数模板,若有模板特例化,优先使用特例化
当我们这样调用时:编译器优先将其看为函数名,优先调用普通函数。

compare("aaa,=", "ccc");

执行结果如图:
在这里插入图片描述
案例2:优先调用模板特例化版本,没有的话才去找函数模板
当我们这样调用时:编译器优先调用compare模板的特例化版本。

compare<const char*>("aaa,=", "ccc");

执行结果如图:
在这里插入图片描述

五、函数模板的非类型参数

非类型参数:就是表示一个固定类型的常量而不是一个类型。

我们对于非类型形参的限定要分两个方面看:
1.对模板形参的限定,即template<>里面的参数。
2.对模板实参的限定,即实例化时<>里面的参数。

非类型形参的局限:
1.浮点数不可以作为非类型形参,包括float,double。具体原因可能是历史因素,也许未来C++会支持浮点数。
2.类不可以作为非类型形参。
3.字符串不可以作为非类型形参。
4.整形,可转化为整形的类型都可以作为形参,比如int,char,long,unsigned,bool,short(enum声明的内部数据可以作为实参传递给int,但是一般不能当形参)。
5.指向对象或函数的指针与引用(左值引用)可以作为形参。

非类型实参的局限:
1.实参必须是编译时常量表达式,不能使用非const的局部变量,局部对象地址及动态对象。
2.非Const的全局指针,全局对象,全局变量(下面可能有个特例)都不是常量表达式。
3.由于形参的已经做了限定,字符串,浮点型即使是常量表达式也不可以作为非类型实参。

我们来看一个例子:使用模板创建冒泡排序

template<typename T, int SIZE>//typename T为类型参数,int SIZE为非类型形参
void sort(T *arr)
{
	for (int i=0; i<SIZE-1; ++i)
	{
		for (int j=0; j<SIZE-1-i; ++j)
		{
			if (arr[j] > arr[j+1])
			{
				int tmp = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = tmp;
			}
		}
	}
}

int main()
{
	int arr[] = {12, 5, 7, 89, 32, 21, 35};
	const int size = sizeof(arr)/sizeof(arr[0]);
	sort<int, size>(arr);//size为模板的非类型实参
	for (int val : arr)
	{
		cout << val << " ";
	}
	cout<<endl;

	return 0;
}

我们这里的int SIZE与size就是模板的非类型参数,最后成功排序:
在这里插入图片描述
参考博客:
模板非类型形参的详细阐述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值