C++模板简介与使用

模板简介与使用

扯一扯模板以及简单的运用


1.模板概述

利用 C++ 模板,您可以定义可对不同类型的信息运行的一系列函数或类。 Microsoft C++ 模板的实现基于 ISO/ANSI C++ 标准。

在导致重复多个类型的同一代码的情况下应使用模板。 例如,可以使用函数模板创建将相同的算法应用于不同数据类型的函数集。 还可以使用类模板开发 typesafe 类集。 模板有时候是比 C 宏和 void 指针更好的解决方案,当与集合(MFC 中的模板的主要用途之一)和智能指针一起使用时,模板尤其有用。


2.模板适用情景

模板(有时称为参模板(有时称为参数化类型)是用于生成基于类型参数的函数和类的机制。通过使用模板,可以设计操作多种类型的数据的单个类或函数,而不必为每种类型创建单独的类。

例如,若要在不使用模板的情况下创建返回两个参数中的最小者的类型安全函数,请编写一组重载函数,如下所示:

// what_are_templates1.cpp
// compile with: /c
// min for ints
int min( int a, int b ) {
   return ( a < b ) ? a : b;
}

// min for longs
long min( long a, long b ) {
   return ( a < b ) ? a : b;
}

// min for chars
char min( char a, char b ) {
   return ( a < b ) ? a : b;
}
通过使用模板,可以减少对单个函数模板的重复:
// what_are_templates2.cpp
// compile with: /c
template  T min( T a, T b ) {
   return ( a < b ) ? a : b;
}

模板可以显著减小源代码的大小并提高代码的灵活性,而不会降低类型安全。


3.模板分类

模板有两大类型:函数模板和类模板。 在前面的示例中,以上所述的min 是函数模板。模板幻术举例, 如两个变量互换:
#include

template
void swap(T &a, T &b){
	T z;
	z = a;
	a = b;
	b = z;
}

int main(int argc, char** argv){
	int i = 3;
	int j = 5;
	std::cout << "i = " << i << "\n" << "j = " << j << std::endl;
	swap(i, j);
	std::cout << "i = " << i << "\n" << "j = " << j << std::endl;
}
类模板是一个带参数的类,例如:
// what_are_templates3.cpp
template  class A {
   T m_t;
   public:
      A(T t): m_t(t) {} 
      void f(T t);
};

int main() {
   A a(10);
}
声明和定义模板在某种程度上类似于其他函数和类,同时存在一些主要差异。模板声明不完全定义函数或类;它仅定义类或函数的语法主干。通过称作实例化的过程从模板中创建实际类或函数。创建的单个类或函数被引用为已实例化的。例如,类模板:
template  struct A { . . . };

可用于实例化 AAAA 等的类。

可以显式或隐式执行类或函数的实例化。显式实例化是一种对外调用代码中将生成模板的版本的方式。隐式实例化允许在第一次使用模板的位置根据需要实例化模板。

模板也可以由值参数进行参数化,在这种情况下,声明模板参数的方式与声明函数的参数的方式类似。禁止将浮点型和类类型作为值参数。

// what_are_templates4.cpp
// compile with: /EHsc
#include 
using namespace std;

template  class A {
   int array[i];
public:
   A() { memset(array, 0, i*sizeof(int)); }
};

int main() {
   A<10> a;
}
模板的常见问题是,它们可以是通用型解决方案,这意味着同一代码适用于所有类型。如果需要自定义特定类型的模板的行为,则可以使用专用化。通过使用显式专用化,模板可专用于特定的实际类型而不是泛型类型。也可以对类模板进行部分专用化,这在您的模板有多个类型参数且您只想自定义与该模板的部分而非所有参数相关的行为时会很有用。部分专用化仍为泛型且需要实际模板参数才能生成实际的实例化类。

4.模板规范

template 声明指定参数化类或函数的集

“模板参数列表” 是模板参数的逗号分隔的列表,它可能是在窗体“类”、“标识符”、“类型名称” “标示符” 或 模板<”模板参数列表“>类“标示符” 中的类型或在模板主体中使用的非类型参数。模板参数的语法是以下的其中一个:

parameter-declaration
class identifier [ = typename ] 
typename identifier [ = typename ]
template < template-parameter-list > class [identifier][= name]

必须实例化类模板,就像您要实例化普通类一样,但是在尖括号 (<>) 内必须包括模板参数。这些模板参数可以是任何类型,如果模板参数列表包含类或“类名称”关键字,如果该参数是非类型参数则为合适类型的值。不需要特殊语法调用函数模板,尽管,可能需要尖括号和模板参数,如果模板参数不能从参数推导到函数。

“模板参数列表” 是指定紧跟其后的代码哪一个部分将改变的模板函数所使用的参数列表。例如:

template< class T, int i > class MyStack...

在这种情况下,该模板可以接到类型 (class T) 和常量参数 (int i)。在实例化时,模板将使用类型 T 和常量整数 i在 MyStack 声明体内,您必须引用T 标识符。

模板声明本身不生成代码;它指定类或函数的系列以及当被其他代码引用时生成的一个或多个代码。

模板声明有全局、命名空间或类范围。它们不能在函数内声明。

以下示例阐释具有类型参数 T 和非类型模板参数 i 的类模板的声明、定义和实例化。

// template_specifications1.cpp
template  class TestClass 
{
public:
   char buffer[i];
   T testFunc(T* p1 );
};

template 
T TestClass::testFunc(T* p1) 
{
    return *(p1++)
};

// To create an instance of TestClass
TestClass ClassInst;
int main()
{
}

非类型模板参数: 非类型模板参数必须是整型、枚举、指针、引用或指向成员类型的指针,并且必须是在编译时的常量。 它们可以被限定为常数或可变类型。 浮点值不允许作为模板参数。 类、结构或联合类型的对象不允许作为非类型模板参数,尽管指向此类对象的指针是被允许的。 作为非类型模板参数传递的数组转换为指针。 作为非类型参数传递的函数被视为函数指针。 字符串文本不允许作为模板参数。

5.模板实例化

(1)显示实例化

(2)隐式实例化

(3)特化(=具体化)、偏特化

到底嘛是实例化:一个通过使用具体值替换模板参数,从模板产生的普通类,函数或者成员函数的过程。

显示实例化:通过名字可见,就是清楚的表明你要实例化的类型

隐式实例化:通过编译器自己推测判断要实例化的类型。

显示与隐式实例化的示例:

#include
#include

template
void swap(T &a, T &b){
	T z;
	z = a;
	a = b;
	b = z;
}

int main(int argc, char** argv){
	int i = 3;
	int j = 5;
	char m = '1';
	char n = '2';
	char s = '6';
	char t = '8';
	std::cout << "i = " << i << "\n" << "j = " << j << std::endl;
	std::cout << "m = " << m << "\n" << "n = " << n << std::endl;
	std::cout << "s = " << s << "\n" << "t = " << t << std::endl;
	swap(i, j);  //显示实例化
	swap(m, n); //显示实例化
	swap(s, t);       //隐式实例化
	std::cout << "i = " << i << "\n" << "j = " << j << std::endl;
	std::cout << "m = " << m << "\n" << "n = " << n << std::endl;
	std::cout << "s = " << s << "\n" << "t = " << t << std::endl;
	system("pause");

	return 0;
}

1)显示实例化:template  void swap();  // 无须给该函数重新编写函数体,这只是个声明

