【c++】类模板

以下主要实例都来自来自《c++ primer plus》

定义和使用模板类

#ifndef STACK_H
#define STACK_H
using std::string ;
using std::cout;
using std::endl;
template <typename T>
class Stack{
    private :
        enum{MAX=10};
        T items[MAX];
        int top;
    public :
        Stack();
        bool isempty();
        bool isfull();
        bool push(const T& item);
        bool pop(T& item);
};
template<typename T>
Stack<T>::Stack(){
    top=0;
}
template<typename T>
bool Stack<T>::isempty(){
    return top==0;  
} 
template<typename T>
bool Stack<T>::isfull(){
    return top==MAX;
}
template<typename T>
bool Stack<T>::push(const T& item){
    if(top<MAX){
        items[top++]=item;
        return true;
    }
    else 
        return false;

}
template<typename T>
bool Stack<T>::pop(T& item){
    if(top>0){
        item=items[--top];
        return true;
    }
    else
        return false;
}

#endif

和模板函数很像定义和使用时都要加上
template<typename T>
在使用模板累的时候一定要指定其类型

Stack<int> stack;
Stack<int> stack=new Stack<int>();

小心使用模板类

不正确使用指针栈

首先我们通过上面的Stack类来存储string 类型订单信息。

#include <iostream>
#include<cstring>
#include<cctype>
#include"stack.h"
using std::string ;
using std::cout;
using std::cin;
using std::endl;

int main(int argc, char** argv) {

    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&&std::toupper(ch)!='Q'){
        while (cin.get()!='\n')
            continue;
        if(!std::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 full\n";
                else
                st.push(po);
                break;
            case 'P':
            case 'p':
                if(st.isempty())
                cout<<"stack already empty\n";
                else{
                    st.pop(po);
                    cout<<"PO # "<<po<<" popped\n";
                }

                break;

        }
        cout<<"Please enter A to add a purchase order.\n"
            <<"P to process a PO ,or Q to quit.\n"; 
    }   


    cout<<"Bye\n";

    return 0;
}

注意事项1:
首先如果我们把

Stack<sting> st;
string po

改为

Stack<char *> st;
char *po

会怎么样呢?
因为我们没有为指针定义存储的空间当运行到cin>>po时程序会奔溃

注意事项1:
首先如果我们把

Stack<sting> st;
string po

改为

Stack<char *> st;
char po[40]

会怎么样呢?
这样po 是char * 类型的满足Stack < char * > 。
而且也有内存空间可以存储字符串。但是数组又会和pop方法出现了冲突,例如

template<typename T>
bool Stack<T>::pop(T& item){
    if(top>0){
        item=items[--top];
        return true;
    }
    else
        return false;

}

首先引用一定是左值,而数组名是一个常量,不能赋值给他,其次
item=items[--top];它给数组名(一个常量)赋值了。之显然也是错误的。

注意事项3:
首先如果我们把

Stack<sting> st;
string po

改为

Stack<char *> st;
char 8 po=new char[40]?

会怎么样呢?
好像没问题了。po也可写入数据了,也不会和pop方法冲突了。但是好像还有问题,因为所有的压栈和出栈都是对同一个指针进行操作。所以栈中所有的指针都是指向最后写进来的数据。

正确使用指针栈

某人将一车文件交给Plodson,如果Plodson的收取篮(in-basket)是空的,就会把车中最上层的文件放入收取蓝中,如果收取蓝是满的,将把收取蓝最上层的文件拿出来处理放入发出蓝中,如果收取蓝即不是空的也不是满的,就会扔硬币的方式来决定文件的去和留下来

#ifndef STACKP_H
#define STACKP_H


using std::string ;
using std::cout;
using std::endl;
template <typename T>
class Stack{
    private :
        enum{SIZE=10};

