引言:
所谓泛型程序就是以独立于任何特定类型的方式编写代码。使用泛型程序时,我们需要提供具体程序实例所操作的类型或值。
模板是泛型编程的基础。使用模板时可以无须了解模板的定义。
泛型编程与面向对象编程一样,都依赖于某种形式的多态性。面向对象编程中的多态性在运行时应用于存在继承关系的类。我们能够编写使用这些类的代码,忽略基类与派生类之间类型上的差异。只要使用基类的引用或指针,基类类型或派生类类型的对象就可以使用相同的代码。
在泛型编程中,我们所编写的类和函数能够多态地用于跨越编译时不相关的类型。一个类或一个函数可以用来操纵多种类型的对象。标准库中的容器、迭代器和算法是很好的泛型编程的例子。标准库用独立于类型的方式定义每个容器、迭代器和算法,因此几乎可以在任意类型上使用标准库的类和函数。
在C++中,模板是泛型编程的基础。模板是创建类或函数的蓝图或公式。
编写重载函数:
int compare(const string &v1,const string &v2) { if (v1 < v2) { return -1; } else if (v1 > v2) { return 1; } return 0; } int compare(const double &v1,const double &v2) { if (v1 < v2) { return -1; } else if (v1 > v2) { return 1; } return 0; }
这些函数几乎相同,它们之间唯一的区别是形参的类型,每个函数的函数体是相同的。
每个要比较的类型都需要重复函数的函数体,不仅麻烦而且容易出错。更重要的是,需要事先知道空间可能会比较哪些类型。如果希望将函数用于未知类型,这种策略就不起作用了。
一、定义函数模板
我们可以不用为每个类型定义一个新函数,而是定义一个函数模板。函数模板是一个独立于类型的函数,可以作为一种方式,产生函数的特定类型版本。
template<typename T> int compare(const T &v1,const T &v2) { if (v1 < v2) { return -1; } else if (v1 > v2) { return 1; } return 0; }
模板定义以关键字template开始,后接模板形参表,模板形参表是用尖括号括住的一个或多个模板形参的列表,形参之间以逗号分隔。而且模板形参表不能为空。
1、模板形参表
模板形参表类似于函数形参表,表示可以在类或函数的定义中使用的类型或值。例如,compare函数声明一个名为T的类型形参。在compare内部,可以使用名字T引用一个类型,T表示哪个实际类型由编译器根据所用的函数而确定。
模板形参可以是表示类型的类型形参,或者是表示常量表达式的非类型形参。类型形参跟在关键字class或typename之后定义。
2、使用函数模板
使用函数模板时,编译器会推断哪个(或哪些)模板实参绑定到模板形参。一旦编译器确定了实际的模板实参,就称它实例化了函数模板的一个实例。
推导出实际模板实参后,编译器使用实参代替相应的模板形参产生编译该版本的函数。
int main () { // 绑定到compare(const int&, const int&) cout << compare(1, 0) << endl; // 绑定到compare(const string&, const string&) string s1 = "hi", s2 = "world"; cout << compare(s1, s2) << endl; return 0; }
3、inline函数模板
inline说明符放在模板形参表之后、返回类型之前,不能放在关键字template之前。
template<typename T> inline int compare(const T &v1,const T &v2); //OK inline template<typename T> int compare(const T &v1,const T &v2); //Error
//P528 习题16.1 template<typename T> inline T abs(T val) { return val > 0 ? val : -val; } int main () { cout << abs(-1) << endl; cout << abs(-0.98) << endl; cout << abs(short(3.4)) << endl; return 0; }
//习题16.2 template<typename T> ostream &write(ostream &os,T val) { return os << val << endl; } int main() { write(cout,1); write(cout,12.3); write(cout,"Hello World"); ofstream outFile("output"); write(outFile,"Hello"); write(outFile,123); string temp; ostringstream strStream(temp); write(strStream,"Hello_World"); cout << strStream.str() << endl; }
二、定义类模板
为了举例说明类模板,我们将为标准库queue类实现一个自己的版本。
我们自定义的Queue类必须能够支持不同类型的对象,所以将它定义为类模板。Queue所能支持的操作:
1)push:在队尾添加一项
2)pop:从队头删除一项
3)front:返回队头的引用
4)empty:指出队列是否为空
template<typename Type> class Queue { public: Type &front(); const Type &front() const; void push(const Type &); void pop(); bool empty() const; private: //... };
类模板也是模板,因此必须以关键字template开头,后接模板形参表。
除了模板形参表外,类模板的定义看起来与任意其他类相似。在类和类成员的定义中,可以使用模板形参作为类型或值的占位符,在使用类时再提供那些类型或值。
使用类模板
与调用函数模板形成对比,使用类模板时,必须为模板形参显式指定实参:
Queue<int> qi; Queue< vector<double> > qc; Queue<string> qs;
编译器使用实参来实例化这个类的特定类型版本。实质上,编译器用用户提供的实际特定类型代替Type,重新编写Queue类。在这个例子中,编译器将实例化三个Queue类:第一个用int代替 Type,第二个用vector<double>代替 Type,第三个用string代替 Type。
//P529 习题16.5 template<typename T> T getBigger(const T &val1,const T &val2) { return val1 > val2 ? val1 : val2; }
//习题16.6 template<typename Type> class List { public: List(); void push_back(const Type &); void push_front(const Type &); std::size_t size() const; void insert(Type *ptr,const Type &val); bool empty(); private: //... };
三、模板形参
像函数形参一样,为模板形参选择的名字没有本质含义:
template<typename Glorp> int compare(const Glorp &v1,const Glorp &v2) { if (v1 < v2) { return -1; } else if (v1 > v2) { return 1; } return 0; }
该代码与前面定义的compare模板一样。
可以给模板形参授予的唯一含义是区别是类型形参还是非类型形参。如果是类型形参,我们就知道该形参表示未知类型,如果是非类型形参,我们就知道它是一个未知值。
如果希望使用模板形参所表示的类型或值,可以使用与对应模板形参相同的名字。例如,compare函数中所有的Glorp引用将在该函数被实例化时确定为同一类型。
1、模板形参作用域
模板形参的名字可以在声明为模板形参之后直到模板声明或定义的末尾处使用。
模板形参遵循常规名字屏蔽规则:
typedef double T; template <class T> T calc(const T &a,const T &b) { //此处T为template形参表中的T,全局名字被屏蔽 T tmp = a; //... return tmp; }
2、使用模板形参名字的限制
用作模板形参的名字不能在模板内部重用:
template <class T> T calc(const T &a,const T &b) { typedef double T; //Error T tmp = a; //... return tmp; }
这一限制还意味着模板形参的名字只能在同一模板形参表中使用一次:
template <class T,class T> T calc(const T &a,const T &b); //Error
正如可以重用函数形参名字一样,模板形参的名字也能在不同模板中重用:
template <class T> T calc(const T &a,const T &b); template <class T> int compare(const T &,const T&); //OK
3、模板声明
像其他任意函数或类一样,对于模板可以只声明而不定义。声明必须指出函数或类是一个模板:
template <class T> int compare(const T &,const T&);
同一模板的声明和定义中,模板形参的名字不必相同:
template <class T> T calc(const T &,const T &); template <typename U> U calc(const U&,const U&); template <class Type> Type calc(const Type &,const Type &);
每个模板类型形参前面必须带上关键字class或typename,每个非类型形参前面必须带上类型名字,省略关键字或类型说明符是错误的:
template<typename T,U> T calc(const T &,const U &); //Error template<typename T,class U> T calc(const T &,const U &); //OK
//P531 习题16.9 template <typename Type,typename T> Type find(Type begin,Type end,const T &val) { while (begin != end) { if (*begin == val) { return begin; } ++ begin; } return end; } int main() { int ia[] = {01,1,1,999,2,3,2,34,4,3,4}; int *p; if ((p = find(ia,ia+sizeof(ia)/sizeof(*ia),999)) != ia + sizeof(ia)/sizeof(*ia)) { cout << *p << endl; } else { cout << "Not Found!" << endl; } vector<int> iVec(ia,ia + sizeof(ia)/sizeof(*ia)); vector<int>::iterator iter; if ((iter = find(iVec.begin(),iVec.end(),888)) != iVec.end()) { cout << *iter << endl; } else { cout << "Not Found!" << endl; } ifstream inFile("input"); vector<string> strVec; string val; while (inFile >> val) { strVec.push_back(val); } vector<string>::iterator it; if ((it = find(strVec.begin(),strVec.end(),"hello")) != strVec.end()) { cout << *it << endl; } else { cout << "Not Found!" << endl; } }