zoukankan      html  css  js  c++  java
  • 模板和标准模板库

    template<class T>

    class Array{

      //...

    };

    T就是类参数,T前面的关键字是class或typename。

    类参数是个符号,代表通过关键字class或typename将要被某个确定类型代替的类型

    template和<之间可以有一个空格,但是通常不要有

    我们将Array<double>和Array<char>称为模板类Array<T>的模板实例

    不可能存在一个类型为Array或Array<T>的对象,但我们可以定义类型为Array<int>的对象。也就是说,一个对象不能属于像Array这样的模板类,但可以属于Array<char>

    这样的模板类实例

    用内建或自定义的数据类型都可以创建模板实例

    模板类可以作为一种数据类型出现在参数列表中

    template<class T>

    ostream & operator<<(ostream& os,const Array<T>& ar) {

      for(int i = 0 ;i < ar.get_size() ; i ++)

         os << ar[i] << endl;

      return os;

    }

    模板类必须至少有一个类参数,当然可以有多个类参数。模板类还可以有非类参数的参数。一般称之为函数类型参数(无类型模板参数),一个模板类可以有多个函数类型参数,这些参数的数据类型可以使内建类型或自定义类型参数

    template<class T,int x,int y>

    class Sample {

    };

    用具体的数据类型代替模板头中的类参数,并用具体的数值代替模板头中的函数类型参数,就可以实例化一个模板类,所以模板类有时称为参数化的类

    模板类中的成员函数可以以内联函数的形式定义

    模板类中的函数类型参数是类参数中除了class或typename定义的类型

    例子:

    头文件stack.h:

    #include <iostream>
    #include <string>
    #include <cassert>
    using namespace std;

    template<class T>
    class Stack {
    public:
       enum {DefaultStack=50,EmptyStack=-1};
       Stack();
       Stack(int);
       ~Stack();
       void push(const T&);
       T pop();
       T topNoPop() const;
       bool empty() const;
       bool full() const;
    private:
       T *elements;
       int top;
       int size;
       void allocate() {
          elements = new T[size];
          top = EmptyStack;
       }
       void msg(const char *m) const {
          cout << "*** " << m << " ***" << endl;
       }
       friend ostream& operator<<(ostream&,const Stack<T> &);
    };

    template<class T>
    Stack<T>::Stack() {
       size = DefaultStack;
       allocate();
    }

    template<class T>
    Stack<T>::Stack(int s) {
       if(s < 0)
          s *= -1;
       else if( 0 == s)
          s = DefaultStack;
       size = s;
       allocate();
    }

    template<class T>
    Stack<T>::~Stack() {
       delete[] elements;
    }

    template<class T>
    void Stack<T>::push(const T& e) {
       assert(!full());
       if(!full())
          elements[++top] = e;
       else
          msg("Stack full!");
    }

    template<class T>
    T Stack<T>::pop() {
       assert(!empty());
       if(!empty())
          return elements[top --];
       else {
          msg("Stack empty!");
          T dummy_value = 0;
          return dummy_value;
       }
    }

    template<class T>
    T Stack<T>::topNoPop() const {
       assert(top > EmptyStack);
       if(!empty())
          return elements[top];
       else {
          msg("Stack empty!");
          T dummy_value = 0;
        return dummy_value;
       }
    }

    template<class T>
    bool Stack<T>::empty() const {
       return top <= EmptyStack;
    }

    template<class T>
    bool Stack<T>::full() const {
       return top+1 >= size;
    }

    template<class T>
    ostream& operator<<(ostream& os,const Stack<T> &s) {
       s.msg("Stack conents:");
       int t = s.top;
       while(t > s.EmptyStack)
          cout << s.elements[t--] << endl;
       return os;
    }

    测试文件:

    结果:

    标准模板库(Standard Template Library)STL是标准C++库的一部分。STL的模板类为C++提供了完善的数据结构

    容器、算法、和迭代器是STL的三个基本组成部分。

    一个STL容器是对象的集合。

    STL包括vector、stack、queue、deque、list、set和map等

    STL算法是对容器进行处理的函数,例如:copy、sort、search、merge和permute等

    STL迭代器是一访问器中的一种机制,一次访问一个对象

    STL的迭代器可分为 正向、反向、双向和随机遍历

    和C++数组不同,STL容器的大小自动变化。

    STL基本容器可以分两组:序列式容器和关联式容器

     例子:vector容器

    vector支持两端插入,并提供begin和end成员函数,分别用来访问头部和尾部元素。两个函数都返回一个可随机访问的迭代器

    begin:

    如果容器不为空,指向容器的第一个元素

    如果容器为空,指向容器尾部之后的位置

    end:

    仅指向容器尾部之后的位置

    vector和deque的区别主要在于他们的底层实现不同,特别是插入和删除操作的实现机制不同。

    对于vector来说,不管其大小时多少,在头部插入的效率总是比在尾部插入的效率低。在尾部插入将耗费固定的时间。在头部插入时,耗时与vector大小乘正比。

    deque与vector不同,不管插入还是删除操作,也不管这些操作是头部还是在尾部进行,算法效率是固定的

    例子:list容器

    list有两种迭代器:

    list<type>::iterator

    list<type>::const_iterator

    type是我们要定义的类型

    vector、deque和list

    三者都是STL的基本序列式容器:都有insert和erase成员函数

    vector和deque都重载了[]而list没有,因此我们用list时要使用迭代器

    另外效率上也有差别:

    STL的关联式容器:set和map

    set是一种集合,其中包含0个或多个不重复的和不排序的元素,这些元素被称为键值

    例子:(set)

    set重载了而多个insert成员函数,我们既可以向vector那样采用指定插入位置,也可以不指定

    s.insert(s.begin(),66);

    s.insert(s.end(),99);

    s.insert(33);

    不论采用哪种重载方式,insert均能保证在set中不含重复的键

    find是set中另一个常用的函数用来查看set中是否包含某个指定的键值,如果存在返回指向该键的迭代器,否则返回end成员函数的值

    例子:(map)

    map也定义了insert函数,map还重载了[]操作符

    容器适配器是基本容器的衍生物,并利用基本容器来实现其特定功能。

    容器适配器有三种:stack,queue和priority_queue

    stack适配器用于创建LIFO列表。queue适配器用于创建FIFO列表

    priority_queue用于创建带优先级的队列,其元素以某种优先顺序进行删除

    例子:(stack)

    默认情况下STL stack衍生自deque因此

    stack<char> s;

    等价于

    stack<char, deque<char> > s;

    如果需要让stack从vector衍生,可采用如下定义方式:

    stack<char , vector<char> > s;

    stack中的

    top函数返回栈顶元素

    pop函数删除栈顶元素

    两个通常配对使用top一个元素判断后,再pop

    例子:(queue)

    和stack相同,默认情况下,queue衍生自deque。

    queue也有push和pop成员函数

    同时提供了front成员函数,用来访问queue头元素

    例子: (priority_queue)

    在默认情况下,priority_queue衍生自vector

    优先队列是指该队列以某种优先级顺序删除队列中的元素。例如,一个整数优先级队列可能以降序方式来删除元素。这样就保证了在删除元素的过程中保持队列元素的优先级顺序

    priority_queue使用我们熟悉的push和pop成员函数来插入和删除。top返回不删除

    我们熟悉的string类以及bitset类也可以做STL容器来使用:

    例子:(bitset)

    bitset是二进制序列。bitset并不是数学意义上的集合,因为它可以包含重复的数字(毕竟只有0和1)

    由于bitset默认构造函数将所有的位都清0,

    但是可以用转型构造函数初始一个bitset的值 bitset(unsigned long n);

    bitset<8> bs(9) 就创建一个00001001的bitset

    bitset可完成如下功能

    1>将某个指定位设置为0或1

    2>对某个位或所有位取反

    3>测试某个位为0或1

    4>进行左移或右移操作

    5>进行与、或和异或等标准二进制操作

    6>将bitset转型为string或unsigned long类型

    #include <iostream>
    #include <string>
    #include <bitset>
    using namespace std;
    const featureCount = 9;
    const unsigned Framed = 1;
    const unsigned Bordered = 2;
    const unsigned StdMenu  = 4;
    const unsigned ToolBar   = 8;
    const unsigned StatusBar= 16;

    class Window {
    public:
       Window(const string &n,unsigned f) {
          name = n;
          features = bitset<featureCount>(f);
          createWindow();
       }
       Window(const char *n ,unsigned f) {
          name = n;
          features = bitset<featureCount>(f);
          createWindow();
       }
    private:
       void createWindow() {
          cout << "\n***  Windows feature for " << name 
            << " given bit mask " << features << ":" << endl;
        if(features[0])
           cout << "\t" << "framed" << endl;
        if(features[1])
           cout << "\t" << "bordered" << endl;
        if(features[2])
           cout << "\t" << "with standard menu" << endl;
        if(features[3])
           cout << "\t" << "with tool bar" << endl;
        if(features[4])
           cout << "\t" << "with status bar" << endl;
       }
       string name;
       bitset<featureCount> features;
    };

    int main() {
       Window w1("w1",Framed | ToolBar | StatusBar);
       Window w2("w2",ToolBar | Framed | StatusBar);
       Window w3("w3",Framed | StdMenu | StatusBar | ToolBar | Bordered );
       return 0;
    }

    结果:

    STL有大量用来处理容器的算法。这些算法可以分为如下几类:排序和搜索、数值处理、集合运算、复制等

    STL算法是用模板函数实现的,如STL的reverse算法:

    template<class BidirectionalIterator >

    void reverse(BidirectionalIterator it1,BidirectionalIterator it2);

    STL算法使用迭代器来变量容器,并在迭代过程中处理容器中的元素:

    例子1:

    例子2:

    STL算法nth_element将序列中的中值元素(median elememt)放置到序列的中间位置。仅仅是找到并放到中间位置,不会对序列排序,结果我们可以看到

    nth_element有三个参数,第一个迭代器标志序列头部,第二个要放置的位置,最后一个迭代器标志序列的尾部

    STL的copy算法三个参数,前两个是迭代器的范围,最后一个是输出型迭代器:

    输出迭代器实例:ostream_iterator<char>

    表达式ostream_iterator<char>(cout," ")调用带参数的构造函数来创建迭代器,这个构造函数的第一个参数为输出流,本来为cout,第二个参数是可选的,用来指定输出到输出流的各项之间的分隔符,本例为空格符

    输出迭代器例子:

    STL除了上面的还有其他构件:

    函数对象(function object)、函数适配器(function adaptor) 和 STL alocator

    函数对象是一个类对象,它对函数调用操作符() 进行重载,并且该重载函数是公有的。可用STL的函数对象来代替普通函数

    上面的dump函数:

    void dump(int i) { cout << i  << endl;}

    在该程序中将dump作为for_each第三个参数

    for_each(v.begin(),v.end(),dump);

    作为另一种实现方式,我们可以设计一个函数对象:

    template<clasls T>

    struct dumpIt {

      void operator()(T arg) { cout << arg << endl; }

    };

    然后调用这个模板类的int型实例的重载调用操作符

    for_each(v.begin(),v.end(),dumpIt<int>());

    由于在默认的情况下结构的成员是公有的,因此我们使用关键字struct来创建dumpIt类。之所以将函数对象的函数调用操作符设计为公有的,是因为要在函数对象的定义之外使用它

    一个函数对象的开销比指针传递函数的开销小,所以函数对象通常比常规函数执行速度更快

    函数适配器是用来以现有函数对象创建新函数对象的构件。因此函数适配器类似于容器适配器。

    有多种函数适配器,包括函数对象求反,在函数对象的重载函数调用操作符中绑定常数和参数,把函数指针转换成函数对象,以及构造现有函数对象之外的新函数对象

    两种内建STL函数对象类型为unary_function和binary_funciton

    函数对象unary_function第一个模板参数是重载函数调用操作符的参数类型此处为unsigned

    第二个模板参数是操作符返回类型,此处为bool

    not1(1代表一元)

    STL allocator是一个模板类,用于内存管理。

     看一个大例子:

    #include <iostream>
    #include <fstream>
    #include <deque>
    #include <algorithm>
    #include <string>
    using namespace std;

    const string inFile = "stockData.dat";
    const string Unknown = "????";

    class Stock {
    public:
       Stock() {
          symbol = Unknown;
          open = close = gainLoss = volume = 0;
       }
       Stock(const string &s,double o,double c,unsigned long v) {
          symbol = s;
          open = o;
          close = c;
          volume = v;
          gainLoss = (close - open) / open;
       }
       const string & getSymbol() const {
          return symbol;
       }
       double getOpen() const {
          return open;
       }
       double getClose() const {
          return close;
       }
       unsigned long getVolume() const {
          return volume;
       }
       double getGainLoss() const {
          return gainLoss;
       }
    private:
       string symbol;
       double open;
       double close;
       double gainLoss;
       unsigned long volume;
    };

    struct winCmp {
        bool operator()(const Stock & s1,const Stock & s2) const {
            return s1.getGainLoss() > s2.getGainLoss();
        }
    };

    struct volCmp {
       bool operator()(const Stock& s1, const Stock & s2) const {
          return s1.getVolume() > s2.getVolume() ;
       }
    };

    void output(bool volFlag,
            const string& name,
            const char * openLabel,double open,
            const char * closeLabel,double close,
            const char * gainLabel,double gain,
            const char * volLabel, unsigned long vol) {
              cout << "*** " << name << endl;
              if(volFlag)
                 cout << '\t' << volLabel << vol << endl;
              cout << '\t' << gainLabel << gain << endl
                  << '\t' << openLabel << open << endl
                  << '\t' << closeLabel << close << endl;
              if(!volFlag)
                 cout << '\t' << volLabel << vol << endl;

    }

    struct winPr {
       void operator() (const Stock & s ) const {
          output(false,
                s.getSymbol(),
                "Open Price:    ",s.getOpen(),
                "Closing Price: ",s.getClose(),
                "% Changed:     ",s.getGainLoss() * 100,
                "Volume:        ",s.getVolume() );
       }
    };

    struct volPr {
       void operator() (const Stock & s) const {
          output( true,
              s.getSymbol(),
              "Opening Price:     ",s.getOpen(),
              "Closing Price:     ",s.getClose(),
              "% Changed:         ",s.getGainLoss() * 100,
              "Volume:            ",s.getVolume() );
       }
    };

    void herald(const char *);
    void input(deque<Stock> &);

    int main() {
       deque<Stock> stocks;
       input(stocks);

       herald("Gainers in descending order: ");
       sort(stocks.begin(),stocks.end(),winCmp());
       for_each(stocks.begin(),stocks.end(),winPr());
     
       herald("Loser in asceding order: ");
       for_each(stocks.rbegin(),stocks.rend(),winPr());
     
       herald("Volume in descending order: " );
       sort(stocks.begin(),stocks.end(),volCmp());
       for_each(stocks.begin(),stocks.end(),volPr());

       return 0;
    }

    void input(deque<Stock> & d) {
       string s ;
       double o,c,v;
       ifstream input(inFile.c_str());
       while(input >> s >> o >> c>> v)
          d.insert(d.end(),Stock(s,o,c,v));
       input.close();
    }

    void herald(const char *s) {
       cout << endl << "******* " << s <<endl;
    }

     结果:

    完整结果文件输出:


    ******* Gainers in descending order:
    *** COHU
     % Changed:     5.17382
     Open Price:    48.9
     Closing Price: 51.43
     Volume:        134900
    *** ALCD
     % Changed:     2.46607
     Open Price:    60.42
     Closing Price: 61.91
     Volume:        230000
    *** GMGC
     % Changed:     1.81159
     Open Price:    2.76
     Closing Price: 2.81
     Volume:        129400
    *** MSFT
     % Changed:     1.55296
     Open Price:    135.87
     Closing Price: 137.98
     Volume:        8301700
    *** ZTEC
     % Changed:     -0.784016
     Open Price:    39.54
     Closing Price: 39.23
     Volume:        100300
    *** TMXI
     % Changed:     -8.59873
     Open Price:    3.14
     Closing Price: 2.87
     Volume:        255000
    *** BUTI
     % Changed:     -13.8286
     Open Price:    8.75
     Closing Price: 7.54
     Volume:        159000
    *** EPEX
     % Changed:     -17.3342
     Open Price:    15.98
     Closing Price: 13.21
     Volume:        54000

    ******* Loser in asceding order:
    *** EPEX
     % Changed:     -17.3342
     Open Price:    15.98
     Closing Price: 13.21
     Volume:        54000
    *** BUTI
     % Changed:     -13.8286
     Open Price:    8.75
     Closing Price: 7.54
     Volume:        159000
    *** TMXI
     % Changed:     -8.59873
     Open Price:    3.14
     Closing Price: 2.87
     Volume:        255000
    *** ZTEC
     % Changed:     -0.784016
     Open Price:    39.54
     Closing Price: 39.23
     Volume:        100300
    *** MSFT
     % Changed:     1.55296
     Open Price:    135.87
     Closing Price: 137.98
     Volume:        8301700
    *** GMGC
     % Changed:     1.81159
     Open Price:    2.76
     Closing Price: 2.81
     Volume:        129400
    *** ALCD
     % Changed:     2.46607
     Open Price:    60.42
     Closing Price: 61.91
     Volume:        230000
    *** COHU
     % Changed:     5.17382
     Open Price:    48.9
     Closing Price: 51.43
     Volume:        134900

    ******* Volume in descending order:
    *** MSFT
     Volume:            8301700
     % Changed:         1.55296
     Opening Price:     135.87
     Closing Price:     137.98
    *** TMXI
     Volume:            255000
     % Changed:         -8.59873
     Opening Price:     3.14
     Closing Price:     2.87
    *** ALCD
     Volume:            230000
     % Changed:         2.46607
     Opening Price:     60.42
     Closing Price:     61.91
    *** BUTI
     Volume:            159000
     % Changed:         -13.8286
     Opening Price:     8.75
     Closing Price:     7.54
    *** COHU
     Volume:            134900
     % Changed:         5.17382
     Opening Price:     48.9
     Closing Price:     51.43
    *** GMGC
     Volume:            129400
     % Changed:         1.81159
     Opening Price:     2.76
     Closing Price:     2.81
    *** ZTEC
     Volume:            100300
     % Changed:         -0.784016
     Opening Price:     39.54
     Closing Price:     39.23
    *** EPEX
     Volume:            54000
     % Changed:         -17.3342
     Opening Price:     15.98
     Closing Price:     13.21

     模板类和继承:

      一个模板类可以从另一个模板类或非模板类派生而来,模板类或模板类实例都可以作为基类,而它们的派生类既可以是模板类,也可以使非模板类

    1>

    class B {

      // ...

    };

    template<class T>

    class TD : public B {

      // ...

    };

    2>

    template<class T>

    class TB {

      // ...

    };

    class D : public TB<int> {

      // ...

    };

    template<class T>

    class TB {

      // ...

    };

    template<class T>

    class D : public TB<T> {

      // ...

    };

    还可以对STL模板类进行继承

  • 相关阅读:
    Linux 全盘备份恢复工具(Clonezilla)
    Dremel琢美电磨机配件指南
    智能设备常用电机入门指南(实物+原理图)
    硬件入门 之 20种视频接口线
    【Spring源码分析】Bean加载流程概览
    面试题:Spring为什么默认bean为单例?
    @Autowired @Resource @Qualifier的区别
    Spring的接口InitializingBean、BeanPostProcessor以及注解@PostConstruct、bean的init-method的执行先后顺序
    Spring Bean的生命周期(非常详细)
    spring中BeanFactory和FactoryBean的区别
  • 原文地址:https://www.cnblogs.com/lfsblack/p/2710256.html
Copyright © 2011-2022 走看看