• Cpp Chapter 14: Reusing code in C++ Part2


    14.4 Class Templates

    ) Templates provide parameterized types, which is capable of passing a type name as a recipe for building a class or a function.
    ) Changes defining a template class:
    1 preface class and method definition with

    template <class Type>
    

    Here type serves as a generic type specifier(built-in types, classes, etc.)
    2 The method name will be altered from

    bool Stack::push(const Item & item)
    {
        ...
    }
    

    to

    bool Stack<Type>::push(const Type & item)
    {
        ...
    }
    

    ) A particular actualization of a such a template is called instantiation.
    Here comes the class definition and class methods file:

    // stacktp.h -- a stack template
    #ifndef STACKTP_H_INCLUDED
    #define STACKTP_H_INCLUDED
    
    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 // STACKTP_H_INCLUDED
    

    ) To use template classes, you generate template class in this way:

    Stack<int> kernels;
    Stack<string> colonels;
    

    Here comes the code:

    // stacktem.cpp -- testing the template stack class
    #include <iostream>
    #include <string>
    #include <cctype>
    #include "stacktp.h"
    using std::cin;
    using std::cout;
    
    int main()
    {
        Stack<std::string> st;
        char ch;
        std::string po;
        cout << "Please enter A to add a purchase order,
    "
             << "P to process a PO, or Q to quit.
    ";
        while (cin >> ch && std::toupper(ch) != 'Q')
        {
            while (cin.get() != '
    ')
                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
    ";
                else
                    st.push(po);
                break;
            case 'P':
            case 'p':
                if (st.isempty())
                    cout << "stack already empty
    ";
                else
                {
                    st.pop(po);
                    cout <<"PO #" << po << " popped
    ";
                    break;
                }
            }
            cout << "Please enter A to add a purchase order,
    P to process a PO, or Q to quit.
    ";
        }
        cout << "Bye
    ";
        return 0;
    }
    

    ) To use a stack of pointers to strings, you need to have each pointer point to a separate string to work well.

    // stcktp1.h -- modified Stack template
    #ifndef STCKTP1_H_INCLUDED
    #define STCKTP1_H_INCLUDED
    
    template <class Type>
    class Stack
    {
    private:
        enum {SIZE = 10};
        int stacksize;
        Type * items;
        int top;
    public:
        explicit Stack(int ss = SIZE);
        Stack(const Stack & st);
        ~Stack() {delete [] items;}
        bool isempty() {return top == 0;}
        bool isfull() {return top == stacksize;}
        bool push(const Type & item);
        bool pop(Type & item);
        Stack & operator=(const Stack & st)
    };
    
    template <class Type>
    Stack<Type>::Stack(int ss) : stacksize(ss), top(0)
    {
        items = new Type [stacksize];
    }
    
    template <class Type>
    Stack(Type)::Stack(const Stack & st)
    {
        stacksize = st.stacksize;
        top = st.top;
        items = new Type [stacksize];
        for (int i = 0; i < top; i++)
            items[i] = st.items[i];
    }
    
    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<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 < top; i++)
            items[i] = st.items[i];
        return *this;
    }
    
    #endif // STCKTP1_H_INCLUDED
    

    Noteworthy:
    The prototype for assignment operator declared the return type of the method as Stack rather than Stack:

    Stack & operator=(const Stack & st);
    

    This is an abbreviation used only in class scope.
    ) Here comes the code that uses the simplified version of stack:

    // stkoptr1.cpp -- testing stack of pointers
    #include <iostream>
    #include <cstdlib>
    #include <ctime>
    #include "stcktp1.h"
    const int Num = 10;
    int main()
    {
        std::srand(std::time(0));
        std::cout << "Please enter stack size: ";
        int stacksize;
        std::cin >> stacksize;
        Stack<const char *> st(stacksize);
    
        const char * in[Num] = {"a","b","c","d","e","f","g","h","i","j"};
        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++)
            std::cout << out[i] << std::endl;
    
        std::cout << "Bye
    ";
        return 0;
    }
    

    ) Next let's come to an array template example:

    // arraytp.h -- Array Template
    #ifndef ARRAYTP_H_INCLUDED
    #define ARRAYTP_H_INCLUDED
    
    #include <iostream>
    #include <cstdlib>
    
    template <class T, int n>
    class ArrayTP
    {
    private:
        T ar[n];
    public:
        ArrayTp() {}
        explicit 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
    ";
            std::exit(EXIT_FAILUER);
        }
        return ar[i];
    }
    
    template <class T, int n>
    T ArrayTP<T,n>::operator[](int i) const
    {
        if (i < 0 || i >= n)
        {
            std::cerr << "Error in array limits: " << i << " is out of range
    ";
            std::exit(EXIT_FAILURE);
        }
        return ar[i];
    }
    
    #endif // ARRAYTP_H_INCLUDED
    

    Noteworthy
    1 Note the template heading:

    template <class T, int n>
    

    This second parameter is called a non-type, as it acts as a particular type. You can use the template in this way:

    ArrayTP<double, 12> eggweights;
    

    This initializes an array of 12 doubles called eggweights.
    2 Advantage and drawback
    Advantage: fast execution, free from new and delele
    Drawback: Each array size generate its own template

    ) As normal classes, you could also use template classes for inheritance or containment.
    You could also use templates recursively:

    ArrayTP< ArrayTP<int,5>, 10> twodee;
    

    This is the same effect as

    int twodee[10][5];
    

    Here comes an example using the recursive call of template to create a two-dimensional array:

    template <class T, int n>
    class ArrayTP
    {
    private:
        T ar[n];
    public:
        ArrayTP() {};
        explicit ArrayTP(const T & v);
        virtual T & operator[](int i);
        virtual T operator[](int i) const;
    };
    

    ) You could also use templates with different types combined:

    template <class T1, class T2>
    

    Here comes the code using this trick with a Pair class:

    // pairs.cpp -- defining and using a Pair template
    #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 std::cout;
        using std::endl;
        using std::string;
        Pair<string,int> ratings[4] =
        {
            Pair<string,int>("a",1),
            Pair<string,int>("b",2),
            Pair<string,int>("c",3),
            Pair<string,int>("d",4),
        };
    
        int joints = sizeof(ratings) / sizeof(Pair<string,int>);
        cout << "Rating:	 Eatery
    ";
        for (int i = 0; i < joints; i++)
            cout << ratings[i].second() << ":	 " << ratings[i].first() << endl;
        cout << "Oops! Revised rating:
    ";
        ratings[3].first() = "g";
        ratings[3].second() = 6;
        cout << ratings[3].second() << ":	 " << ratings[3].first() << endl;
        return 0;
    }
    

    ) You can provide default values for type parameters:

    template <class T1, class T2 = int> class Topo {...}
    

    ) Template specializations
    1 Implicit instantiation
    When an object of the desired type is declared, the compiler generates a specialized class definition, using the recipe provided by the template:

    ArrayTP<double, 30> * pt;
    pt = new ArrayTP<double, 30>;
    

    The compiler only generates a class for ArrayTP<double, 30> at the second sentence because only the second sentence requires declaration of a specific object of the class ArrayTP<double, 30>.
    2 Explicit instantiation
    When you explicitly use the keyword template and desired type to declare a class, the compiler generates one with the specific data types according to the template instructions even if there is no object of the class needed currently:

    template class ArrayTP<string, 100>;
    

    3 Explicit specialization
    In some of the cases, the template's methods might work differently for one specific type, so you might want to single out the desired type's method implementations. A specialized class template definition has the following form:

    template <> class Classname<specialized-type-name> { ... };
    

    for example, after you explicit specialized template <> class Thing<char *> { ... }, next time you require a class of the char * type, the compiler would use the specialized version rather than generating one by the template.

    ) Member templates
    A template could be a member of a structure, class or template class:

    // tempmemb.cpp -- template members
    #include <iostream>
    using std::cout;
    using std::endl;
    template <typename T>
    class beta
    {
    private:
        template <typename 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<typename U>
            U blab(U u, T t) { return (n.Value() + q.Value()) * u / t;}
            void Show() const { q.show(); n.show();}
    };
    
    int main()
    {
        beta<double> guy(3.5, 3);
        cout << "T was set to double
    ";
        guy.Show();
        cout << "V was set to T, which is double, then V was set to int
    ";
        cout << guy.blab(10,2.3) << endl;
        cout << "U was set to int
    ";
        cout << guy.blab(10.0, 2.0) << endl;
        cout << "U was set to double
    ";
        cout << "Done
    ";
        return 0;
    }
    

    ) Templates could also be parameters for templates:

    template <template <typename T> class Thing>
    

    Here template <typename T> class is the type and Thing is the parameter. View an example:

    // tempparm.cpp -- templates as parameters
    #include <iostream>
    #include "stacktp.h"
    
    template <template <typename T> class Thing>
    class Crab
    {
    private:
        Thing<int> s1;
        Thing<double> s2; // Thing is the template parameter
    public:
        Crab() {};
        bool push(int a, double x) { return s1.push(a) && s2.push(x);}
        bool pop(int & a, double & x) { return s1.pop(a) && s2.pop(x);}
    };
    
    int main()
    {
        using std::cout;
        using std::cin;
        using std::endl;
        Crab<Stack> nebula; // send Stack template as parameter to template class Crab
        int ni;
        double nb;
        cout << "Enter int double pairs, such as 4 3.5 (0 0 to end):
    ";
        while (cin >> ni >> nb && ni > 0 && nb > 0)
        {
            if (!nebula.push(ni,nb))
                break;
        }
    
        while (nebula.pop(ni,nb))
            cout << ni << ", " << nb <<endl;
        cout << "Done.
    ";
    
        return 0;
    }
    

    ) Template classes and friends
    1 Non-template friend functions to template classes
    This means that the function is friend to all possible instantiations of the tempalate, use this form:

    template <class T>
    class HasFriend
    {
        friend void report(HasFriend<T> &);
        ...
    };
    

    In this case report could accept a object of specific type as parameter.
    Here comes an example:

    // frnd2tmp.cpp -- template class with non-template friends
    #include <iostream>
    using std::cout;
    using std::endl;
    
    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 reports(HasFriend<T> &);
    };
    
    template <typename T>
    int HasFirned<T>::ct = 0;
    
    void counts()
    {
        cout << "int count: " << HasFriend<int>::ct << "; ";
        cout << "double conut: " << HasFriend<double>::ct << "
    ";
    }
    
    void reports(HasFriend<int> & hf)
    {
        cout << "HasFriend<int>: " << hf.item << endl;
    }
    
    void reports(HasFriend<double> & hf)
    {
        cout << "HasFriend<double>: " << hf.item << endl;
    }
    
    int main()
    {
        cout << "No objects declared: ";
        counts();
        HasFriend<int> hfi1(10);
        cout << "After hfi1 declared: ";
        counts();
        HasFriend<int> hfi2(20);
        cout << "After hfi2 declared: ";
        counts();
        HasFriend<double> hfdb(10.5);
        cout << "After hfdb declared: ";
        counts();
        reports(hfi1);
        reports(hfi2);
        reports(hfdb);
    
        return 0;
    }
    

    2 Bound template friend functions to template classes
    This means that the type of the friend is determined by the type of class when it is instantiated.

    3 Unbound template friend functions to template classes
    This means that all specializations of the friend are friends to each specialization of the class.

  • 相关阅读:
    SQL Server 使用日志传送
    SQL Server 2008 R2 主从数据库同步
    JavaScript及C# URI编码详解
    sql server日期时间函数
    ASP.NET Core在Azure Kubernetes Service中的部署和管理
    [Nuget]Nuget命令行工具安装
    利用HttpListener创建简单的HTTP服务
    短链接实现
    [ubuntu]中文用户目录路径改英文
    [ubuntu]deb软件源
  • 原文地址:https://www.cnblogs.com/fsbblogs/p/9962562.html
Copyright © 2020-2023  润新知