[c++] 函数模板基本用法

前言:

c++编程可以分为两大类:1)面向对象编程,由类的继承体系和运行时多态作为根基;2)泛型编程,以模板编程作为基础,搭建编译时多态体系。

这两大类别都有优缺点,一般情况下,面向对象编程用来搭建对象模型结构,而泛型编程用来规划一些通用的算法和函数。

 

基本用法:

案例1:

#include <stdio.h>

template <typename T>//申明和定义都要添加此行
void swap(T &a,T &b);//这里的&符号是为了swap功能方便,不是模板功能的标配,也可以 void swap(T a,T b),只不过在swap的时候需要使用*运算


int main(int argc,char ** argv)
{
	int a=4,b=6;
	printf("before swap,a=%d,b=%d\n",a,b);
	swap(a,b);
	printf("after swap,a=%d,b=%d\n",a,b);

	float c=1.1,d=2.2;
	printf("before swap,c=%f,d=%f\n",c,d);
	swap(c,d);
	printf("after swap,c=%f,d=%f\n",c,d);

	//下面的内容编译不通过,在一个模板函数中,两个参数的类型必须一致,比如模板中都是T,那么这里也应该一致,都是int,或者都是float
//	printf("before swap,a=%f,c=%f\n",a,c);
//	swap(a,c);
//	printf("before swap,a=%f,c=%f\n",a,c);


}

template <typename T>//申明和定义都要添加此行
void swap(T &a,T &b)
{
	T swap;
	
	swap=a;
	a=b;
	b=swap;
}


/*模板函数在此处被不同参数类型调用了两次,在编译的时候会对应生成两个函数:
 *1.   void swap(int &a,int &b)
 *2.   void swap(float &a,float &b)
 * 
 *使用objdump -t 1|grep swap查看符号表,可见如下:
 *[root@localhost 函数模板]# objdump -t 1|grep swap
 *00000000004006f0  w    F .text  000000000000002c              _Z4swapIfEvRT_S1_
 *00000000004006c4  w    F .text  000000000000002c              _Z4swapIiEvRT_S1_
 *确实是被扩展成了两个函数,并且看名字,发现其中有f和i字样,用以表明真实类型
 *
 *
 *
 *
 * */

案例2:

#include <stdio.h>

template <typename T>
void swap(T &a,T &b);

template <typename T>			//每个template只影响紧跟着的那个函数,所以每次都要在写一遍
void swap(T a[],T b[],int num);


int main(int argc,char ** argv)
{

	int a=4,b=6;
	
	char c[3]={'a','b','c'};
	char d[3]={'x','y','z'};
	
	printf("before swap,a=%d,b=%d\n",a,b);
	swap(a,b);
	printf("after swap,a=%d,b=%d\n",a,b);

	printf("before swap,c={%c%c%c},d={%c%c%c}\n",c[0],c[1],c[2],d[0],d[1],d[2]);
	swap(c,d,3);
	printf("after swap,c={%c%c%c},d={%c%c%c}\n",c[0],c[1],c[2],d[0],d[1],d[2]);
}

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

template <typename T>
void swap(T a[],T b[],int num)	//模板修饰的未必一定是引用类型,模板中也可以有通用类型
{
	int i;
	T swap;
	for(i=0;i<num;i++)
	{
		swap = *(a+i);
		*(a+i) = *(b+i);
		*(b+i) = swap;
	}
}

/*这里对swap函数做了重载,同时参数列表也包含了更多类型,但是需要注意一点:如果使用了模板,那么紧接着的这个函数
 * 一定要在入参里面使用模板变量T,否则编译时会报错“定义了模板没使用”,所以不要尝试定义模板仅仅在函数实现内使用,
 * 比如:
 * 	template <typename T>
 * 	void myprint(void)
 *	{
 *		T i;
 *		i=10;
 *		printf("%d\n",i);
 *	}
 * 上面那个例子编译不通过。
 *
 * */

案例3:

