zoukankan      html  css  js  c++  java
  • 读书笔记C++ Template(The complete guide)Chapter3类模板

    上一篇讲到函数模板,自然需要接下来讲讲类模板,通俗点说,类模板就是带类型参数的类,它表示一族类,这些类的实现逻辑是一致的,STL中的容器类就是这一思想的典型应用,在这一章里,我们将用类模板来实现一个Stack的模板类(类模板?好像差不多)。

    1.一个使用类模板的例子—Stack类

    //bascis/stack1.hpp
    
    #include <vector>
    
    #include <stdexcept>
    
    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& elem)
    
    {
    
    	elems.push_back(elem);
    
    }
    
    template<typename T>
    
    void Stack<T>::pop()
    
    {
    
    	if(elems.empty())
    
    	{
    
    		throw std::out_of_range("Stack<>::pop():empty stack");
    
    	}
    
    	elems.pop();
    
    }
    
    template <typename T>
    
    T Stack<T>::top()const
    
    {
    
    	if(elems.empty())
    
    	{
    
    		throw std::out_of_range("Stack<>::top():empty stack");
    
    	}
    
    	return elem.back();
    
    }

    模板类中的数据用vector来储存,目的是为了排除冗杂反复的细枝末节,从而是我们能关注模板类的模板特性。

    2.模板类的声明

    声明模板类与声明模板函数差不多,也是需要有一行声明

    template<typename T>
    声明的格式同样是
    template<comma-sparated-typename-list>

    在之前给出的代码中,Stack是类的名称,Stack<T>是类的类型,这是需要注意的,在需要类型的地方,请用Stack<T>,反之,用Stack,如:

    template<typename T>
    
    class Stack
    
    {
    
    	...
    
    	Stack(Stack<T>const&);
    
    	Stack<T>&&operator=(Stack<T>const&);
    
    	...
    
    }

    3.模板类的成员函数

    模板类的成员函数与普通的模板函数用法几乎一致,只是加上了属于某个类的束缚,不详述了。

    4.怎么使用模板类

    以刚才的Stack为例:

    //basics/stack1test.cpp
    
    #include <iostream>
    
    #include <string>
    
    #include <cstdlib>
    
    #include "stack1.hpp"
    
    int main()
    
    {
    
    	try{
    
    		Stack<int> intStack;
    
    		Stack<std::string>stringStack;
    
    		intStack.push(7);
    
    		std::cout<<intStack.top()<<std::endl;
    
    		stringStack.push("hello");
    
    		std::cout<<stringStack.top()<<std::endl;
    
    		stringStack.pop();
    
    		stringStack.pop();
    
    	}
    
    	catch
    
    	{
    
    		std::err<<"ExceptionL: "<<ex.what()<<std::endl;
    
    		return EXIT_FAILURE;
    
    	}
    
    }

    可以看到,其实和普通的类用起来没多大区别,就是声明的时候带个参数。

    模板类的使用时可以嵌套的,如下:

    Stack<Stack<int> >intStackStack;

    需要注意的是> >不能连着,否则会被识别成>>,导致错误(一直不理解编译器为什么连这种都识别不出)。

    5.类模板的特化

    具体来讲,类模板的特化允许对某些特定的类型采取更加有效的实现手段,或者是解决某些类型在用同样一套模板时会出现的行为异常(比如指针作为类模板的参数的话经常可能出现问题)。当然,一旦特化了某个类型参数,对应的成员函数全部需要特。第3小节说过,模板类的成员函数是跟普通的模板类差不多的,因此,它们当然也可以单独特化,但是这样的话,我们就不能特化整个类了。

    要特化某个类模板,语法规则是前置一条生命:template<>,如:

    tempalte<>
    
    class Stack<std::string>
    
    {
    
       ...
    
    }

    一旦特化之后,所有的成员函数都必须重新定义成普通成员函数,如:

    //bascis/stack2.hpp
    
    #include <vector>
    
    #include <stdexcept>
    
    template<> //特化标志
    
    class Stack<std::string>{//原来就是class Stack
    
    private:
    
    	std::vector<std::string> elems;
    
    public:
    
    	void push(std::string const &);
    
    	void pop();
    
    	std::string top()const;
    
    	bool empty()const{
    
    		return elems.empty();
    
    	}
    
    };
    
    //template<typename T> 不需要了
    
    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 elem.back();
    
    }

    6.部分特化

    侯捷先生称之为偏特化,可能是是他们的翻译习惯吧,记得偏最小二乘回归的英文就是PLSR,第一个单词就是partial,部分特化英文为partial specialization,这是一个非常值得讲一讲的问题,因为这是STL中trait编程技法的一个重要理论基础(有空的话会讲讲这方面的内容)。

    那么什么是部分特化呢?C++ template给出的说法是这样的:


    You can specify special implementations for particular circumstances,but some template parameters must still be defined by the users.


    也就是说,对于某些特定的使用环境,你可以为模板类定义特殊的实现,对于部分特化而言,还是需要用户指定一些参数,假设原来有个模板类如下:

    template<typename T1,typename T2>
    
    class MyClass
    
    {
    
    	...
    
    };
    部分特化有以下几种:

    • 当两个模板参数的类型一致时:
      template<typename T>
      
      class MyClass<T,T>
      
      {
      
      	...
      
      };
    • 某个模板参数需要固定
      template<typename T>
      
      class MyClass<T,int>
      
      {
      
      	...
      
      };
      
    • 模板参数是指针
      template<typename T1,typename T2>
      
      class MyClass<T1*,T2*>
      
      {
      
      	...
      
      };
      
    具体使用时,选用哪个是有规则的,如果只有一个严格匹配的,自然是选严格匹配的,如:
    MyClass<int,float>mif; //uses MyClass<T1,T2>
    
    MyClass<float,float>mff; //uses MyClass<T,T>
    
    MyClass<float,int>mfi;//use MyClass<T,int>
    
    MyClass<int * float*>mp;//use MyClass<T1*,T2*>

    但是,有时候不只一个匹配的,而且匹配程度一样,就会发生编译错误:

    MyClass<int,int>m; //Error: matches MyClass<T,T> and MyClass<T,int>
    
    MyClass<int *,int *>m;//Error: matches MyClass<T,T> and MyClass<T1* ,T2*>

    匹配实际上是有强有弱的,如果编译器能够识别出匹配的强弱,选强的那个,对于模板匹配,越特化的那个越强,比如说,对于上面例子的第二个情况,我们可以再特化一个模板类:

    template<typename T>
    
    class MyClass<T* ,T*>
    
    {
    
    	...
    
    };

    这个模板类非常强化,以至于编译器会直接选用这个,关于强弱在12章里面会有详细解释,大概意思就是如果A能表示B,B不能表示A,那么说明B的特化更强。

    7.类模板的默认参数

    与函数模板不一样的是,类模板是可以拥有默认参数的,甚至默认参数可以用到模板标识前面的类型参数,请看这个例子:

    //bascis/stack3.hpp
    
    #include <vector>
    
    #include <stdexcept>
    
    template<typename T,typename CONT =std::vector<T> >
    
    class stack{
    
    private:
    
    	CONT elems;
    
    public:
    
    	void push(T const &);
    
    	void pop();
    
    	T top()const;
    
    	bool empty()const{
    
    		return elems.empty();
    
    	}
    
    };
    
    template<typename T,typename CONT =std::vector<T> >
    
    void StacK<T,CONT>::push(T const& elem)
    
    {
    
    	elems.push_back(elem);
    
    }
    
    template<typename T,typename CONT =std::vector<T> >
    
    void StacK<T,CONT>::pop()
    
    {
    
    	if(elems.empty())
    
    	{
    
    		throw std::out_of_range("Stack<>::pop():empty stack");
    
    	}
    
    	elems.pop();
    
    }
    
    template <typename T>
    
    T StacK<T,CONT>::top()const
    
    {
    
    	if(elems.empty())
    
    	{
    
    		throw std::out_of_range("Stack<>::top():empty stack");
    
    	}
    
    	return elem.back();
    
    }

    当然,你可以定义一个这样的具现化实例:

    Stack<double,std::deque<double> >

    8.总结

    • 模板类是一族类的表示,有一个或多个用户指定类型作为参数
    • 要使用模板类,传给它类型参数,模板据此生成特定的代码
    • 模板类中的方法只有在使用到的时候才会具现化.也就是说即便某个类型不适合具现化,不能实现某些操作,只要这些操作没在已经用到的函数中,就不会发生问题.
    • 模板类能够特化和部分特化.
    • 模板类能定义默认的模板类型参数.
  • 相关阅读:
    Gitee + PicGo搭建图床 & Typora上传图片到图床
    算法思维 ---- 双指针法
    Floyd's cycle-finding algorithm
    Boyer-Moore Voting Algorithm
    youtube-dl 使用小记
    算法思维 ---- 滑动窗口
    Memo
    英语
    BZOJ 3270
    BZOJ 3196
  • 原文地址:https://www.cnblogs.com/obama/p/3055229.html
Copyright © 2011-2022 走看看