组件:

  容器:管理某类对象的集合。

  迭代器:用来在一个对象集合内遍历元素。

  算法:处理集合内的元素。


容器

  • 序列式容器:顾名思义。array,vector,deque,list 和 forward_list(c++11)。
  • 关联式容器:已排序集合。元素位置取决于value(或者key -- 如果元素是个key--value 对)和给定的某个排序准则。set,multiset,map 和 multimap。
  • 无序容器:无序集合,每个元素的位置无关紧要,唯一重要的是某特定元素是否位于此集合内。值或安插顺序都不能影响元素的位置,元素的位置有可能在容器生命中被改变。unordered_set, unordered_multiset, unordered_map 和 unordered_multimap。 unordered容器在TR1中引入。

序列式容器常被时限为array或者linked list 。

关联式容器常被实现为binary tree。

无序容器常被实现为hash table。

序列式容器

  重点提一下 TR1中新加入的array。

  一个array对象乃是在某个固定大小的array内管理元素。因此,不可以改变元素个数,只能改变元素值。必须在建立时就指明其大小。array允许随机访问,只要有索引。包含在头<array>中

  eg:

//array container of 5 string elements
array<string,> col = {"hello","world"}; array<string,> col2; //默认情况下被default构造函数初始化,对基础类型而言,初值不明确

list:list不提供随机访问,比如要访问第10个元素,就必须走完前9个。这比vector和deque提供的摊提式常量时间,效率差太多。但优势是快速插入和删除,只需改变链接即可。

std::forward_list 是支持从容器中的任何位置快速插入和移除元素的容器。不支持快速随机访问。它实现为单链表,且实质上与其在 C 中实现相比无任何开销。与 std::list 相比,此容器提在不需要双向迭代时提供更有效地利用空间的存储。

在链表内或跨数个链表添加、移除和移动元素,不会非法化当前指代链表中其他元素的迭代器。然而,在从链表移除元素(通过 erase_after )时,指代对应元素的迭代器或引用会被非法化。

std::forward_list 满足容器 (Container) (除了 operator== 的复杂度始终为线性和 size 函数)、具分配器容器(AllocatorAwareContainer) 和顺序容器 (SequenceContainer) 的要求。

参考链接:为什么forward_list不支持push_back ,  如何使用std::forward_list 单链表

关联式容器

  • set : 元素依据value自动排序,每个元素只能出现一次,不许重复
  • multiset:和set的唯一差别就是可以重复
  • map:每个 元素都是键值对,key是排序准则的基准。每个key只准出现一次
  • multimap:和map的唯一差别就是,元素可以重复,即key可以重复

实际上所有这些关联式容器通常都由二叉树实现。multimap也可以用作字典。

无序容器

对于无序容器而言,某次插入操作可能对其他元素位置造成影响(rehashing,删除操作不会造成rehashing)。我们唯一关心的是,某个特定元素是否位于容器内。可以理解为无序容器是个袋子。

无序容器常以hash table实现出来,内部结构是一个“由linked list组成”的array。通过某个hash函数计算,确定元素落于这个array的位置。Hash函数运算的目标是:让每个元素的落点(位置)有助于快速访问任何一个元素。

但前提是hash函数本身必须足够快。但这个完美的hash函数不一定存在或者被找到,抑或是由于它造成array耗费巨额内存而显得不切实际,因此,退而求其次的hash函数可能让多个元素落在同一个位置上。所以,设计上就让array的元素再被放进一个linked list中,如此一来array的每个位置就得以存在一个以上的元素。

  无序容器优点是,查找一个带有特定值的元素,其速度肯能快过关联式容器。事实上无序容器提供的是摊提式常量复杂度,前提是有一个良好的hash函数。然而这并不简单,你可能需要提供许多内存作为bucket。

stl定义了下面这些无序容器:

  • unordered set 无需元素集合。元素不允许重复
  • unordered map 键值对。每个key唯一。
  • unordered multiset 允许元素重复
  • unordered multimap 允许key重复,可以作为字典

容器适配器

Container Adapter是预定义的容器,提供的是一定限度的接口,用于处理特殊需求。这些容器都是根据基本容器定义实现的。

  • stack 顾名思义。对元素采用LIFO管理策略。
  • Queue  FIFO策略。
  • Priority queue  期内元素拥有优先权。优先权奶水给予程序员提供的排序准则而定义的。效果类似于这样一个缓冲区:下一元素永远是容器中优先权最高的元素。

