为什么需要模板
我们经常有这样的一种使用的情形,就是我们可能需要设计一个函数,然后函数的参数可能是整形的,也可能是浮点型的,还有可能是其他的类型的,这个时候如果对于每一个类型都写一个函数,未免有点太复杂了
我们有以下几个方法来实现一个函数针对不同的类型都能够实现
法一:通过函数重载实现
比如我们想要写一个相加的函数,我们需要实现函数的重载
int Add(const int a,const int b)
{
return a+b;
}
double Add(cosnt double a,const double b)
{
return a+b;
}
char Add(const char a,const char )
缺点分析:
1. 一个类出现时就需要继续重载一个函数
2. 代码的复用率低
3. 如果一个函数只是返回值不同,函数的重载还解决不了问题
4. 如果函数的逻辑错误了,那么所有的函数都需要修改,麻烦
法二:使用公共的基类
我们可以将重复的代码放在一个公共的基类,然后进行一个继承
缺点:
1. 公共的代码放在了基类里面就缺少了类型检查的优点了
2. 一个简单的函数,以后使用了继承之后,以后维护起来麻烦
法三:使用宏函数
#define Add(a,b) ((a)+(b))
缺点:
1. 没有了类型检查
2. 在预处理阶段就被替换掉了,不方便调试
引入模板
C++为了解决上面的问题,于是就引入了模板的使用,像下面的形式来实现Add函数的复用
template<class T>
T Add(const T& a,const T&b)
{
return a+b;
}
其中我们使用class的时候,也是可以使用typename这个关键字的
我们在其他的地方调用这个模板函数的时候,就可以使用下面的方式进行一个调用的过程
int x = 1;
int y = 2;
double m = 1.0;
double n = 2.0;
Add(x,y);
Add(m,n);
上面的两次对模板函数的调用是不是调用的同一个函数呢,答案:不是。他们调用了不同的函数,这里引入的一个概念就是模板的实例化
我们经常听到的一个概念是对象的实例化,就是我们拿一个类去实例化一个对象,比如我们使用下面的代码去实例化一个string的对象
string str;
同样的我们的模板也是有实例化的一个过程的,就是在我们调用我们函数的时候,根据我们的参数进行一个推演,比如刚刚的那个Add(x,y);这个 时候编译器会根据参数的类型,实例化生成一个参数是整形的Add函数,然后在对他进行一个调用,这个就是简单的实例化的过程。
所以说上面的两次调用实际上是生成了不同的函数,然后进行的一个调用的过程
小问题
问题一:模板函数和实例函数共存
还是上面Add函数,如果我们这个时候在上面的模板函数的基础之上又实现了一个函数,这个时候会如何调用呢
int Add(const int& a,const int& b)
{
return a+b;
}
在这种情况下,如果我们有已经实例化的函数,这个时候会直接调用我们已经实例化的函数,如果没有已经 实例化的函数,这个时候才会去找我们的模板函数,然后实例化一个函数进行一个调用的过程
问题二:模板函数和实例函数参数不匹配
还是上面的程序,我们即有了一个模板函数,又有了一个实例函数,这个时候我们在使用的时候,传递的参数不是两个整形而是两个双精度浮点型,那么这个时候是如何调用的呢
double a = 1.0;
double b = 2.0;
Add(a,b);
这个时候会检查我们的实例函数,但是参数的类型不匹配,所以实际上是生成了一个参数是double类型的模板函数,然后再进行函数的一个调用的过程
如果我们的参数一个是int,一个是double的,这个时候又是怎么样的呢,由大家自己去验证吧
这里我们还需要注意的一个问题就是,实例化函数中的第二个参数不是一个const的时候,这个时候,我们的給 我们的参数传入的是一个double的变量的时候,会发生隐式类型的转换的,这个时候会生成一个临时的变量,这个临时的变量具有常性,所以我们应该在参数上面加上一个const
问题三:模板实例化和类型检查
我们知道我们的模板函数只有在实例化的时候,才会生成可执行代码的,如果我们在VS下面测试的时候,编译器是不会对模板函数的内部进行检查的,比如我们少写了一个分号,但是在linux下面使用g++进行 编译的时候是会进行一个检查的过程的
模板类
模板类也很容易实现,只不过在使用的时候,我们需要对模板类进行一个显式的实例化的过程
template<class T>
class Arr
{
public:
Str()
:_a(a)
{}
private:
T _a;
}
Arr<int> arr;
上面的调用就是一个显式的实例化的过程