        int stacksize;
        T *items;
        int top;
    public :
        explicit Stack(int ss=SIZE);
        Stack(const Stack &);
        ~Stack(){delete [] items;} 
        Stack & operator=(const Stack & st);//返回类型Stack 和Stack<T>一样的 
        bool isempty(){return top==0;}
        bool isfull(){return top==stacksize;}
        bool push(const T& item);
        bool pop(T& item);
};

template<typename T>
Stack<T>::Stack(int ss):stacksize(ss),top(0){
    items=new T [stacksize];
}
template<typename T>
Stack<T>::Stack(const Stack & st){
    stacksize=st.stacksize;
    top=st.top;
    items=new T [stacksize];
    for(int i=0;i<top;i++)
    items[i]=st.stems[i];

}
template<typename T>
Stack<T>& Stack<T>::operator=(const Stack<T> & st){
    if(this==&st)
    return *this;
    delete [] items;
    stacksize=st.stacksize;
    top=st.top;
    items=new T [stacksize];
    for(int i=0;i<top;i++)
    items[i]=st.stems[i];
    return *this;
}

template<typename T>
bool Stack<T>::push(const T& item){
    if(top<stacksize){
        items[top++]=item;
        return true;
    }
    else 
        return false;

}
template<typename T>
bool Stack<T>::pop(T& item){
    if(top>0){
        item=items[-
-top];
        return true;
    }
    else
        return false;
}

#endif

#include <iostream>
#include<cstdlib>//for rand(),srand()
#include<ctime>//for time()
#include"stackp.h"
using std::string ;
using std::cout;
using std::cin;
using std::endl;

const int Num=10;

int main(int argc, char** argv) {

    std::srand(std::time(0));//randomize rand();
    std::cout<<"please enter stack size ";
    int stacksize;
    std::cin>>stacksize;
    //data
    const char * in[Num]={
        "1:Hank Gilgamesh"
        ,"2:Kiki Ishtar"
        ,"3:Betty Rocker"
        ,"4:Ian Flagranti"
        ,"5:Wolfgang Kibble"
        ,"6:Portia Koop"
        ,"7:Joy Kibble"
        ,"8:Xaverie Paprika"
        ,"9:Juan Moore"
        ,"10:Misha Mache"
    };
    //creat an empty stack with stacksize slots
    //in basket
    Stack<const char *>st(stacksize);
    //out basket
    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(std::rand()%2&&nextin<Num)
            st.push(in[nextin++]);
        else
        st.pop(out[processed++]);   
    }   
    for(int i=0;i<Num;i++)
        cout<<out[i]<<std::endl;
    cout<<"Bye\n";
    return 0;
}

数组模板示例和非类型参数

#ifndef ArrayT_H
#define ArrayT_H
#include<iostream>
#include<cstdlib>
template<typename T,int n>
class ArrayT{
    private :
        T ar[n];
    public :
        ArrayT(){};
        explicit ArrayT(const T& v);
        virtual T & operator[](int i);
        virtual T operator[](int i)const;

};

template <typename T,int n>
ArrayT<T,n>::ArrayT(const T& v){

    for(int i=0;i<n;i++)
        ar[i]=v;

}
template<typename T,int n>
T& ArrayT<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 <typename T,int n>
T ArrayT<T,n>::operator[](int i)const
{
    if(i<0||i>=n){
        std::cerr<<"Error in array limits:"
            <<"is out of range\n";
        std::exit(EXIT_FAILU
RE);
    }
    return ar[i];
}




#endif

template<typename T,int n>
上述T指出类型参数,int指出n的类型为int。这种参数(指定特殊类型而不用作泛型名)称为非类型(non-type)或表达式(expression)参数
非类型参数有一些限制

  • 表达式参数可以为整型,枚举,引用和指针
  • 模板代码不能修改参数的值
  • 实例模板使,表达式参数的值必须为常量表达式

