C++学习笔记(第14章->代码重用->类模板)
2014-11-30 13:54
363 查看
1.定义类模板
模板提供参数化类型,即能够将类型名作为参数传递给接收方来建立类或函数.模板类如:valarray.template<class Type> or template<typename Type>template关键字告诉编译器要定义一个模板,尖括号内容相当于函数的参数列表,class看作类型名.
#ifndef _STACKTP_H_ #define _STACKTP_H_ template<class Type> class Stack { private: enum{MAX = 10}; Type items[MAX]; int top; public: Stack(); bool isEmpty(); bool isfull(); bool push(const Type & item); bool pop(Type & item); }; template<class Type> Stack<Type>::Stack() { top = 0; } template<class Type> bool Stack<Type>::isEmpty() { return top == 0; } template<class Type> bool Stack<Type>::isfull() { return top == MAX; } template<class Type> bool Stack<Type>::push(const Type & item) { if(top < MAX) { items[top++] = item; return true; } else return false; } template<class Type> bool Stack<Type>::pop(Type & item) { if(top > 0) { item = items[--top]; return true; } else return false; } #endif以上,定义一个Stack模板类,模板方法也定义在此文件中.假如,模板方法要在另一个独立的cpp文件中定义,则需要在模板类声明时加关键字:export.
export template<class Type> class Stack { ... };
2.使用模板类
#include<iostream> #include<string> #include<cctype> #include"stacktp.h" using namespace std; int main() { Stack<string> st; char ch; string po; cout<<"please enter a to add a purchase order, \n"<<"p to process a po or Q to quit!\n"; while(cin>>ch && toupper(ch) != 'Q') { while(cin.get() != '\n') continue; if(!isalpha(ch)) { cout<<'\a'; continue; } switch(ch) { case 'A': case 'a': cout<<"enter a po number to add:"; cin>>po; if(st.isfull()) cout<<"stack already is full"; else st.push(po); break; case 'P': case 'p': if(st.isEmpty()) cout<<"stack already empty;"; else { st.pop(po); cout<<"po # "<<po<< " popped\n"; } break; } cout<<"please enter a to add a purachse order,\n p to process a po or q to quit.\n"; } cout<<"bye"; while(1); return 0; }
3.深入探讨模板类
(1)不正确的使用指针堆栈,牢记这3个反例,均以以上的例子为参考修改!版本1:
Stack<char *> st; char * po;这里通过po指针来接受键盘输入,但是failed,因为仅仅创建指针,没有用于保存输入字符串的空间.cin试图将输入保存至某些不合适的内存单元是奔溃.(Run-Time Check Failure #3 - The variable 'po' is being used without being initialized.)
版本2:
char po[40];
po类型为数组名,这将与pop(Type item)方法冲突,因为item = items[--top];item是数组名,不能被赋值.同时,也不能为数组名赋值.
版本3:
char * po = new char[100];
这里为输入分配了内存空间,po可以于pop()兼容了.但问题又来了,每次压入stack的内存单元总是相同的,因此,对堆栈弹出操作时,得到的地址总是相同的,他总是指向读入的最后一个字符串,具体地说,堆栈并没有保存每一个新字符串.
(2)正确使用堆栈指针
方法之一是,让调用程序提供一个指针数组,其中每个指针都指向不同的字符串.堆栈的任务是管理指针,而不是创建指针.
#ifndef _STACKTP_H_ #define _STACKTP_H_ template<class Type> class Stack { private: enum{SIZE = 10}; //Type items[SIZE]; int stacksize; Type * items; int top; public: explicit Stack(int ss = SIZE); Stack(const Stack & st); ~Stack(){delete [] items;} //delete保存指针的数组. bool isEmpty(); bool isfull(); bool push(const Type & item); bool pop(Type & item); Stack & operator = (const Stack & st); }; template<class Type> bool Stack<Type>::isEmpty() { return top == 0; } template<class Type> bool Stack<Type>::isfull() { return top == stacksize; } template<class Type> bool Stack<Type>::push(const Type & item) { if(top < stacksize) { items[top++] = item; return true; } else return false; } template<class Type> bool Stack<Type>::pop(Type & item) { if(top > 0) { item = items[--top]; return true; } else return false; } template<class Type> Stack<Type>::Stack(int ss):stacksize(ss),top(0) { items = new Type[stacksize]; //new 一个保存指针的数组,析构的时候必须delete该数组 } template<class Type> Stack<Type>::Stack(const Stack & st) { stacksize = st.stacksize; top = st.top; items = new Type[stacksize]; for(int i = 0; i < stacksize; i++) items[i] = st.items[i]; } template<class Type> Stack<Type> & Stack<Type>::operator = (const Stack<Type> & st) { if(this == &st) return *this; delete [] items; stacksize = st.stacksize; top = st.top; items = new Type[stacksize]; for(int i = 0; i < stacksize; i++) items[i] = st.items[i]; return *this; } #endif调用程序:
#include<iostream> #include<cstdlib> #include<ctime> #include"stacktp.h" const int Num = 10; using namespace std; int main() { srand(time(0)); cout<<"please enter the stacksize:"; int stacksize; cin>>stacksize; Stack<const char*> st(stacksize); const char * in[Num] = { "1:hongzong.lin", "2:dizong.lin", "3:tangzong", "4:bingzai.lin", "5:chuansheng.chen", "6:chunzong.lin", "7:youzong.yang", "8:qingzong.wang", "9:xiongzong.yu", "10:haizong.shi" }; const char* out[Num]; int processed = 0; int nextin = 0; while(processed < Num) { if(st.isEmpty()) st.push(in[nextin++]); else if(st.isfull()) st.pop(out[processed++]); else if(rand()%2 && nextin < Num) st.push(in[nextin++]); else st.pop(out[processed++]); } for(int i = 0; i < Num; i++) cout<<out[i]<<endl; cout<<"bye!"; while(1); return 0; }以上,把字符串压入栈,其实是新建一个指向该字符串的指针,栈保存的是这个指针,而非字符串本身.出栈即把地址复制到out指针数组中.
4.数组模板范例和非类型参数
模板通常被用作容器类,这是因为类型参数的概念非常适合于将相同的存储方案用于不同的类型.下面来实现一个允许指定数组大小的简单数组模板.#ifndef _ARRAYTP_H_ #define _ARRAYTP_H_ #include<iostream> #include<cstdlib> template<class T, int n> class ArrayTP { private: T ar ; public: ArrayTP(){}; explict ArrayTP(const T & v); virtual T & operator[](int i); virtual T operator[](int i)const; }; template<class T,int n> ArrayTP<T,n>::ArrayTP(const T & v) { for(int i = 0; i < n; i++) { ar[i] = v; } } template<class T, int n> T& ArrayTP<T,n>::operator[](int i) { if(i < 0 || i >= n) { std::cerr<<"Error in array limits: "<<i<<" is out of range\n"; std::exit(EXIT_FAILURE); } return ar[i]; } template<class T, int n> T ArrayTP<T, n>::operator[](int i) { if(i < 0 || i >= n) { std::cerr<<"Error in array limits: "<<i<<" is out of range\n"; std::exit(EXIT_FAILURE); } return ar[i]; } #endif与Stack中使用的构造函数相比,这种改变数组大小的方法有一个优点,构造函数使用的是new和delete管理堆内存,而表达式参数方法使用的是自动变量维护的内存栈,这样运行速度将更快.
表达式参数方法的主要缺点是,每种数组大小都将生成自己的类模板.Stack只生成一个类,另一个区别是,Stack构造函数方法更通用,因为数组大小是作为类成员存储在定义中.
5模板的多功能性
模板类可以用作基类,也可以用作组件类,还可以用作其他模板的类型参数。template<class Type> class GrowArray:public ArrayTP<Type>{...};//inhertitance template<class Type> class Stack { ArrayTP<Type> ar;//use an ArrayTP<> as a component }; ArrayTP<Stack<int> > asi; //an array of stack of int
5.1 递归使用模板
ArrayTP<ArrayTP<int, 5>, 10> twodee;其实和二维数组等价,int twodee[10][5];
5.2 使用多个类型参数
#include<iostream> #include<string> template<class T1, class T2> class Pair { private: T1 a; T2 b; public: T1 & first(); T2 & second(); T1 first()const; T2 second()const; Pair(const T1 & aval, const T2 & bval):a(aval),b(bval){} Pair(){} }; template<class T1, class T2> T1 & Pair<T1, T2>::first() { return a; } template<class T1, class T2> T2 & Pair<T1, T2>::second() { return b; } template<class T1, class T2> T1 Pair<T1, T2>::first()const { return a; } template<class T1, class T2> T2 Pair<T1, T2>::second()const { return b; } int main() { using namespace std; Pair<string, int> ratings[4]={ Pair<string, int>("dizong.lin", 1), Pair<string, int>("hongzong.lin", 2), Pair<string, int>("tangzong.lin", 3), Pair<string, int>("bingzong.lin", 4), }; int joints = sizeof(ratings) / sizeof(Pair<string, int>); cout<<"Rating:\t Eatery\n"; for(int i = 0; i < joints; i++) { cout<<ratings[i].second()<<endl; cout<<ratings[i].first()<<endl; } cout<<"oops!,receive ratings"<<endl; ratings[3].first() = "chengzong"; ratings[3].second() = 6; cout<<ratings[3].second()<<endl; cout<<ratings[3].first()<<endl; return 0; }
5.3 默认类型模板参数
可以为模板的类型参数提供默认值:template<class T1, class T2 = int> class Topo{....};在T2没有指定的情况下,将自动设置类型为int型
6. 模板的具体化
类模板和函数模板十分相似,具体化(specialization)可分为隐式实例化、显示实例化、显示具体化。6.1 隐式实例化
目前为止,前面所有模板范例都是隐式实例化(implicit instatiation),即他们声明一个或多个对象,指出所需类型。ArrayTP<int, 10>stuff;
编译器在需要对象之前,不会生成类的隐式实例化。
6.2 显示实例化
当使用关键字template并指出所需类型来声明类时,编译器将生成类声明的显示实例化(explicit instantiation)。声明必须位于模板定义所在的名称空间中。下面声明:template class ArrayTP<string, 100>;
将ArrayTPM<string, 100>声明为一个类,虽然没有创建或提及类对象,编译器也将生成类声明。
6.3 显示具体化
显示具体化(explicit specialization)是特定类型的定义。具体化类模板定义的格式如下:template<> class Classname<specialized-type-name>{...};那么,什么情况下使用具体化呢?假设我们定义了一个模板:
template<class T> class SortedArray { ....//detailed };比如模板使用>对值进行比较,对于int型,这管用。假如T是有char * 表示的字符串,那么这将不管用,必须采用strcmp来进行比较。这个时候,我们就可以提供一个显示模板具体化,专门给char*类型使用:
template<> class SortedArrat<char *> { ....//detailed };
所以,当传进去的参数是char*时,将会自动匹配这个具体化模板,其余的类型还是采用通用的模板定义。
SortedArray<int> scores;//use gerneral defination SortedArray<char *>dates;//use specialized defination
6.4 部分具体化
C++允许部分具体化(partial specialization),即部分限制模板的通用性。template<class T1, class T2> class Pair{...};//general template template<class T1>class Pair<T1, int>{...};//specialization with T2 set to int假设<>为空,那么已经是显示具体化了:
template<>class Pair<int, int>{...};如果有多个模板选择,编译器将使用具体化程度最高的模板:
Pair<double, double>p1;//use general Pair template Pair<double, int>p2;//use Pair<T1, int>partial specialization Pair<int, int>p3;//use Pait<int, int>explicit specializaion
7.成员模板
模板可以用作结构、类或模板类的成员,要完全实现STL设计,必须使用这项特性。#include<iostream> using namespace std; template<class T> class beta { private: //start:nested template class member template<class V> class hold { private: V val; public: hold(V v = 0):val(v){} void show()const{cout<<val<<endl;} V Value()const{return val;} }; //end:nested template class member hold<T> q; hold<int> n; public: beta(T t, int i):q(t),n(i){} //template function template<class U> U blab(U u, T t){return (n.Value() + q.Value())*u/t;} //template function void show()const{q.show();n.show();} }; int main() { beta<double> guy(3.5, 3); guy.show(); cout<<guy.blab(10, 2.3)<<endl; cout<<"done\n"; return 0; }假设要在外面定义嵌套类和嵌套类的方法,则必须按下面格式定义:
template<class T> template<class V> class hold{...};
8.将模板用作参数
模板可以包含类型参数和非类型参数,同时,还可以包含本身就是模板的参数。用于实现STL。来看一个例子:template<template<class T> class Thing> class Crab{ .... };以上,模板参数是template<class T> class Thing,其中template<class T> class 是类型,Thing是参数。假如用Stack模板类作为参数,则声明如下:
Grab<Stack> s;
9.模板类和友元
模板类声明也可以有友元,分为3类:(1)非模板友元
(2)约束(bound)模板友元,即友元的类型取决于类被实例化时的类型。
(3)非约束(unbound)模板友元,即友元的所有具体化都是类的每一个具体化的友元。
9.1模板类的非模板友元
template<class T> class HasFriend { friend void counts();//非模板友元 friend void report(HasFriend<T> &);//bound template friend,必须指定T类型 };
#include<iostream> using namespace std; template<class T> class HasFriend { private: T item; static int ct; public: HasFriend(const T & i):item(i){ct++;} ~HasFriend(){ct--;} friend void counts(); friend void report(HasFriend<T> & ); }; template<class T> int HasFriend<T>::ct = 0; //non-template friend to all HasFriend<T> class void counts() { cout<<"int count:"<<HasFriend<int>::ct; cout<<"double count"<<HasFriend<double>::ct; } //non-template friend to all the HasFriend<int> classes void report(HasFriend<int> & hf) { cout<<"HasFriend<int>::item = "<<hf.item<<endl; } //non-template friend to all the HasFriend<int> classes void report(HasFriend<double> & hf) { cout<<"HasFriend<double>::item = "<<hf.item<<endl; } int main() { HasFriend<int> hfi1(10); HasFriend<int> hfi2(11); HasFriend<double> hfi3(10.5); counts(); report(hfi1); report(hfi2); report(hfi3); return 0; }以上,report定义两个,一个为int,一个为double,这样显示具体化,才能匹配到。
9.2模板类的约束模板友元函数
修改前一个范例,使友元函数成为模板。首先,在类定义面前声明每个模板函数template<class T> void counts(); template<class T> void report(T &);然后在函数中,再次将模板声明为友元。
template<class TT> class HasFriend { friend void counts<TT>(); friend void report<>(HasFriend<TT> &); };如下所示,其中测试程序不贴了:
//template prototypes
template<class T> void counts(); template<class T> void report(T &);
template<class TT>
class HasFriend
{
private:
TT item;
static int ct;
public:
HasFriend(const TT & tt):item(tt){ct++;}
!HasFriend(){ct--;}
friend void counts<TT>();
friend void report<>(HasFriend<TT> &);
};
template<class TT>
int HasFriend<TT>::ct = 0;
//template friend functions definations
template<class T>
void counts()
{
cout<<"template counts():"<<HasFriend<T>::ct<<endl;
}
template<class T>
void report(T & t)
{
cout<<t.item<<endl;
}
9.3模板类的非约束模板友元函数
前面9.2是在类外面声明的模板具体化。依此类推,通过在类内部声明模板,可以创建非约束友元函数,即每个函数具体化都是每个类具体化的友元。对于非约束友元,友元模板类型参数与模板类类型参数是不同的:template<class T> class ManyFriend { ... template<class C, class D> friend void show2(C & c, D & d); };他是所有ManyFriend具体化的友元。T=int, show2(ManyFriend<int>, ManyFriend<int>)
#include<iostream> using namespace std; template<class T> class ManyFriend { private: T item; public: ManyFriend(const T & t):item(t){} template<class C, class D> friend void show2(C & c, D & d); }; template<class C, class D> void show2(C & c, D & d) { cout<<c.item<<d.item<<endl; } int main() { ManyFriend<int> h1(10); ManyFriend<int> h2(12); ManyFriend<double> h3(1.2); show2(h1, h2); show2(h2, h3); return 0 ; }
相关文章推荐
- C++学习笔记(第14章->代码重用->包含,继承,多重继承,虚基类)
- <<精通iOS开发>>第14章例子代码小缺陷的修复
- <<精通iOS开发>>第14章例子代码彻底清除警告
- Control Study->AdRotator广告控件显示自定义配置文件中广告以及与DataGrid控件结合使用显示图片(示例代码下载)
- UML参考手册 第三部分 参 考 资 料 第14章. 标准元素 <一>
- 把textarea中字符串里含有的回车换行替换成<br>的javascript代码
- Control Study -> 自定义DataGrid翻页按钮(示例代码下载)
- Control Study -> 自定义DataGrid翻页控件(示例代码下载)
- Control Study -> 键盘模拟鼠标(实现用键盘操作鼠标光标)(示例代码下载)
- 把textarea中字符串里含有的回车换行替换成<br>的javascript代码
- Control Study -> 自定义DataGrid翻页按钮(示例代码下载)
- Control Study->AdRotator广告控件显示自定义配置文件中广告以及与DataGrid控件结合使用显示图片(示例代码下载)
- Control Study -> Asp.net几个较复杂的显示数据控件(Repeater/DataList/DataGrid)用法 (示例代码下载)
- 分布式编程->Remoting的一个代码示例(借助Remoting实现发送信息功能)
- Control Study -> 主表和子表数据同时在绑定控件显示(示例代码下载)
- 与(本地连接-->修复)功能相同的代码:修复本地连接.bat
- Control Study->AdRotator广告控件显示自定义配置文件中广告以及与DataGrid控件结合使用显示图片(示例代码下载)
- Control Study->AdRotator广告控件显示自定义配置文件中广告以及与DataGrid控件结合使用显示图片(示例代码下载)
- 网站源文件被注入了<iframe>代码―ARP欺骗的木马病毒攻击
- Control Study -> 自定义DataGrid翻页按钮(示例代码下载)