那么为什么要显示实例化? 

主要是提高效率,当显式实例化模板时,在使用模板之前,编译器根据显式实例化指定的类型生成模板实例,这样就相当于本程序里面有如下的内容:

void swap(int &a, int &b){
	T z;
	z = a;
	a = b;
	b = z;
}

2)隐式实例化

    隐式实例化指的是:在使用模板之前,编译器不生成模板的声明和定义实例。只有当使用模板时,编译器才根据模板定义生成相应类型的实例。

   int i=3, j=5;
   swap(i, j); //编译器根据参数i,j的类型隐式地生成swap(int &a, int &b)的函数定义。

   隐式实例化就是程序员为了省事,把类型省略让编译器判断,这是一个偷懒的表现吧。

3)特化(=具体化)

     然而通常又有一些特殊的情况,不能直接使用泛型模板展开实现,这时就需要针对某个特殊的类型或者是某一类特殊的类型,而实现一个特例模板————即模板特化

    当T如果为 一个 struct类型的,它的交换就无法进行,所以我们针对这种特殊的情形,专门写了一个函数,只有当T为这种struct类型时候,才会调用这个特化的函数

偏特化,你又是个嘛

    模板的偏特化是指需要根据模板的部分参数进行特化。

a. 类模板的偏特化

例如c++标准库中的类vector的定义

template 
class vector { // … // };
template 
class vector { //…//};
//这个偏特化的例子中,一个参数被绑定到bool类型,而另一个参数仍需要由用户使用时指定。
b. 函数模板的偏特化

从严格意义上讲,函数模板并不支持偏特化,但由于可以对函数进行重载,所以可以达到类似于类模板偏特化的效果。
比如:
a) template void f(T); 
    根据重载规则,对a)进行重载
b) template < class T> void f(T*); 

    如果将a)称为基模板,那么b)称为对基模板a)的重载,而非对a)的偏特化。


6.模板的匹配顺序

1. 类模板的匹配规则

例如:
template class vector{//…//}; // (a) 普通型
template class vector ; // (b) 的显式实例化
template class vector{//…//}; // (c) 对指针类型特化
template <> class vector {//…//}; // (d) 对void*进行特化
每个类型都可以用作普通型(a)的参数,但只有指针类型才能用作(b)的参数,而只有void*才能作为(c)的参数

所以,当一个调用一个模板类,首先,找显式实例化的,如果不匹配;接着,找特化的,然后,找偏特化的,最后,根据模板隐式实例化

2.函数模板的匹配规则

例如:

void swap(int &a, int &b){} // 普通的函数
template<> swap(int &a, int &b){} // 特化的模板函数
template void swap(int &a, int &b); // 显式实例化,这个只用声明就行
template void swap(T &a, T &b){} // 模板

以上书写的顺序就是模板的调用顺序。

参考资料:

https://msdn.microsoft.com/zh-cn/library/y097fkab.aspx

https://blog.csdn.net/chenyiming_1990/article/details/10526371

你可能感兴趣的:(C/C++)