前言:
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,但是函数功能也可能不定,而且指针操作还可能导致非法内存访问