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