迭代器

自c++11起,可以使用range-based for循环处理所有元素。

  • Operator* 返回当前位置上的元素值,
  • Operator ++  迭代器前进到下一元素
  • operator == 和 != 判断两个迭代器是否相同
  • Operator =   对迭代器赋值

任何容器定义两种迭代器类型:

  1  container::iterator 以“读/写”模式遍历元素

  2 container::const_iterator 以“只读”模式遍历元素

cbegin() 和 cend()

  c++11起,关键字auto可以代替迭代器的精确类型 ,其可以使程序比较精简,或者当容器类型变化时,程序整体仍保持较佳的强壮性。  缺点是迭代器丧失常量性,可能引发“计划外赋值”风险。因为 auto pos = coll.begin();会使pos成为一个非常量迭代器。那么,cbegin() 和 cend() 就是返回了一个类型为const_iterator的对象。

为了避免循环中大量的拷贝行为,每个元素的copy构造函数和析构函数都被调用,可以使用const reference

std::vector<int> vec;
for(const auto& elem : vec){
cout << elem ;
}

迭代器种类

根据能力不同,迭代器被划分为5种不同的类别。STL预先定义好的所有容器,其迭代器均属于以下三种分类:

  • 前向迭代器   只能够以累加操作符向前迭代。class forward_list的迭代器就是属于此类,unordered_set , unordered_multiset ,  unordered_map 和 unordered_multimap都至少是此类。
  • 双向迭代器   递增递减都行。list,set,map, multimap,multiset提供的迭代器都属此类
  • 随机访问迭代器  不但具备双向迭代的所有属性,还具备随机访问的能力。它们提供了迭代器算术运算的必要操作符。vector,dequeue,array和string提供的迭代器都属于此类。

除此之外,stl还定义了两个类别:

  • 输入型迭代器   向前迭代时能够读取/处理 value. Input stream迭代器就是这样的例子。
  • 输出型迭代器  向前迭代时能够涂写value。Inserter和output stream迭代器都是此类。

迭代器之适配器

  Insert Iterator(安插型迭代器)

  它可以使算法以安插的方式而非覆写的方式运作。使用它可以解决算法的“目标空间不足”的问题。是的,他会促使目标区间的大小按需要成长。

#include <algorithm>
#include<iterator>
#include<list>
#include<vector>
#include<deque>
#include<set>
#include<iostream> using namespace std; int main() {
list<int> coll1 = { ,,,,,,,, }; //复制coll1的元素到coll2,by appending them
vector<int> coll2;
copy(coll1.cbegin(),coll1.cend(), //source
back_inserter(coll2)); //destination //复制coll1的元素到coll3中,by inserting them at the front,反转了元素顺序
deque<int> coll3;
copy(coll1.cbegin(),coll1.cend(), //source
front_inserter(coll3)); //destination //只有inserter工作于关联式集合
set<int> coll4;
copy(coll1.cbegin(),coll1.cend(),
inserter(coll4,coll4.begin()));
}

此例运用了三种预定义的insert iterator:

1 Back inserter(安插于容器最末端):其内部调用push_back(),在容器末端插入元素(即追加动作)。只有在提供push_back() 的容器中back inserter才能派上用场。在c++标准库中的此类容器有vector,deque,和string。

2 Front inserter(安插于容器最前端) 其内部元素调用push_front(),将元素安插与容器的最前端。c++中可用的容器为deque,list和 forward_list。

3 General inserter :一般性的inserter,作用是在“初始化时接受之第二实参”所指位置的前方插入元素。内部调用 成员函数insert(),并以新值和新位置作为实参传入。所有STL容器都提供insert()成员函数,因为,这是唯一可用于关联式容器身上的一种预定义inserter。

Stream Iterator(串流迭代器)

#include <algorithm>
#include<iterator>
#include<string>
#include<vector>
#include<iostream> using namespace std; int main() {
vector<string> coll; //read all words from the standard input
//-source :all strings until end-of-file(or err)
//-detination :coll(inserting) copy(istream_iterator<string>(cin), //start of source
istream_iterator<string>(), //end of source
back_inserter(coll)); //destination sort(coll.begin(),coll.end()); //print all elements without duplicates
//-source :coll
//-destination: 标准输出
unique_copy(coll.begin(),coll.end(), //source
ostream_iterator<string> (cout,"\n")); //destination
}

