C++学习笔记(第14章->代码重用->类模板)

来源:互联网 发布:潘粤明和董洁 知乎 编辑:程序博客网 时间:2024/06/07 00:01

1.定义类模板

模板提供参数化类型,即能够将类型名作为参数传递给接收方来建立类或函数.模板类如:valarray.

template<class Type>     ortemplate<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";elsest.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[n];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>{...};//inhertitancetemplate<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 definationSortedArray<char *>dates;//use specialized defination

6.4 部分具体化

C++允许部分具体化(partial specialization),即部分限制模板的通用性。

template<class T1, class T2> class Pair{...};//general templatetemplate<class T1>class Pair<T1, int>{...};//specialization with T2 set to int
假设<>为空,那么已经是显示具体化了:

template<>class Pair<int, int>{...};
如果有多个模板选择,编译器将使用具体化程度最高的模板:

Pair<double, double>p1;//use general Pair templatePair<double, int>p2;//use Pair<T1, int>partial specializationPair<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 membertemplate<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 memberhold<T> q;hold<int> n;public:beta(T t, int i):q(t),n(i){}//template functiontemplate<class U>U blab(U u, T t){return (n.Value() + q.Value())*u/t;}//template functionvoid 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> classvoid counts(){cout<<"int count:"<<HasFriend<int>::ct;cout<<"double count"<<HasFriend<double>::ct;}//non-template friend to all the HasFriend<int> classesvoid report(HasFriend<int> & hf){cout<<"HasFriend<int>::item = "<<hf.item<<endl;}//non-template friend to all the HasFriend<int> classesvoid 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 prototypestemplate<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 definationstemplate<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 ;}

0 0
原创粉丝点击