模板和标准模板库
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模板类进行继承