具体介绍查阅stream 迭代器

反向迭代器

move 迭代器

更易型算法

移除(Removing)元素

算法remove() 自某个区间删除元素。然而如果用它来删除容器所有的元素,行为肯定会令人吃惊。例如:

int main() {
list<int> coll;
for (int i = ; i < ; ++i) {
coll.push_front(i);
coll.push_back(i);
} //print all elements of the collection
cout << "pre: ";
copy(coll.cbegin(), coll.cend(), ostream_iterator<int>(cout, " "));
cout << endl;
//remove all elements with value 3
remove(coll.begin(),coll.end(),); cout << "post: ";
copy(coll.cbegin(),coll.cend(),
ostream_iterator<int>(cout," "));
cout << endl;
}

输出为:

pre:   6 5 4 3 2 1 0 0 1 2 3 4 5 6
post: 6 5 4 2 1 0 0 1 2 4 5 6 5 6

显然,这不符合预期。

事实上,remove() 函数返回了新的终点。可以利用该终点获得新区间,缩减后的容器大小,或者获得被删除元素的个数。下面是改进版本:

int main() {
list<int> coll;
for (int i = ; i < ; ++i) {
coll.push_front(i);
coll.push_back(i);
} //print all elements of the collection
cout << "pre: ";
copy(coll.cbegin(), coll.cend(), ostream_iterator<int>(cout, " "));
cout << endl;
//remove all elements with value 3
list<int>::iterator end = remove(coll.begin(),coll.end(),); copy(coll.begin(), end,
ostream_iterator<int>(cout, " "));
cout << endl;
//print numbers of removed elements
cout << "member of remove elements:" << distance(end, coll.end()) << endl;
coll.erase(end,coll.end()); cout << "post: ";
copy(coll.cbegin(),coll.cend(),
ostream_iterator<int>(cout," "));
cout << endl;
}

pre: 6 5 4 3 2 1 0 0 1 2 3 4 5 6
6 5 4 2 1 0 0 1 2 4 5 6
member of remove elements:2
post: 6 5 4 2 1 0 0 1 2 4 5 6

end正是“被修改集合”经过元素移除动作后的新的逻辑终点。

当然,你可以单一一句完成上述过程:

coll.erase(remove(coll.begin(),coll.end(),),coll.end());

为何算法不自己调用erase()呢,这个问题正好点出STL为获取弹性而付出的代价。通过“以迭代器为接口”,STL将数据结构和算法分离开。迭代器不过是“容器内部某一位置的抽象”。一般而言,迭代器对自己所属的容器一无所知,任何“以迭代器访问容器”的算法,都不得通过迭代器调用容器类所提供的任何成员函数。

更易Associative(关联式)和Unordered(无序)容器

更易型算法(指的是那些会移除(remove),重拍(reorder),修改(modify)元素的算法)若用于关联式容器或者无序容器,会出问题。关联式容器和无序容器不能被当作操作目标,原因很简单:如果更易维护型算法用于关联式和无序容器上,会改变某些位置上的值,进而破坏容器本身对次序的维护。为了避免累及内部次序,关联式容器和无序容器的所有迭代器均被声明为指向常量的value或key。

那如何实现从关联容器和无序容器删除元素呢? ====调用它们的成员函数。如果earse()函数。

以函数作为算法的实参

有些算法可以接受用户自定义的辅助函数,借以提高弹性和能力。这些函数将被算法内部调用。

以函数作为算法实参的实例示范

最简单的是for_each()算法,它针对区间内的每一个函数,调用一个由用户指定的函数。

void print(int elem){
cout << elem ;
}
int main(){
vector<int> coll{,,,};
for_each(coll.cbegin(),coll.cend(),
print);
cout << endl;
}

算法以数种态度来面对这些辅助函数:有的是为可有可无,有的视为必要。你可以利用它们来指定查找准则,排序准则,或定义某种操作使其在“容器元素转换至另一容器”时被调用。

int Aquare(int elem){
return elem*elem ;
}
int main(){
vector<int> coll{,,,};
vector<int> coll2;
transform(coll.cbegin(),coll.cend(), //source
back_inserter(coll2), //destination
squre); //operation
cout << endl;
}