对于上面的模板创建一个实例ArrayT<double ,12> arr编译器会用double代替T使用12代替n。但是如果我们给n传递一个变量m,如`ArrayT

ArrayT <double ,19> ar1;
ArrayT<double 20>ar2;

上面会生成两个模板实例

Stack<double > st1;
Stack<double> st2;

上面只会生成一个模板实例。

在实际使用中我们的Stack类还是比ArrayT类更加通用,因为Stack使用的是一个指针,可以指向不同大小的数组,方便更改。

模板多功能性

模板类可以被用作基类,组件类,还可以用与其他模板的类型参数

template <typename T>
class Array{
private :
T entry;
...
}
template <typebane Type>
class GrowArray:public Array<Type>//inheritance

template<typename Tp>
class Stack{
    Array<Tp> ar; //a component
    ...
}
...
Array <Stack<int>> asi;//argument

Note:
最后一条语句再c++98中要> >中间要有一个空格以区分运算符>>但是c++11中就不需要了

递归使用模板

对于ArrayT我们可以递归使用它,如
ArrayT<ArrayT<int,5>,10> twoart
与之定价的常规数组声明为
int twoart [10][5]

默认类型模板参数

template<typename T1,typename T2=int>
class A{
……
}
A<double,double>a1
A<double >a2//T1 double,T2 int

Note:

  • 模板类型参数默认值,函数模板没有这样的功能
  • 表达式参数可以提供默认值,这对于类模板和函数模板都适用

模板具体化

1 隐式实例化
Stack<char *> st
编译器使用通用模板来生成具体的类定义

Stack<char *> * p
p=new  Stack<char *> ;

编译器在需要对象之前是不毁创建对象的。如上面第一条语句是不会创建对象的,而第二条会创建对象。

2显式实例化
template class Stack<char * >;
使用关键字template来显式实例化类定义,
虽然没有创建对象,但是编译器会生成类的声明
3显式具体化

template<>class Stack<char*>
{
...
}

使用关键字template<>,虽然没有创建对象,但是编译器会在使用在使用时生成类的声明,
而且调用顺序和函数模板一样。具体类优先于显式具体化模板优先于通用模板

Tips::
早期的格式为


class Stack<char*>

{

...

}

这种格式是没有关键字template<>

部分具体化模板

template <typename T1,typename T2> class A{}//模板
template <typename T1> class A<T1,int>{}//部分具体化模板

Note:
注意具体化的部分要从template<>参数中移除,并加入后面的<>中

成员模板

模板可以作为结构,类和模板类的成员

template<typename T>
class A{
    private :
        template<typename V>
        class B{
            private:
                V val;
            public :
            B(V v=0):val(v){};
            void show const {cout<<va<<endl;}
            V value() const {return val;}
        };
        B<T>q;
        B<int> n;
    public:
        A(T t,int i):q(t),n(i){}        
        template<typenam U>
        U blab(U u ,T t){return (n.Value()+q.Value())*u/t;}
        void Show() const{q.show();n.show);}

}; 

代码为定义如下

template<typename T>
    class A{
    private :
        template<typename V>
        class B;
        B<T>q;
        B<int> n;
    public:
        A(T t,int i):q(t),n(i){}        
        template<typenam U>
        U blab(U u ,T t);
        void Show() const{q.show();n.show();}

    }; 
template<typename T>
    template<typename V>
    class A<T>::B{

        private:

            V val;

        public :    
        B(V v=0):val(v){};  
        void show const {cout<<va<<endl;}   
        V value() const {return val;}

    };


template<typename T>
template<typename U>
    U A<T>::blab(U u ,T t)
    {
    return (n.Value()+q.Value())*u/t;
    }

模板参数

template<template <typename T> class A>
class B{
private :
A<int> a1;
A<double> a2;

}

如果有一个别的模板类C,它的类型和上面声明的(templateclass ) 完全一样

template<typename V>
class C{

}

那么我们就可以把它当作B的模板参数了

B<C> b1;

这样B中的

A<int> a1;
A<double> a2;

就会被替换为

C<int> a1;
C<double> a2;

模板和友元

模板类的有源分为三类
- 非模板友元
- 约束(bound)模板友元,即友元类型取决于类被实例时的类型
- 非约束模板友元,即友元的所有具体化都是类的每一个具体化的友元

非模板友元

template<typename T>
class HasFriend{
    public :
    friend void counts();
}

couts函数将会时模板所有实例的友元,如count函数将会是Hasfriend,Hasfriend的友元。那我们能不能这样呢
friend void couts(HasFriend &)
答案是不可以。因为根本不存在HasFriend这个类型。不过我们可以这样

template<typename T>
class HasFriend{
    public :
    friend void report(HasFriend<T>&);


}

当我们隐式实例化的时候
HasFriend<int> hf
的时候编译器会用int来替换T,原先的式子就会变成这样

class HasFriend{
    public :
    friend void report(HasFriend<int>&);
}

因为友元函数couts不是模板类的成员,所以我们必须在外面定义一个接受HasFriend&参数的report函数,同样的如果需要HasFriend&,HasFriend&版本的report也都需要独立定义。

Note:
因为我们定义的模板时是没有实例的,那我们的友元函数怎么访问模板生成的类,类生成的对象呢。但是它可以访问如下

  • 全局对象(全局指针可以访问非全局对象)
  • 可以创建自己的对象
  • 可以访问独立与模板类的静态变量
using std::endl;
using std::cout;

template<typename 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<typename T>
int HasFriend<T>::ct=0;

void counts(){
    cout<<"int counts:"<<HasFriend<int>::ct<<";";
    cout<<"double counts:"<<HasFriend<double>::ct<<endl;

}

void report(HasFriend<int> & hf){
    cout<<"HasFriend<int>:"<<hf.item<<endl;

}

void report(HasFriend<double> & hf){
    cout<<"HasFriend<double>:"<<hf.item<<endl;

}

int main(){

    cout<<"No object declared:"; 
    counts();
    HasFriend<int> hf1(10);
    cout<<"After hf1 declared:";
    counts();
    HasFriend<int> hf2(19);
    cout<<"After hf2 declared:";
    counts();
    HasFriend<double> hf3(19.0);
    cout<<"After hf3 declared:";
    counts();

    report(hf1);
    report(hf2);
    report(hf3);




    return 0;
}




约束模板友元

就像上面所说的,如果对于普通模板的友元,我们可能需要写很多差不多的代码,效率上就会差很多。而约束模板友元就大不相同了。
我们需要三步来完成这项事情
第一步:首先要把友元函数声明成模板函数

template<T>void counts();
template<T>void report(T&);

第二步:在模板类中在此声明友元函数,但是这些语句要根据模板参数的类型声明具体化

template<typename T>
class HasFriend{
    ……
    friend void counts<T>();
    friend void report<>(HasFriend<T>&);
}

对于report我们也可以这样声明
friend void report<HasFriend<T>>(HasFriend<T>&);
因为可以从参数列表判断模板类型,所以也可以不写<>。但是counts必须要写。

当我们完成上面的工作后声明一个对象
HasFriend<int> hf
模板类就会变成这样


class HasFriend<int>{
    ……
    friend void counts<int>();
    friend void report<>(HasFriend<int>&)`

}

