模板是 c++ 最重要的特性之一,模板函数、模板类、类中的模板函数、类中的模板类、模板类中的模板类等等,可以写出太多强大的代码,这也是模板的魅力所在,而 STL 就是基于模板的,所以各种意义上都有必要掌握模板的基本用法。
引用《c++ primer》, 《STL 源码解析》
※ 使用模板的目的就是能够让程序员编写与类型无关的代码。※
比如C++编写了一个交换两个int类型的swap函数,这个函数就只能实现int型,对double、char真相类型就无法实现,要实现这些类型的交换就要重新编写另一个swap函数。而使用模板的目的就是要让这些程序的实现与类型无关。比如让一个swap模板函数既可以实现int型,又可以实现double型的交换。
(模板是相对于编译器而言,顾名思义就是向编译器提供一个处理事务的模板,以后需要处理的东西,如果都是这个事务类型、那么统统可以用这个模板处理。)
模板编程是 STL 的基石,也是 c++11 的核心特性之一。
1.函数模板的基本语法如下:
template <typename/class T> (T为形参名,换个字母也可以)
模板函数即
template <typename/class 形参名,typename/class 形参名.....> 返回类型 函数名(参数列表)
{
函数体
}
其中template和class是关键字,class可以用typename 关键字代替,在这里typename 和class在使用上没有区别,<>括号中的参数叫模板形参,模板形参和函数形参很相像,模板形参不能为空。一但声明了模板函数就可以用模板函数的形参名声明类中的成员变量和成员函数,即可以在该函数中使用内置类型的地方都可以使用模板形参名。
※比如swap的模板函数形式为:
template <typename T> void swap(T& a, T& b){},
当调用这样的模板函数时类型T就会被被调用时的类型所代替,比如swap(a,b)其中a和b是int 型,这时模板函数swap中的形参T就会被int 所代替,模板函数就变为swap(int &a, int &b)。而当swap(c,d)其中c和d是double类型时,模板函数会被替换为swap(double &a, double &b) ;
例2:
template<tempname T>
T add(const T lva ,const T rva) //const修饰函数参数,传递过来的参数在函数内不可改变
{ T a; a = lva + rva;
return a; }
我们可以写出add(1,2) 这样的函数,也可以写出add(2.5,4.6)这样的函数,向 add 函数提供参数时,编译器会自动分析参数的类型,然后将所有用到 T 定义的换成相对性的类型;(※但如果我们使用add(1,2.0)是会报错的,因为编译器无法找到add(int,double) )
---------
2.类模板 基本语法:
template <class 形参名,class形参名,...> class 类名
{ ... };
例子:
1.
template <class T>
class Myclass
{
T a;
public :
T add(const T lva ,const T rva);
};
2.
template <class T>
T Myclass<T>::add(const T lva, const T rva)
{
a = lva + rva;
return a;
}
关于类模板的使用:类模板的使用实际上是将类模板实例化成一个具体的类
它的格式为:类名<实际的类型>
即,上面那个是个简单且经典的类模板,但在程序中给出的模板并不能使用它,还必须实例化出一个具体的类,
比如: Myclass<int> A; //用int实例化一个类A
Myclass<double> B; //用double实例化一个类B
模板类是类模板实例化后的一个产物,(说个具体点的例子吧,我们把类模板比作是一个做饼干的模子,而模板类就是用这个模子做出来的饼干,至于这个饼干是什么味道的就要看你自己在实例化时用的是什么材料了,你可以做巧克力饼干,也可以做牛奶饼干,这些饼干出了材料不一样外,其它的东西都是一样的了。)
--------------------------------
3.成员模板
模板的使用范围是广泛的,不仅可以用作函数模板,类模板,还可以用作 class ,struct ,template class 的成员。而要实现 STL 这是我们必须掌握和使用的特性。我们先看一个简单的例子,用上面的类改编而来:
template <class T>
class Myclass
{
public :
T a;
template <typename type_1, typename type_2> //
type_1 add(const type_1 lva ,const type_2 rva); //成员作模板
};
template <class T>
template <typename type_1 ,typename type_2>
type_1 Myclass<T>::add(const type_1 lva, const type_2 rva)
{
a = lva + rva;
return a;
}
在类的声明中使用了一个嵌套的模板声明。且通过一个作用域运算符::指出add是类的成员。
(需要注意的一点,有些编译器不支持模板成员,而有些编译器不支持在类外定义。我们默认大家的编译器都支持。模板如此强大,甚至能够允许我们在模板类中再建立模板类)
--------------
4.模板中的静态成员
我们知道,在类中定义的静态成员是存储在静态区中,被所有类对象共享,并不属于某一个类所有,同样的,在模板类中的静态成员也不会被复制多份,而是被同类实例化的类对象共享,比如所有 int 和所有 double 的类对象,享有相互独立的静态变量。也可以说是编译器生成了 int 和 double 两个版本的类定义。
----------
※ 5.关于 typename 和 class
typename和class是模板中经常使用的两个关键词 ,在模板定义的时候没有什么区别。以前用的是 class,后来 c++ 委员会加入了 typename。因为历史原因,两个是可以通用的。对有些程序员来说,在定义类模板的时候,常常使用 class 作为关键字,增加代码可读性。其它则用 typename,上面的代码大都遵循这样的标准,但是并无强制规定。
那么如果二者并没有差别,为什么还要再加入typename呢?因为在 c++ 中,允许我们在类中定义一个类型别名,且使用的时候和类名访问类成员的方法一样。这样编译器在编译的时候就会产生二义性,它根本不知道这是一个类型还是别名,所以我们加上 typename 显式说明出来。当然如果这里没有二义性,比如Myclass ::test * a ,加上 typename 是会报错的。(此外,在 class 的 STL 底层还有一个特性,用于保留模板参数,但是在 c++17 中已经舍弃。)