显式具体化,隐式实例化,显式实例化
/* 一、显式具体化(是一种函数模板,对应编译器做的“隐式具体化”)
 * 前面介绍个函数的重载和函数的模板,现在做个小结:
 * 1.函数重载:通过改变函数列表的形式(返回值无影响),达到多个函数实现公用一个函数名的目的
 * 2.函数模板:提供适用于多种入参的统一函数实现,达到多种入参类型公用一个函数逻辑的目的(入参格式不变)
 * 
 * 如果在使用函数模板的时候,对于多种入参,函数逻辑是一样的,在编译的时候会根据具体的被使用情况,具体化多个
 * 模板实例出来,比如 void func(int a,int b) , void func(int a[],int b[])  ,  void func(stu stu1,stu stu2)。(这的stu是结构体,C++使用结构体时可直接用结构体名)
 * 那么如果我想自己依据模板定义一个模板实例,这个实例的逻辑和模板不一样,那么该怎么做呢。这就是“显示具体化”,与之对应的就是在编译时自动生成的“隐式实例化”(见
 * 上面的三个func,都是隐式实例化)
 *
 * 直白点:针对当前模板,如果入参是某个参数类型,那就做和别的入参不一样的动作(显示具体化)
 *
 * 声明格式: template <> void Swap<int *>(int *a,int *b);        Swap后面的尖括号里面的内容就表示要特殊对待的入参类型
 *
 * 二、上面提到了“隐式实例化”,那么必然与之对应的“显式实例化”,那么现在来对比下  显式定制化 、隐式实例化 和 显式实例化 三者的异同:
 *    1.显式定制化:已有模板,但不想改变函数名,又不想改入参列表(重载),那么可以通过显式具体化来声明一个特例;		  (函数逻辑一般和模板不一样,由编译器判断入参类型,就是想搞个特例,但是又想用现在的模板)
 *    2.隐式实例化:创建模板后,编译器会根据调用模板时使用的入参类型,自动创建相应的模板实例;				  (函数逻辑和模板一样,由编译器判断入参类型,有点"编译器你帮我看着弄吧,我不管了的意思",熟手用起来比较合适,新手用感觉不大靠谱)
 *    3.显式实例化:创建模板后,不想让编译器帮助自己隐式创建实例,于是乎自己在调用函数的地方,手动指定传入参数的参数类型;(函数逻辑和模板一样,人为指定入参类型,比隐式实例化多一步,有点"我知道自己要什么,我就要自己指定的意思",控制欲较强)
 *
 *
 *	(!!!)注:可以把显示定制化理解为对当前模板函数的重写。目标场景:想要用模板函数的名字和参数列表结构(不想重新起函数名),但是内部逻辑完全不一样。
 *
 *
 *
 *
 *
 * */


#include <stdio.h>

template <typename T>
void Swap(T &a,T &b);

template <> void Swap<int>(int &a,int &b);	//显示定制化

int main(int argc,char ** argv)
{
	int a=4,b=6;
	char c='c',d='d';
	float e=1.0,f=2.0;	

	printf("a=%d,b=%d\n",a,b);
	Swap(a,b);					//显示定制化,会使用显示定制化的函数逻辑
	printf("a=%d,b=%d\n",a,b);

	printf("c=%c,d=%c\n",c,d);
	Swap<char>(c,d);				//显式实例化
	printf("c=%c,d=%c\n",c,d);	

	printf("e=%f,f=%f\n",e,f);
	Swap(e,f);					//隐式实例化
	printf("e=%f,f=%f\n",e,f);

}

template <typename T>
void Swap(T &a,T &b)	//模板
{
	printf("muban hanshu\n");
	T swap;

	swap=a;
	a=b;
	b=swap;
}


template <> void Swap<int>(int &a,int &b)	//显示定制化
{
	printf("xianshi jutihua\n");

	int swap;
	swap=a;
	a=b;
	b=swap;
}

 

模板编程局限性:

模板函数有一定的局限性,比如入参的类型和函数流程中能接受的类型不匹配,此时会在编译的时候报错,可以理解为,模板函数在编译时会被翻译成相应的特定函数实现,那么函数实现本身有问题,自然是会报错的。比如:

template <typename T>
void func(T a,T b)
{
    a=b;
}

如果a是结构体,b是数组的话,就会出现数组赋值给结构体的错误。
注:这里说到,a是结构体,b是数组,这样传入模板函数是可以编译通过的。但是!!!如果a是int,b是float,那么编译无法通过,因为模板中要求a和b的类型一致,而int和float明显无法满足模板的要求


如果有:

template <typename T>
void func(T a,T b)
{
    a=b;
}

那么,如下函数是可以匹配上的:
int a=1;b=2;
int &c=a;
int &d=b;
func(c,d);
也就是说,c++把&看成是一种类型,即(int &) = T,但是,很可能作用和功能会受到限制,比如1.cpp的swap功能就失效了。
同理(int *)=T,但是函数功能也可能不定,而且指针操作还可能导致非法内存访问

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值