此例是将coll的值平方,然后转移至coll2.

判断式

Predicate是一种特殊的辅助函数。会返回bool值。常被用来指定作为排序准则或者查找准则。判断式可能有一或两个操作数。

并非任何返回bool的单参函数或双参函数都是合法的判断式,stl要求,面对相同的值,predicate必须得出相同的结果。

双参数函数:

class Student{
public:
string name() const;
}; bool StudentSortCriterion(const Student& s1, const Student&s2) {
return s1.name() < s2.name();
} int main() {
deque<Student> coll;
//...
sort(coll.begin(),coll.end(),StudentSortCriterion);
}

单参数函数:略

使用Lambda

一篇小小的博文

函数对象

传递给算法的“函数型实参”不一定得是函数,可以是行为类似函数的对象。这种对象称为函数对象,或称为仿函数

定义一个函数对象

任何东西,只要其行为像函数,它就是函数。

什么才算具备函数行为呢?所谓函数行为,是指可“使用小括号传递实参,借以调用某个东西”。例如:

functon(arg1,arg2);

对象如果也要如此,只需 定义operator(),并给予合适的参数类型:

class X{
public:
return-value operator(arguments) const;
};

现在,你可以把这个class 的对象当作函数来调用了:

X fo;
...
fo(arg1, arg2); //call operator for function object fo

上述调用等同于;

fo.operator()(arg1, arg2);

一个完整的例子:

class PrintInt
{
public:
void operator() (int elem) const {
cout << elem << " ";
}
}; int main() {
vector<int> coll{,,,,,,,,}; for_each(coll.cbegin(), coll.cend(),
PrintInt());
cout << endl; }

比起寻常函数,仿函数带来如下优点:

1 函数对象是一种带状态的函数。行为像pointer 的对象我们称之为smart pointer。同理,“行为像function”的对象可以称之为“smart function”,因为它们的能力超越了operator()。函数对象可以拥有成员对象和成员变量,这意味着函数对象拥有状态。事实上,在同一时间点,相同类型的两个不同的函数对象所表述的相同技能,,可具备不同的状态,这在寻常函数中是不可能的。另一个好处是,你可以在运行期初始化它们---当然必须在它们被使用(调用)之前。

2 每个函数对象都有自己的类型。寻常函数,唯有在其签名式(signature)不同时,才算类型不同。而函数对象即使签名相同,也可以有不同的类型。事实上由函数对象定义的每一个函数行为都有其自己的类型。这对于“运用template实现泛型编程”是一个卓越贡献,因为这样一来我们便可以将函数行为当作template参数来运用。这使得不同类型的容器可以使用同类型的函数对象作为排序准则。也可以确保你不会在“排序准则不同”的集合间赋值,合并,比较。你甚至可以设计函数对象的继承体系,以此完成某些特别的事情,比如在一个总体原则下确立某些特殊的情况。

3 函数对象通常比寻常函数速度快。就template概念而言,由于更多细节在编译期就已确定,所以通常可能进行更好的优化。所以,传入一个函数对象(而非寻常函数)可能获得更好的执行效能。

下面是几个例子:

对集合内的每个元素加上一个特定的值,如果在编译期便确定这个值,可以使用寻常函数完成工作:

void add10(int& elem) {
elem += ;
} void f1() {
vector<int> coll{ ,,,, };
for_each(coll.begin(), coll.end(),
add10);
} //如果你需要数个不同的特定值,而且他们在编译期都已经确定,可以使用template template <int theValue>
void add(int& elem) {
elem += theValue;
} void f1() {
vector<int> coll{ ,,,, };
for_each(coll.begin(), coll.end(),
add<>);
}

如果在执行时期处理这个特定的值,那就麻烦了。你必须在函数被调用之前先将这个数值传给该函数。这通常会导致生成若干全局变量,让“算法的调用者”和“算法所调用的函数”都能看到和用到他们。这样不好。

如果两次用到该函数,每次用到的特定值不同,而且都是在执行期才处理那些特定值,那么寻常函数根本无能为力。要么传入一个标记,要么干脆写2个函数。你是否有过这样的经验:手握一个函数,他有个static变量用以记录状态(state),而你需要这个函数在同一时刻内有另外一个不同的状态?于是你只好拷贝整份函数定义,华为两个不同的函数。这正是先前所说的问题。(这段话本可以不抄,但抄下来下次看的时候就能直接点题)

