现在的位置: 首页 > 综合 > 正文

模板和标准模板库

2012年12月15日 ⁄ 综合 ⁄ 共 12093字 ⁄ 字号 评论关闭

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模板类进行继承

 

 

 

 

 

 

 

抱歉!评论已关闭.