C++类模板(第十四章)

容量类等有些类并不能使用继承和包含来实现。容器类设计用来存储其他对象或数据类型,比如Stack、Queue。
与其编写类声明,不如编写泛型栈,然后将具体的类型作为参数传递这个类。既类模板。

定义类模板

  1. 代码开头template <typename Type>
  2. Type应作为标识符
  3. 类声明定义了方法,则可以省略模板前缀和类限定符
  4. 模板的具体实现被称为实现化或具体化,模板必须与特点模板实例化请求一起使用,最简单的方法是将所有模板信息放在header中。
#ifndef STACKTP_H_
#define STACKTP_H_
template <class Type>
class Stack
{
    private:
        enum{ MAX=10};
        Type items[MAX];   //Type作为标识符
        int top;
    public:
        Stack();
        bool isempty();
        bool isfull();
        bool push(const Type& item);
        bool pop(Type& item);
 } ;
 
 template <class Type>  //不是在类声明中,每次定义需要模板前缀和限定符
 Stack<Type> ::Stack()   //Stack::变为Stack<Type> ::
 {
    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

使用类模板

  1. 使用具体类型替换泛型名,如Stack<string> st
#include<iostream>
#include"stacktp.h"
#include<cctype>
#include<string>
using namespace std;
int main()
{
Stack<string> st;
char ch;
string po;
cout<<"Enter A to add a purchase order\nP to process a PO\nQ 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 is full\n";
            else
                st.push(po);
            break;
        case'p':case'P':
            if(st.isempty())
                cout<<"Stack is empty\n";
            else
            {
                st.pop(po);
                cout<<"PO #"<<po<<" poped\n";
            }
            break;
    }
    cout<<"Enter A to add a purchase order\nP to process a PO\nQ to quit\n";
        
}
cout<<"Done\n";
return 0;
}
image.png

深入探讨模板类
创建指针栈的问题:

  1. 不正确使用指针栈
    Stack<char* > st;
    ①将string po 替换成char* po
    没有创造用于保存字符串的空间
    string po替换为char po[40]
    数组与pop操作冲突
    string po替换为char* po = new char[40]
    只有一个pop变量,该变量总是指向相同的内存地址。压入栈时,保存的都是相同地址。
  2. 正确使用
    提供指针数组
    PS.① 要一个可变大小的栈,则用
Type * items;
int stacksize;

构造函数:

items = new Type [ stacksize ];

析构函数

delete [] items

②在主程序里

Stack<const char*> st(stacksize);
const char* in[NUM] ={"..." ....};
st.push(in[next++]);

构造函数new创建一个用于保存指针的数组,数组元素是指向字符串的指针,析构函数是删除该数组,而不是指针指向的字符串

数组模板和非类型参数

template < class T, int n >
class ArrayTP
{
    private:
        T ar [n] 
....
}
  1. int n 这种参数叫非类型或表达式参数。表达式参数可以是整型、枚举、引用或指针。模板代码不能修改参数值,用作表达式参数的值必须是常量表达式。
  2. 表达式参数方法使用的是为自动变量维护的内存栈,执行速度更快。缺点是每种数组大小都要生成自己的模板。
    ArrayTP<double ,12> a1ArrayTP <int, 12> a2共享一个模板,但ArrayTP<double , 13> a3则是新的模板.构造函数方法会更通用。

模板多功能性

  1. 递归使用模板:
    ArrayTP< ArrayTP<int ,5> ,10>等于int [10][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{return a;}
        T2 second() const{return b;}
        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;
}

int main()
{
    using namespace std;
    Pair<string,int> r[4]=
    {
        Pair<string,int>("JEff1",10),
        Pair<string,int>("JEff2",11),
        Pair<string,int>("JEff3",12),
        Pair<string,int>("JEff4",13),
    }; 
    
    int lim = sizeof(r)/sizeof(Pair<string,int>);
    for(int i=0;i<lim;i++)
    {
        cout<<r[i].first()<<endl;
        cout<<r[i].second()<<endl;
    }
    
    r[3].first() ="Jeff edit";
    r[3].second() = 100;
    cout<<r[3].first()<<" "<<r[3].second();
    return 0;
}
  1. 默认类型模板参数
    template< class T1, class T2 =int >没有T2值,则默认为int

模板具体化

  1. 隐式实例化
    即它们声明一个或多个对象,指出所需的类型,而编译器使用通用模板提供的处方生成具体的类定义。就需要对象的时候会隐式根据类模板创造对象。
  2. 显示实例化
    使用template并指出所需类型来声明类,编译器将生成类声明的显示实例化(explicit instantiation)。声明必须位于模板定义所在的名字空间。
    template class ArrayTP<string,100>,这种情况虽然没有创建或提及类对象,编译器也将生成类声明(方法定义)。和隐式实例化一样,根据通用模板来生成具体化。
  3. 显示具体化
    显式具体化是特定类型(用于替换模板泛型)的定义。比如一些类可以用T::operator>(),但如果是const char*,就得用strcmp来比较大小比较好。这时候可以采用具体类型定义的模板
    格式如下:template <> class Classname(type_name)
    比如
template <> class SortedArray<const char* >
{ ....}

这样当请求const char* 模板类时,编译器将使用上述定义而不是通用模板定义。

  1. 部分具体化
    通用模块template<class T1,class T2> class Pair {...}
    部分具体化template<class T1> class Pair<T1,int>
    显式具体化template<> class Pair<int ,int>
    当有多个模板供选择时,编译器将使用具体化程度最高那个。
    也可以通过指针提供特殊版本来部分具体化现有模板:
template <class T>
class Feeb{...};   通用模板
template<class T*>
class Feeb{...}    

成员模板

#include<iostream>
using namespace std;

template<class T>
class Beta
{
    private:
        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;}
        };
        Hold<T> q;             创建两个模板类对象
        Hold<int> n;
    public:
        Beta(T t,int i) : q(t),n(i){}
        template<class U>
        U blab(U u,T t){return (q.value()+n.value())*u/t;}   
//U类型由方法调用时显式确定,T类型由对象实例化确定
        void show() const {q.show();n.show();}
 }; 
 
 int main()
 {
    Beta<double> guy(3.5,3);    set为T为double
    cout<<"T was set to double\n";
    guy.show();
    cout<<"V was set to T,which is double ,then V was set to int\n";
    cout<<guy.blab(10,2.3)<<endl;            
    cout<<"U was set to int\n";
    cout<<guy.blab(10.0,2.3)<<endl;
    cout<<"U was set to double\n";
    cout<<"Done!";
    return 0;
 }
image.png

也可以在Beta模板中声明hold类和blah方法,在Beta模板外定义它们。

template<class T>
class Beta
{
    private:
        template<class V>
        class Hold;           声明类
        Hold<T> q;
        Hold<int> n;
    public:
        Beta(T t,int i):q(t),n(i){}
        template<class U>
        U blab(U u,T t);       声明方法
        void show() const{q.show();n.show();}
};

template<class T>
    template<class V>      嵌套模板类
    class Beta<T>::Hold
    {
        private:
            V val;
        public:
            Hold(V v=0):val(v){}
            void show() const{std::cout<<val<<std::endl;}
            V value() const{return val;}
    };
    
template<class T>
    template<class U>
    U Beta<T>::blab(U u,T t)
    {
        return (n.value()+q.value()*u/t);
     } 

将模板用作参数

template<template<class T> class Thing>
class Crap
{
    ...
}
其中template<class T> class是类型,Thing是参数。
Crap<Stack> nebula;
就是声明一个Crap类的类对象,模板参数为Stack模板,然后如果类里有pop()方法,
就可以nebula.pop();

模板类和友元

  1. 非模板友元
  2. 约束模板友元:友元的类型取决于被实例化时的类型
  3. 非约束模板友元 : 友元的所有具体化都是类的每一个具体化的友元
  • 非模板友元:
template<class T>
class has
{
    public:
      friend void count();
...
}

上述count()函数成为模板所有实例化友元.可以通过访问全局对象、使用全局指针访问非全局对象、创建自己的对象、访问独立于对象模板类的静态数据成员来访问has对象。
friend void report(has& )是不可以的,因为不存在这样的对象,必须具体化

template<class T>
class has
{
      public:
        friend void report(has<T> &)
}
class has<int>
{  
    friend void report(has<int>&)
}

那将成为has<int>的类友元。
如果has模板有一个静态成员,这说明这个类每一个特定具体化都将有自己的静态成员。

例子:

#include<iostream>
using namespace std;

template<class T>
class has
{
    private:
        T item;
        static int ct;
    public:
        has(const T& t):item(t){ct++;}      
        ~has(){ct--;}
        friend void count();
        friend void report(has<T>&);
 } ;
 
 template<class T>
 int has<T>::ct = 0;
 
 void count()
 {
    cout<<"int count: "<<has<int>::ct; 
    cout<<", Double count: "<<has<double>::ct<<endl;
 }
 
 void report(has<int>& t)
 {
    cout<<"has<int> item: "<<t.item<<endl;
 }
 
 void report(has<double>& t)
 {
    cout<<"has<double> item: "<<t.item<<endl;
 }
 
 int main()
 {
    cout<<"No objects: \t";
    count();
    has<int> h1(10);
    cout<<"After h1: \t";
    count();
    has<double> h2(30.0);
    cout<<"After h2: \t";
    count();
    has<int> h3(8);
    cout<<"After h3: \t";
    count();
    report(h1);
    report(h2);
    report(h3);
    return 0;
 }
  1. 每一种模板类都有一个静态数据成员
  2. 静态数据成员定义如上,说明是T模板的静态成员
  3. count()函数是引用不同类模板数据,用作用域解析运算符
  4. report()是调用参数的数据,用点运算符
  5. report函数要显式具体化,因为你要输出不同的字符串内容


    image.png
  • 模板类约束模板友元函数
#include<iostream>
using namespace std;

template<class T> void count();
template<class T> void report(T &);

template<class TT>
class has
{
    private:
        TT item;
        static int ct;
    public:
        has(const TT& t):item(t){ct++;}     
        ~has(){ct--;}
        friend void count<TT>();
        friend void report<>(has<TT>&);
 } ;
 
 template<class T>
 int has<T>::ct = 0;
 
 template<class T>
 void count()
 {
    cout<<"template size:\t"<<sizeof(has<T>); 
    cout<<",  template count():\t"<<has<T>::ct<<endl;
 }
 
 template<class T>
 void report(T& t)
 {
    cout<<"object item:\t"<<t.item<<endl;
 }
 
 
 int main()
 {
    has<int> h1(10);
    has<double> h2(30.0);
    has<int> h3(8);
    report(h1);               因为<>为空,可以omit
    report(h2);
    report(h3);
    cout<<"Template int:\t";
    count<int>();           要在<>直接指出类型。
    cout<<"Template double:";
    count<double>();
    return 0;
 }
  1. 在类定义面前声明每个模板函数
  2. 在声明中指出模板具体化,可以在<>直接指出,也可以在通过参数推断出模板类型
  3. 为友元模板提供定义

非约束模板友元函数

通过类内部声明模板,可以创建非约束友元函数。约束友元函数就是你已经约束了友元函数用T模板去实现。非约束模板,你可以不仅使用一个模板去实现友元函数。
声明template<class C, class D> friend void show(C&, D&)

using namespace std;

template<class T>
class has
{
    private:
        T item;
    public:
        has(const T& t):item(t){}       
        ~has(){}
        template<class C,class D>friend void show(C&,D&);
 } ;
 
 template<class C, class D> void show(C& c,D& d)
 {
    cout<<c.item<<","<<d.item<<endl;
 }
 
 
 
 int main()
 {
    has<int> h1(10);
    has<double> h2(30.4);
    has<int> h3(8);
    cout<<"h1 and h2 : ";
    show(h1,h2);
    cout<<"h1 and h3 : ";
    show(h1,h3);
    return 0;
 }
image.png

模板别名

  1. typedef std::array<double, 12> arrd
    arrd d1 就声明了std::array<double,12> d1
template<class T>
   using arrtype = std::array<T,12>

arrtype<double> gallones

C++11允许using = 用于非模板
using pc = const char*

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。