第三步:定义之前声明的模板函数counts和report

当我们调用counts和report函数时就会根据参数自动生成相应的函数版本,而这些版本也会刚好和上面的友元函数模板具体化匹配上

#include<iostream>

using std::endl;
using std::cout;

template <typename T> void counts();
template <typename T> void report(T&);


template<typename TT>
class HasFriend{
    private :
    TT item;
    static  int ct;
    public:
    HasFriend(const TT& i):item(i){ct++;}
    ~HasFriend(){ct--;}
    friend void counts<TT>();
    friend void report<>(HasFriend<TT> &); 
};



template<typename T>
int HasFriend<T>::ct= 0;


template<typename T>
void counts(){
    cout<<"template size:"<<sizeof (HasFriend<T>)<<";";
    cout<<"template counts():"<<HasFriend<T>::ct<<endl;

}
template<typename T>
void report(T& hf){
    cout<<hf.item<<endl;

}


int main(){
    counts<int>();

    HasFriend<int> hf1(10);
    HasFriend<int> hf2(19);
    HasFriend<double> hf3(19.0);    
    report(hf1);
    report(hf2);
    report(hf3);
    cout<<"counts<int>() ouput"<<endl;  
    counts<int>();
    cout<<"counts<double>() ouput"<<endl;   
    counts<double>();






    return 0;
}


