四、算法
为了处理容器内的元素,STL提供了一些标准算法,包括搜寻、排序、拷贝、重新排序、修改、数值运算等十分基本而普遍的算法。这些算法都包含在了algorithm中。
这里的算法并非面向对象思维模式,而是泛型函数式编程思维模式,这两种各有优缺点
例子:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
vector<int> coll;
vector<int>::iterator pos;
coll.push_back(2);
coll.push_back(5);
coll.push_back(4);
coll.push_back(1);
coll.push_back(6);
coll.push_back(3);
pos = min_element(coll.begin(), coll.end());
cout << "min:" << *pos << endl;
pos = max_element(coll.begin(), coll.end());
cout << "max:" << *pos << endl;
sort(coll.begin(), coll.end());
pos = find(coll.begin(), coll.end(), 4);
reverse(pos, coll.end());
for (pos = coll.begin(); pos!=coll.end(); ++pos) {
cout << *pos << ' ';
}
cout << endl;
}
1、区间
由上述例子可以看出,这些算法都用来处理“区间”的元素,这样的接口要求调用者必须确保经由两参数定义出来的区间是有效的(属于同一容器,前后放置正确,从起点出发逐一前进能到达终点)
所有算法处理的是前闭后开区间。
2、处理多个区间
有数个算法可以处理多个区间,通常必须设定第一个区间的起点和终点,至于其他区间,只需设定起点即可,例如:
if(equal(coll1.begin(), coll1.end(), coll2.begin())){
...
}
对于下面这个程序:
#include <iostream>
#include <vector>
#include <list>
#include <algorithm>
using namespace std;
int main()
{
list<int> coll1;
vector<int> coll2;
for (int i = 1; i<=9; ++i) {
coll1.push_back(i);
}
//coll2.resize(coll1.size());
copy(coll1.begin(), coll1.end(), coll2.begin());
}
这个程序会出错,因为coll2没有确保有足够的空间。
五、manipulating algorithms(更易型算法)
会改变目标区间的内容的算法
1、移除元素remove()
移除元素后不会改变原容器的大小,即原容器的起点和终点不变,而是返回一个容器的终点
可以调用erase()删除返回的终点与原终点之间的全部元素,但是通常无需移除
2、更易型算法和关联式容器
由于关联式容器是有序的,如果使用更易型算法就会破坏掉其结构,故编译器会报错。
如果要更改关联式容器,可以调用容器的成员函数实现
3、算法 vs 成员函数
一般算法对容器具有普适性,而成员函数仅对单一容器有效。如何选择看实际情况。
六、以函数作为算法的参数
1、例子:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void print(int elem)
{
cout << elem << ' ';
}
int main()
{
vector<int> coll;
for (int i = 1; i<=9; ++i) {
coll.push_back(i);
}
for_each(coll.begin(), coll.end(), print);
cout<<endl;
}
2、判断式(predicates)
Unary Predicates
#include <iostream>
#include <list>
#include <algorithm>
#include <cstdlib>
using namespace std;
bool isPrime(int number)
{
number = abs(number);
if (number == 0||number == 1)
{
return true;
}
int divisor;
for(divisor = number/2;number%divisor!=0;--divisor)
{
;
}
return divisor == 1;
}
int main()
{
list<int> coll;
for (int i = 24; i<=30; ++i)
{
coll.push_back(i);
}
list<int>::iterator pos;
pos = find_if(coll.begin(), coll.end(), isPrime);
if(pos!=coll.end())
{
cout << *pos << " is first prime number found" << endl;
}
else
{
cout<<"no";
}
}
find_if()算法在给定区间内寻找“被传入的一元判断式”运算结果为true的第一个元素。
七、Functors, Function Objects
1、什么是Function Objects
任何东西,只要其行为相函数,它就是个函数。因此,如果你定义了一个对象,行为像函数,它就可以当做函数使用。(不太懂,往后看- -)
所谓函数行为,是指可以“使用小括号传递参数,藉以调用某个东西” 例如:
function(arg1, arg2);
如果指望对象也可以如此这般,就必须让他们也有可能被“调用”——通过小括号的运用和参数的传递。
例子:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class PrintInt{
public:
void operator()(int elem)const{
cout<<elem<<' ';
}
};
int main()
{
vector<int> coll;
for(int i = 1; i<=9; ++i){
coll.push_back(i);
}
for_each(coll.begin(), coll.end(), PrintInt());
cout<<endl;
PrintInt()(5); //这样看起来好奇怪但是只能这样用,无法使用PrintInt(5)
cout<<endl;
//PrintInt::operator()(6); //无法使用
}
这样看起来很复杂,但是比起一般函数有以下优点:
(1)、function objects是”smart functions”
function objects可以拥有成员函数和成员变量,这意味着它拥有状态。
(2)、每个function objects都有自己的类型
一般函数,唯有在它们的标记式(signatures)不同时,才算类型不同,而function objects 即使标记式相同,也可以由不同的类型。事实上,由function objects定义的每一个函数行为都有自己的类型,。这对于“利用template实现泛型程序编写”乃是一个卓越贡献,因为如此一来,我们便可以将函数行为当做template 参数来运用。这使得不同类型的容器可以使用同类型的function objects作为排序准则。这可以确保你不会在排序准则不同的群集之间赋值,合并或比较。你甚至可以设计function objects继承体系,以此完成某些特别的事情,例如在一个总体原则下确立某些特殊情况。
(没太懂,纯复制了一下)
(3)、比一般函数速度快
就template概念而言,由于更多细节在编译期就已确定,所以可能会获得更好地性能。
例子:
#include <iostream>
#include <list>
#include <algorithm>
using namespace std;
class AddValue{
private:
int theValue;
public:
AddValue(){
}
AddValue(int v):theValue(v){
}
void operator()(int &elem) const{
elem+= theValue;
}
};
class PrintInt{
public:
void operator()(int elem) const{
cout<<elem<<' ';
}
};
int main()
{
list<int> coll;
for(int i = 1; i<=9; ++i){
coll.push_back(i);
}
for_each(coll.begin(), coll.end(), PrintInt());
cout<<endl;
for_each(coll.begin(), coll.end(), AddValue(10));
for_each(coll.begin(), coll.end(), PrintInt());
cout<<endl;
auto addValue = AddValue(11);
for_each(coll.begin(), coll.end(), addValue);
for_each(coll.begin(), coll.end(), PrintInt());
cout<<endl;
}
由例子可以看出,该类创建一个函数对象(function objects),并赋初值,返回该function objects, 该返回值相当于一个函数。
3、库中也有一些预先定义好的function objects 模板
八、容器内的元素
1、容器元素的条件
(1)、必须可以通过copy构造函数进行复制,副本与原本必须相等
所有容器都会在内部生成一个元素副本,并返回该暂时性副本,因此copy构造函数会被频繁地调用,所以copy构造函数性能应该尽可能优化。
(2)、必须可以通过assignment操作符完成复制操作
(3)、必须可以通过析构函数完成销毁操作。当容器元素被移除,它在容器内的副本将被销毁,因此析构函数绝不能被设计为private.
以下的几个条件,也应该满足
(1)、对于序列式容器,元素的default构造函数必须可用(无参构造函数)
(2)、对于某些动作,必须定义operator == 以执行相等测试
(3)、在关联式容器中,元素必须定义出排序准则,缺省情况下是operator<
2、Value语意 vs Reference语意(深拷贝构造 vs 浅拷贝构造)
容器建立元素副本时,会调用构造。
STL只支持Value语意,即深拷贝构造
九、STL内部的错误处理和异常处理
错误是无法避免的,可能是程序引起的逻辑错误,也可能是程序运行时的环境或背景所引起的执行期错误,这两种错误都能被异常机制处理。
1、错误处理
STL的设计原则是效率优先,安全次之,错误检查相当花时间,所以几乎没有。(可以自加一层包装使用)
使用STL必须满足一下要求
(1)、迭代器必须合法且有效,例如使用前初始化
(2)、不要对.end()执行*操作
(3)、区间必须合法
(4)、覆盖动作中的目标区间必须拥有足够的元素。
2、异常处理
STL只做了初步的最基本的异常处理机制