• 【类模板】


    函数模板相似,类也可以被一种或多种类型参数化。

    容器类是一个典型案例,通常用于管理和组织某种类型的元素。只要使用类模板,就可以实现容器类,而不需要确定容器中元素的类型。

    我们以stack作为类模板的例子。

    1 类模板stack的实现

    与函数模板的处理方式一样,在头文件中声明和定义类statck<>

    #include 
    #include 
    
    template<typename T>
    class stack{
    private:
        std::vector<T> elems; // 存储元素的容器
    public:
        void push(T const&); // 压入元素
        void pop(); // 弹出元素
        
        T top() const; // 返回栈顶元素
        bool empty() const { // 识别栈是否为空
            return elems.empty();
        }
    };
    template<typename T>
    void stack<T>::push(T const& a)
    {
        elems.push_back(a);
    }
    template<typename T>
    void stack<T>::pop()
    {
        if(elems.empty()) {
            throw std::out_of_range("stack<> pop(): empty stack");
        }
        elems.pop_back(); // 删除最后一个元素
    }
    
    template<typename T>
    T stack<T>::top() const
    {
        if(elems.empty()) {
            throw std::out_of_range("stack<>::top(): empty stack");
        }
        return elems.back(); // 返回最后一个元素的拷贝
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 这里继续使用T作为类型参数的标识符。在类模板的内部,T可以像其他任何类型一样,用于声明成员变量和成员函数。

    这个类的类型是stack,T是模板参数; 类名是stack。
    当你在声明中需要使用该类的类型时,就必须使用stack, 当需要使用类名时就应该是stack。

    template<typename T>
    class stack{
       stack(stack<T> const&); // 拷贝构造,这里使用类型stack作为参数, 函数名是类名
       stack(); // 构造函数
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 为了定义类模板的成员函数,你必须指定该成员函数是一个函数模板,而且你还需要使用这个类模板的完整类型限定符:stack::

    对于类模板的任何成员函数,你都可以把它实现为内联函数,将它定义于类声明里面。

    template<typename T>
    class stack{
        ...
        void push(T const& elem) {
             elems.push_back(elem);
        }
        ...
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2 类模板stack的使用

    为了使用类模板对象,必须显式地指定模板参数。下面例子展示如何使用类模板stack<>

    #include 
    #include 
    #include 
    #include "stack.h" // 这里声明和定义stack的实现
    
    int main()
    {
        try {
            stack<int> intStack;
            stack<std::string> stringStack;
            // 使用init 栈
            intStack.push(7);
            std::cout<<intStack.top()<<std::endl;
            
            // 使用string栈
            stringStack.push("hello");
            std::cout<<stringStack.top() << std::endl;
            stringStack.pop();
            stringStack.pop();
        } catch(std::execption const& ex) {
            std::cerr << "Exception: " << ex.what() << std::endl;
            return EXIT_FAILURE;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    通过声明类型stack, 在类模板内部就可以用int实例化T。对于所有被调用的成员函数,都会实例化出基于int类型的函数代码。类似stack, 将会创建一个stack对象,对于所有被调用的成员函数,也会实例化出基于std::string的函数代码。

    • 只有那些被调用的成员函数,才会生产这些函数的实例代码。对于类模板,成员函数只有在被使用的时候才会被实例化。
    • 这样的好处是节省空间和时间。
    • 对于那些“未能提供所有成员函数中所有操作的”类型,你也可以使用该类型来实例化类模板。只要那些"未能提供某些操作的"成员函数,模板内部不使用就可以。
    • 如果类模板中含有静态成员,那么用来实例化的每种类型,都会实例化这些静态成员。
    • 你可以像使用其他任何类型一样地使用实例化后的类模板类型,只要它支持所调用的操作就可以。
    void foo(stack<int> const&  s)
    {
        stack<int> istack[10];  /// istack是含有10个栈的数组
    }
    
    • 1
    • 2
    • 3
    • 4

    借助于类型定义,你可以方便地使用类模板:

    typedef stack<int>   intStack;
    void foo(intstack const& s)
    {
        intStack istack[10]; /// istack 是一个含有10个int栈的数组
        ....
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    C++的类型定义只是定义了一个类型别名,并没有定义一个新类型,是可以互相赋值。

    模板实参可以是任何类型, eg:

    stack  floatStack; // 元素类型为浮点型指针的栈
    stack > intStackStack; // 元素类型为int栈的栈
    
    • 1
    • 2

    要求是该类型必须提供被调用的所有操作

    3 类模板的特化

    • 用模板实参来特化类模板。和函数模板的重载类似,通过特化类模板,你可以优先基于某种特定类型的实现,或者克服某种特定类型在实例化模板时所出现的不足,比如该类型没有提供某种操作。

    • 特化一个类模板,你还要特化该类模板的所有成员函数。虽然也可以只特化某个成员函数,但这个做法并没有特化整个类,也就是没有特化整个类模板。

    • 为了特化一个类模板,必须在起始处声明一个template<>, 接下来声明用来特化类模板的类型。这个类型被用作模板实参,且必须在类名的后面直接指定:

      template<>
      class stack<std::string> {
       ...
      };
      
      • 1
      • 2
      • 3
      • 4

    进行类模板的特化时,每个成员函数都必须重新定义为普通函数,原来模板函数中的每个T也相应地被进行特化的类型取代:

        void stack<std::string>::push(std::string const& elem)
        {
            elems.push_back(elem);
        }
    
    • 1
    • 2
    • 3
    • 4

    用std::string特化stack<>

    template<>
    class stack<std::string>
    {
    private:
        std::deque<std::string> elems;
    public:
        void push(std::string const& );
        void pop();
        std::string top() const;
        bool empty() const{
            return elems.empty();
        }
    };
    
    void stack<std::string>::push(std::string const& elem)
    {
        elems.push_back(elem);
    }
    
    void stack<std::string>::pop()
    {
        if (elems.empty()) {
            throw std::out_of_range("stack::pop(): empty stack");
        }
        elems.pop();
    }
    
    std::string stack<std::string>::top()const 
    {
        if (elems.empty()) {
            throw std::out_of_range("stack::top(): empty stack");
        }
        return elems.back();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    我们使用deque而不是vector来管理stack内部的元素,目的是为了说明一个特点:特化的实现可以和基本类模板的实现完全不同。

    4 局部特化

    类模板可以被局部特化。你可以在特定环境下指定类模板的特定类型,并且要求某些模板参数仍然必须由用户来定义。
    譬如下面我们给类模板:

    template<typename T1, typename T2>
    class Myclass {
        ...
    };
    
    • 1
    • 2
    • 3
    • 4

    然后我们看看几种局部特化:

    • 局部特化一
      // 两个模板参数具有相同的类型
      template<typename T>
      class Myclass<T, T> {
       ...
      };
      
      • 1
      • 2
      • 3
      • 4
      • 5
    • 局部特化二:
      // 第二个模板参数的类型是int
      template<typename T, int>
      class Myclass<T, int> {
        .... 
      };
      
      • 1
      • 2
      • 3
      • 4
      • 5
    • 局部特化三:
      template<typename T1, typename T2>
      class Myclass<T1*, T2*> {
         ...
      };
      
      • 1
      • 2
      • 3
      • 4

    然后我们看一下,下面各种声明使用哪个模板:

    Myclass<int, float> mif; // 使用Myclass
    Myclass<float, float> mff; // 使用Myclass
    Myclass<float, int> mfi; //使用Myclass
    Myclass<iint *, float*> mp; //使用Myclass
    
    • 1
    • 2
    • 3
    • 4

    如果有多个局部特化同等程度地匹配某个声明,那么就称为声明具有二义性:

    Myclass m; // 错误,同等程度地匹配Myclass和Myclass
    Myclass m; // 错误,同等程度地匹配Myclass和Myclass
    
    • 1
    • 2

    为了解决二义性,你可以另外提供一个指向相同类型指针的特化:

    template
    class Myclass {};
    
    • 1
    • 2

    5 缺省模板实参

    类模板还可以定义缺省值;这些值就被称为缺省模板实参;而且,它们还可以引用之前的模板参数。例如类stack<>中,可以把用于管理元素的容器定义为第二个模板参数,并且使用std::vector<>作为它的缺省值:

    #include
    #include
    template<typename T, typename COUNT = std::vector<T> >
    class stack {
    private:
        COUNT elems; // 包含元素的容器
    public:
        void push(T const&);
        void pop();
        T top() const;
        bool empty() const {
            return elems.empty();
        }
    };
    
    template<typename T, typename COUNT>
    void stack<T, COUNT>::push(T const& elem)
    {
        elems.push_back(elem);
    }
    
    template<typename T, typename COUNT>
    void stack<T, COUNT>::pop()
    {
        if (elems.empty()) {
            throw std::out_of_range("stack<>::pop(): empty stack");
        }
        elems.pop_back();
    }
    
    template<typename T, typename COUNT>
    T stack<T, COUNT>::top() const
    {
        if (elems.empty()) {
            throw std::out_of_range("stack<>::top(): empty stack");
        }
        return elems.back();    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    类模板含有两个模板参数,因此每个成员函数的定义都必须具有这两个参数:

    template<typename T, typename COUNT>
    void stack<T, COUNT>::push(T const& elem)
    {
        elems.push_back(elem);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    你仍然可以像前面例子一样使用这个栈,只是说,你只传递一个类型实参给这个类模板,将使用缺省的vector来管理栈元素。

    下面我们看一下在程序中使用stack对象,指定容器类型:

    #include 
    #include 
    #include 
    #include "stack3.h"
    
    int main()
    {
        stack<int> intStack; // int栈
        stack<double, std::deque<double> > dblStack; // double栈,使用std::deque管理元素
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
  • 相关阅读:
    【C++】“334. 队列检查”题目解析及所用vector相关用法总结
    java基础面试题续集-学习笔记
    vue 测试环境配置test
    LeetCode 34. 在排序数组中查找元素的第一个和最后一个位置
    焱融全闪系列科普| 为什么 SSD 需要 NVMe?
    HR人才测评,如何做管理岗位的领导力测评?
    零基础如何学好Photoshop
    Python 导入Excel三维坐标数据 生成三维曲面地形图(体) 5-2、线条平滑曲面且可通过面观察柱体变化(二)
    这些在ISIS的代码是多少呀
    SQL数据定义语言(DDL)命令应用
  • 原文地址:https://blog.csdn.net/qq_31213895/article/details/128111324