非约束模板友元

通过在类中声明模板,就会创建非约束的友元函数,即每个函数的具体化都是没给类具体化的友元

#include<iostream>

using std::endl;
using std::cout;


template<typename T>
class ManyFriend{
    private :
        T item;

    public:
        ManyFriend(const T& i):item(i){}
        template <typename C,typename D>friend void show(C & ,D &);
};
template<typename C,typename D>
void show(C & c ,D & d){
    cout<<c.item<<";"<<d.item<<endl;
}




int main(){


    ManyFriend<int> hf1(10);
    ManyFriend<int> hf2(19);
    ManyFriend<double> hf3(19.1);   

    cout<<"hf1,hf2:";
    show(hf1,hf2);
    cout<<endl;
    cout<<"hf2,hf3:";
    show(`
f2,hf3);



    return 0;
}

模板别名

可以使用typedef为模板起别名如

typedef std::array<double ,12>arrd;
typedef std::array<int ,12>arri;
typedef std::array<string ,12>arrst;
arrd a;
arri b;
arrst c;

c++11新增了一项模板别名的功能

template<typename T>
using arrtype =std:array<T,12>;
.....
arrtype<double> a;//array<double,12>
arrtype<int> a;
//array<int,12>
arrtype<string> a;//
array<string,12>

c++11还可以把using用于非模板下,这时候和typedef等价

嵌套类模板

下面是一个”队列“的简单实现

#ifndef QUEUET_H 
#define QUEUET_H
template<typename Item>
class QueueT{
    private:
        enum {Q_SIZE=10};
        //Node is nested class definition(It's more like thiat linked list)
        class Node{
            public :
                Item item;
                Node * next;
                Node(const Item& i):Item(i),next(0){}

        };
        Node * front;
        Node * rear;
        int items;
        const int qsize;
        QueueT(const QueueT & q):qsize(0){};
        QueueT & operator=(const QueueT & q){return * this;}
    public :
        QueueT(int qs=Q_SIZE);
        ~QueueT();
        bool isempty()const
        {
            return  items==0;
        }
        bool isfull() const
        {
            return items==qsize;

        }
        int queuecount()const
        {
            return items;
        }
        bool enqueue(const Item &);//add item to end;
        bool dequeue( Item &);//remove item from front;

};

//QueueT method
template <typename Item>
QueueT<Item>::QueueT(int qs):qsize(qs){
    front=rear=0;
    items=0;
}
template <typename Item>
QueueT<Item>::~QueueT(){
    Node *temp;
    while(front!=0){        //while queue is not yet empty
        temp= front;        //save address of front item
        front =front->next;//reset pointer to next item
        delete temp;        //delete former front

    }


}
//add  item to queue
template<typename Item>
bool QueueT<Item>::enqueue(const Item & item){
    if(item.isfull())
        return false;
    Node *add=new Node(item); //create node
    //on failure,new thoews std::bad_alloc excepion
    items++;
    if(front==0)    //if queue is empty
        front=add;  //place item at fornt
    else    
        rear->next=add;//else place at rear
    rear=add;           //have rear point to new  node
    return true;

}
//place itme into item  variable and remove from queue
template<typename Item>
bool QueueT<Item>::dequeue(Item & item){
    if(front==0)
        return false;
    item=front->item;
    items--;
    Node *temp=front;
    f``
ont=front->next;
    delete temp;
    if(items==0)
        rear=0;
    return true;
}


#endif

上面对于每一个Queue模板的实例都是会生成一个Node

Queue<double>::Node
Queue<int>::Node

所以上面两个名称是不会冲突的

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值