如果使用函数对象,我们就可以写出更具智能的函数达成目的。对象可以有自己的状态,可以被正确初始化。下面是个完整的例子:

template <typename T>
inline void PRINT_ELEMENTS(const T& coll, const std::string& optstr = "") {
std::cout << optstr;
for (const auto& elem : coll) {
std::cout << elem << ' ';
}
std::cout << std::endl;
} class AddValue
{ private:
int theValue; //the value to add
public:
//初始化
AddValue(int v) : theValue(v) {
}
//函数调用
void operator()(int& elem) const {
elem += theValue;
} }; int main() {
list<int> coll{ ,,,, };
PRINT_ELEMENTS(coll, "initialized: "); for_each(coll.begin(),coll.end(),
AddValue()); PRINT_ELEMENTS(coll, "after adding 10: "); for_each(coll.begin(), coll.end(),
AddValue(*coll.begin())); PRINT_ELEMENTS(coll, "after adding first of element: ");
}

运行结果:

initialized: 1 2 3 4 5
after adding 10: 11 12 13 14 15
after adding first of element: 22 23 24 25 26

表达式AddValue(10)生出一个AddValue对象,并以10为初值。AddValue构造函数将这个值保存在成员theValue中。第二次也是这个机能。

预定义的函数对象

一个例子,作为排序准则的函数对象。Operator < 之默认排序准则乃是调用less<>,所以,如果你声明:set<int> coll; 会被扩展为set<int,less<int>> coll; 同样,反向排列这些元素也可以:set<int, greater<int>> coll;

另一个运用“预定义函数对象”的地点是STL算法。考虑下面的代码:

template <typename T>
inline void PRINT_ELEMENTS(const T& coll, const std::string& optstr = "") {
std::cout << optstr;
for (const auto& elem : coll) {
std::cout << elem << ' ';
}
std::cout << std::endl;
} int main() {
deque<int> coll = { ,,,,,,,, };
PRINT_ELEMENTS(coll, "initialized: ");
transform(coll.cbegin(), coll.cend(),coll.begin(),negate<int>());
PRINT_ELEMENTS(coll, "negated: ");
transform(coll.cbegin(),coll.cend(), //first source
coll.cbegin(), //second source
coll.begin(), //destination
multiplies<int>()); //operation
PRINT_ELEMENTS(coll, "squared: ");
}

运行结果:

initialized: 1 2 3 5 7 11 13 17 19
negated: -1 -2 -3 -5 -7 -11 -13 -17 -19
squared: 1 4 9 25 49 121 169 289 361

negate<int>() 根据class template negate<> 生成一个函数对象,将传入的int设为负。transform()算法使用此一运算,将第一集合内的所有元素转移到第二个集合。如果转移目的地是自己,那么就是对集合内的所有元素设负值。

下面的求平方则用了transform()的另一种形式,以某个给定的运算,将两集合内的元素处理后写入第三个集合。本例中三个集合实际上是同一个,所以,自身求平方。

Binder

你可以使用特殊的function adapter(函数适配器),或者所谓的binder,将预定义的函数对象和其他数值结合为一体。一个完整的例子:

标准模板库(STL)的更多相关文章

  1. 标准模板库(STL)学习探究之stack

    标准模板库(STL)学习探究之stack queue priority_queue list map/multimap dequeue string

  2. 标准模板库(STL)学习探究之vector容器

    标准模板库(STL)学习探究之vector容器  C++ Vectors vector是C++标准模板库中的部分内容,它是一个多功能的,能够操作多种数据结构和算法的模板类和函数库.vector之所以被 ...

  3. C++ 标准模板库(STL)

    C++ 标准模板库(STL)C++ STL (Standard Template Library标准模板库) 是通用类模板和算法的集合,它提供给程序员一些标准的数据结构的实现如 queues(队列), ...

  4. STL学习系列之一——标准模板库STL介绍

    库是一系列程序组件的集合,他们可以在不同的程序中重复使用.C++语言按照传统的习惯,提供了由各种各样的函数组成的库,用于完成诸如输入/输出.数学计算等功能. 1. STL介绍 标准模板库STL是当今每 ...

  5. 标准模板库--STL

    标准模板库STL 1.泛型程序设计 C++ 语言的核心优势之一就是便于软件的重用 C++中有两个方面体现重用: 1.面向对象的思想:继承和多态,标准类库 2.泛型程序设计(generic progra ...

  6. C++的标准模板库STL中实现的数据结构之顺序表vector的分析与使用

    摘要 本文主要借助对C++的标准模板库STL中实现的数据结构的学习和使用来加深对数据结构的理解.即联系数据结构的理论分析和详细的应用实现(STL),本文是系列总结的第一篇,主要针对线性表中的顺序表(动 ...

  7. 实验8 标准模板库STL

    一.实验目的与要求: 了解标准模板库STL中的容器.迭代器.函数对象和算法等基本概念. 掌握STL,并能应用STL解决实际问题. 二.实验过程: 完成实验8标准模板库STL中练习题,见:http:// ...

  8. C++的标准模板库STL中实现的数据结构之链表std::list的分析与使用

    摘要 本文主要借助对C++的标准模板库STL中实现的数据结构的学习和使用来加深对数据结构的理解,即联系数据结构的理论分析和详细的应用实现(STL),本文是系列总结的第二篇.主要针对线性表中的链表 ST ...

  9. C++ 标准模板库STL 队列 queue 使用方法与应用介绍

    C++ 标准模板库STL 队列 queue 使用方法与应用介绍 queue queue模板类的定义在<queue>头文件中. 与stack模板类很相似,queue模板类也需要两个模板参数, ...

  10. 【c++】标准模板库STL入门简介与常见用法

    一.STL简介 1.什么是STL STL(Standard Template Library)标准模板库,主要由容器.迭代器.算法.函数对象.内存分配器和适配器六大部分组成.STL已是标准C++的一部 ...

随机推荐

  1. JAVA多线程程序ProgressBar

    JAVA多线程程序ProgressBar 题目简介: 思维导图: 实验代码:建议先看CalThread类,计算线程的实现,再作基本CalFrame类的界面, 然后作ReadThread类,结合CalF ...

  2. Python学习笔记:读取Excel的xlrd模块

    一.安装xlrd 可以使用命令行安装也可使用pycharm进行安装 表示xlrd库已经安装成功,安装成功后,我们就可以导入使用了. 二.xlrd说明 (1.单元格常用的数据类型包括 0:empty(空 ...

  3. PHP 文件夹上传

    一.我的准备情况说明. 编辑器:sublime text3(用什么编辑器看自己爱好)服务器构建:使用phpstudy2014构建服务器,服务器文件存储在我自身电脑D盘的www文件中.(安装phpstu ...

  4. Using NodeLists

    Understanding a NodeList object and its relatives, NamedNodeMap and HTMLCollection, is critical to a ...

  5. 基于Bootstrap 3可预览的HTML5文件上传插件

    前端常用资源地址: http://www.htmleaf.com/ http://www.htmleaf.com/html5/html5muban/201505091801.html 源代码地址 ht ...

  6. 【Python开发】urllib2.urlopen超时问题

    原帖地址:http://hi.baidu.com/yss1983/item/933fbe45a09c43e01381da06 问题描述:     没有设置timeout参数,结果在网络环境不好的情况下 ...

  7. 学习shell的第二天

    重定向和管道符: 1.重定向 程序 = 指令 + 数据            命令    变量  在程序中,数据如何输入?又如何输出?  数据输入:键盘  --  标准输入,但是并不是唯一输入方式:  ...

  8. 2019牛客暑期多校训练营(第三场)- H Magic Line (计算几何)

    题目链接:https://ac.nowcoder.com/acm/contest/883/H 题意:给定n个点(n为偶数),求一条直线使得n个点平均分散在直线两端,即每端n/2个点. 思路:把n个点按 ...

  9. 【转贴】bat脚本基础教程

    bat脚本基础教程 https://www.cnblogs.com/linyfeng/p/8072002.html 自己动手太少了. bat脚本就是DOS批处理脚本,就是将一系列DOS命令按照一定顺序 ...

  10. python装饰器的原理

    装饰器的原理就是利用<闭包函数>来实现,闭包函数的原理就是包含内层函数的return和外层环境变量: