STL ~= 容器 + 算法 + 迭代器。容器用于容纳数据,算法用于处理数据,迭代器像胶水一样将容器和算法紧密地结合在一起。算法通过迭代器来定位和操控容器中的元素,事实上,每个容器都有自己的迭代器,只有容器自己才知道如何访问自己的元素,这使得迭代器就像一个指向容器中元素的普通指针一样。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
//工资统计,计算收入大于1000的员工人数
int main()
{
vector<int> vecSalary;
int put;
do
{
cin>>put;
if(0 == put)
break;
vecSalary.push_back(put);
}while(true);
int total = count_if(vecSalary.begin(), vecSalary.end(), bind2nd(greater<int>(), 1000)); //count_if:返回区间中满足指定条件的元素数目。
cout<<total<<endl;
return 0;
}
容器vector的使用不像数组那样固定分配内存,其可以根据用户的输入动态地增长,保证内存空间的合理利用,又可以让程序具有很大的可扩张性,处理很大范围内的数据。由此可得到STL的主要几个好处:封装很多常用的数据结构(容器),提供很多常用的通用算法(算法),调试程序更加安全和方便,支持跨平台(使用STL的C++代码可以轻松地移植到其他平台上)。模板是实现代码重用的一种重要机制,它可以实现类型的参数化,把类型定义为参数,而STL正是借助模板的威力建构起来的。
函数模板
如果说把函数比作一个箱子,每个箱子都是专用的(实现具体的不同功能),那函数模板就是一个万能箱子,它可以用来装各种东西,处理各种类型的数据。比如当编译器发现一个函数模板的调用后,将根据实参的实际数据类型来确认是否匹配函数模板中对应的形参,然后生成一个重载函数,称该重载函数为模板函数。根据参数类型的不同,一个函数模板可以随时变成各种不同的重载函数,从而实现对各种数据类型的处理。函数模板可以用来创建一个通用功能的函数,以支持各种不同的数据类型,简化重载函数的设计。函数模板的声明与使用如下:
#include <iostream>
using namespace std;
//模板函数范例
template <typename T> //type理解为模板函数中的一个参数,代表抽象的数据类型
T max(const T a, const T b)
{
return a>b ? a : b;
}
int main()
{
int a = 10;
int b = 20;
int c = std::max<int>(a, b); //注意写法,不加std::,会error
cout<<c<<endl; //output: 20
float aa = 0.3;
float bb = 0.4;
float cc = std::max(aa, bb); //max<float>的<...>不写编译器会自动分析生成相应的重载函数,但是有时候必须要指明类型参数以满足特殊需求,比如max<string>
cout<<cc<<endl; //output: 0.4
string aaa = "abcd";
string bbb = "cde";
string ccc = std::max(aaa, bbb);
cout<<ccc<<endl; //output: cde,这里输出cde,不是返回最长的而是返回字符较大的,所以如果需要应该对模板函数进行特化,实现特定类型的模板函数
return 0;
}
特化模板函数,实现特定类型的string模板函数,希望返回最长的字符串:
#include <iostream>
using namespace std;
//特化函数模板string范例
template <typename T> //普通函数模板
T max(const T a, const T b)
{
return a>b ? a : b;
}
template <> //特化string的函数模板
string max<string>(const string a, const string b) //注意这里及以上的写法格式
{
return a.size()>b.size() ? a : b;
}
int main()
{
int a = 5;
int b = 4;
float c = 5.3;
float d = 4.4;
cout<<std::max(a, b)<<endl<<std::max(c, d)<<endl; //output: 5 5.3
string x = "chen";
string y = "aisda";
cout<<std::max(x, y)<<endl; //用普通函数模板output: cde,返回字符较大的
cout<<std::max<string>(x, y)<<endl; //用特化函数模板output: chen,返回字符串最长的
return 0;
}
类模板
#include <iostream>
using namespace std;
//特化类模板范例
//定义一个比较两个数的类模板compare<T>
template <typename T> //typename所定义的标识符实际上就是类模板的参数,模板参数可以是一个可以是多个
class compare
{
public:
compare(T a, T b)
: m_a(a), m_b(b)
{
}
public:
T min()
{
return m_a > m_b ? m_b : m_a;
}
T max()
{
return m_a > m_b ? m_a : m_b;
}
private:
T m_a;
T m_b;
};
int main()
{
compare<int> intcompare(2, 3);
cout<<intcompare.max()<<" > "<<intcompare.min()<<endl;
compare<float> floatcompare(2.2, 4.4);
cout<<floatcompare.max() <<" > "<<floatcompare.min()<<endl;
return 0;
}
/*output: 3 >2 4.4 > 2.2*/
模板的实例化
模板的实例化分为:隐式实例化和显式实例化。
compare<int> intcompare(2, 3); //隐式实例化
大多数都采用隐式实例化,具体的显式实例化如果需要时自行再查,注意的是显式实例化类时,所有的类成员也必须实例化。
用模板实现自己的通用算法
#include <iostream>
using namespace std;
/*软件谁集中,通常有一个撤销undo和恢复redo的通用功能*/
/*动作容器模板类,使用T作为模板参数*/
template <class T>
class actioncontainer
{
public:
actioncontainer()
{
m_nredo = 0;
m_nundo = 0;
}
void add(T value); //向容器中添加新动作
T redo(); //恢复上一步动作
T undo(); //撤销上一步动作
private:
int m_nredo;
int m_nundo;
const static int ACTION_SIZE = 5;
T m_redoAction[ACTION_SIZE]; //使用数组记录恢复和撤销的动作,数组元素的类型也是模板参数,它在类模板实例化时才确定
T m_undoAction[ACTION_SIZE];
};
/*模板类的成员函数实现*/
//向容器中添加动作,这里的T可以是一个基本数据类型,也可以是一个自定义类型
template <class T>
void actioncontainer<T>::add(T value)
{
//判断容器中的动作数目是否超过容器的容量
if(m_nundo >= ACTION_SIZE)
{
m_nundo = ACTION_SIZE - 1;
for(int i=0; i<ACTION_SIZE; ++i)
m_undoAction[i] = m_undoAction[i+1]; //将容器中已有的动作前移一个位置
}
//将新动作添加到容器中
m_undoAction[m_nundo++] = value;
}
//撤销上一步动作
template <class T>
T actioncontainer<T>::undo()
{
m_redoAction[m_nredo++] = m_undoAction[--m_nundo]; //将撤销的动作复制到恢复数组中
return m_undoAction[m_nundo]; //返回撤销的动作
}
//恢复上一步动作
template <class T>
T actioncontainer<T>::redo()
{
m_undoAction[m_nundo++] = m_redoAction[--m_nredo]; //将撤销的动作复制到恢复数组中
return m_redoAction[m_nredo]; //返回撤销的动作
}
int main()
{
actioncontainer<int> intaction; //定义一个int类型的动作容器,这样这个动作容器就可以容纳int类型的数据
intaction.add(1); //向容器中添加新的动作,也就是整数
intaction.add(2);
intaction.add(3);
intaction.add(4);
int nundo = intaction.undo(); //撤销上一步动作,这时,nundo的值为4
nundo = intaction.undo();//再次撤销身上一步动作,nundo的值为3
int nredo = intaction.redo(); //恢复上一步动作,这时,nredo的值为3
nredo = intaction.redo();//再次恢复身上一步动作,nredo的值为4
return 0;
}
以上代码定义了一个类模板actioncontainer<T>,这个类可以容纳动作(add)并且能够进行撤销(undo)和恢复(redo)动作。在这个模板类的内部,使用了两个数组来分别记录撤销和恢复的动作。这里的动作实际上是广义上的一种数据类型,它可以是一个基本数据类型比如int float,也可以是自己定义的数据类型,这样就使得这个动作容器具有广泛的通用性,可以处理各种数据类型。在这个动作容器类模板actioncontainer<T>中,还定义了它的成员函数,用于操作这个容器中的数据,其中包括添加新的动作到容器中、撤销或恢复动作等。当然,这里成员函数同样是对抽象的模板参数进行操作,其中并不涉及具体的数据类型,也就是说,这些操作与具体的数据类型无关。