函数模板使程序员能够用单段代码指定相关(重载)函数的全部范围,称为函数模板特殊化;
类模板使程序员能够用单段代码指定相关类的全部范围,称为类模板特殊化。
什么是泛型编程?
STL方法允许编写通用的程序,使得代码不依赖于底层的容器。这种编程风格被称为泛型编程。泛型编程以独立于任何特定类型的方式编写代码,泛型编程与面向对象编程一样,都依赖于某种形式的多态性。
函数模板特殊化和类模板特殊化,这种技术就是泛型编程。
重载函数通常用于对不同的数据类型执行相似的操作,不同数据类型的程序逻辑可能有所不同。如果每种数据类型的程序逻辑和操作都相同,则可以使用函数模板来更紧凑、更方便地实现函数重载。
就本质而言,定义一个函数模板就定义了一群重载函数。
所有的函数模板定义都从关键字template开始,后接它的模板参数表,列表位于一对尖括号(<和>)中。表示类型的每个模板参数,其前面都必须带关键字class或template(二者可以互换),例如:
template < typename T> 或
template < class ElementType> 或
template < typename BorderType, typename FillType>
函数模板定义中的类型模型参数,用于指定函数实参的类型。函数的返回类型或声明函数内部的变量。除此之外,函数定义与其他任何函数定义的形式相同。
下面看一个demo:printArray函数模板
#include <iostream> using namespace std; //定义函数模板 template <typename T> void printArray(const T* const array,int count) { for (int i=0; i<count; i++) { cout<<array[i]<<" "; } cout<<endl; }//函数模板定义结束 int _tmain(int argc, _TCHAR* argv[]) { const int ACOUNT = 5; const int BCOUNT = 7; const int CCOUNT = 6; int a[ACOUNT] = {1,2,3,4,5}; double b[BCOUNT] = {1.1,2.2,3.3,4.4,5.5,6.6,7.7}; char c[CCOUNT] = "HELLO"; //通过调用printArray输出每个数组 //编译器会使用它的重载解析功能找到与这个函数调用最为匹配的printArray函数定义 cout<<"Array a contains:"<<endl; printArray(a,ACOUNT); //用int*类型的第一个实参a调用 cout<<"Array b contains:"<<endl; printArray(b,BCOUNT); //用double*类型的第一个实参b调用 cout<<"Array c contains:"<<endl; printArray(c,CCOUNT); //用char*类型的第一个实参c调用 cout<<endl; system("pause"); return 0; }
运行结果:(这里HELLO后面多了一个a,不知道是什么原因,谁知道给我说下)
在这个例子中,采用模板机制使得不必像下面这样编写三个不同的重载函数的函数原型:
void printArray(const int*,int);
void printArray(const double*,int);
void printArray(const char*,int);
它们都使用了相同的代码,唯一的区别是类型T
类模板称为参数化类型,因为它需要一个或者多个类型参数来指定如何定制一个“泛型类”模板,以形成一个类模板特殊化。
为了产生各种类模板特殊化,只需编写一个类模板定义。每次需要一个额外的类模板特殊化时,只需要使用一种清晰、简单的方法,编译器就能为所需要的类模板特殊化编写源代码。
Demo:例如一个Stack类模板,可以是程序中创建许多Stack类的基础,如:"Stack of double"、"Stack of int"、"Stack of char"、"Stack of Employee"等等
创建类模板Stack<T>
stack.h :
#ifndef STACK_H #define STACK_H template <typename T> //指定一个使用参数类型T的类模板定义 class stack { public: stack(int = 10); //默认构造函数,栈大小为10 ~stack() { delete[] stackPtr; } bool push(const T &); //把一个函数压入栈 bool pop(T &); //从栈中取出一个函数 //判断栈是不是空 bool isEmpty() const { return top == -1; } //判断栈是不是满了 bool isFull() const { return top == size - 1; } private: int size; int top; T *stackPtr; }; //constructor template template <typename T> stack<T>::stack(int s):size(s>0 ? s:10),top(-1),stackPtr(new T[size]){} template<typename T> //出现在类模板定义之外的成员函数均以template<typename T> 开始 bool stack<T>::push(const T &pushvalue) { if ( !isFull() ) { stackPtr[++top] = pushvalue; return true; } return false; } template<typename T> bool stack<T>::pop(T &popvalue) { if ( !isEmpty()) { popvalue = stackPtr[top--]; return true; } return false; } #endif
main函数:
#include <iostream> using namespace std; #include "stack.h" //栈类模板定义 int _tmain(int argc, _TCHAR* argv[]) { stack<double> doubleStack(5); //实例化一个大小为5的doubleStack对象 double doubleValue = 1.1; cout<<"Pushing elements onto doubleStack\n"; while (doubleStack.push(doubleValue)) //将double值1.1,2.2,3.3,4.4和5.5压入doubleStack中 { cout<<doubleValue<<' '; doubleValue += 1.1; } cout<<"\nStack is full.Cannot push "<<doubleValue <<"\n\nPoping elements from doubleStack\n"; //pop elements from doubleStack //在一个while循环中调用pop函数,从堆栈中移走这5个值 while (doubleStack.pop(doubleValue)) { cout<<doubleValue<<' '; } cout<<"\nStack is empty,Cannot pop\n"; stack<int> intStack; //实例化了整数堆栈intStack,由于没有指定大小,因此它的大小就是默认构造函数,这里为10 int intValue = 1; cout<<"\nPushing elements onto intStack\n"; //循环调用push函数,将一些值压入intStack中,直到栈满 while (intStack.push(intValue)) { cout<<intValue++<<' '; } cout<<"\nStack is full.Cannot push "<<intValue <<"\n\nPoping elements from intStack\n"; //调用pop函数,将这些值从intStack中移走,直到栈空。 while (intStack.pop(intValue)) { cout<<intValue<<' '; } cout<<"\nStack is empty.Cannot pop"<<endl; system("pause"); return 0; }
运行结果:
参考资料:《c++程序员教程》 电子工业出版社 张良华等译 P151-153,P420-426