C++ STL算法

来源:互联网 发布:利用网络赚钱的方法 编辑:程序博客网 时间:2024/04/27 21:55

【C++ STL学习之一】容器的共通能力和共通操作总结

一、C++ STL各容器有一些共通的能力

其中三个最核心的能力是:

(1)所有容器提供的是“value语意”而不是“reference语意”。

容器进行元素的安插操作时,内部实施的是拷贝操作,存放的是元素的副本。因此容器的每一个元素必须能被拷贝

如果打算存放的对象不具备public 拷贝构造函数或者你要得不是对象的副本,那么容器元素只能是指针

(2)所有元素形成一个次序。

我们可以以相同顺序一次或者多次遍历每个元素。每个容器都提供可返回迭代器的函数,运用这些迭代器可以遍历元素。

(3)一般来说,各项操作并非绝对安全。

调用者必须确保传给操作函数的参数符合需求。违反需求的会导致未定义的行为。而且STL通常不会自己跑出异常。


二、容器的共通操作

下表列出容器的共通操作函数:


(1)初始化

每个容器类别都提供了一个默认构造函数、一个拷贝构造函数和一个析构函数。

以另一个容器的元素为初值,进行初始化:

list<int> intList;
vector<float> floatVec(intList.begin(),intList.end());

以一个数组的元素为初值进行初始化:

int array[] = {1,2,3,4,5};
set<int> intSet(array,array + sizeof(array) / sizeof(array[0]));

从标准输入获取元素进行初始化:

deque<int> c((istream_iterator<int>(cin)),
(istream_iterator<int>()));

注意:不能遗漏了初始化参数的那对多余的括号:
不能是这样的:
deque<int> c(istream_iterator<int>(cin),
istream_iterator<int>());

否则,表达式被认为是一个函数c的声明。它的一个参数的型别是istream_iterator<int>,参数名是cin。第二个参数无名,型别是istream_iterator<int>。函数的返回值型别是istream_iterator<int>。
但是,我们通过加上一对括号,就可以使参数istream_iterator<int>(cin)不再符合声明语法。

因为C++标准中的语法不接受这样的函数声明形式:

T name((U), (V));

(2)与大小相关的操作函数

1、size()

返回当前元素的数量

2、empty()

等价于size() == 0.但是其效率可能会高些。所以应该尽量使用empty()。

3、max_size()

返回容器所能容纳的最大元素数量。其值会因实作版本不同而不同。

(3)比较

包括常用的==,!=,<,<=,>,>=。

注意:比较操作的两端即两个容器必须属于同一型别。如果容器的所有元素依次序相等,则两个容器相等。采用operator==检查元素是否相等。采用字典式书序比较原则来判断某个容器是否小于另一个容器。

(4)赋值和swap交换

当对容器赋值元素时,源容器的所有元素被拷贝到目标容器内,后者的所有元素完全被移除。所以,赋值操作代价较高。

如果两个容器的型别相同,而且拷贝后源容器不在使用,可以使用swap进行优化。它的性能要优异很多。事实上它只交换某些内部指针,所以时间复杂度是常数,而赋值操作的复杂度是线性的。


【C++ STL学习之二】容器vector大总结

一、容器vector

使用vector必须包含头文件<vector>:

#include<vector>

型别vector是一个定义于namespace std内的template:

[cpp] view plaincopyprint?
  1. template<class _Ty,  
  2.     class _Ax = allocator<_Ty> >  
第二个参数定义内存模型。我们一般采用默认的内存模型。


二、vector的功能

vector模塑出一个动态数组。vector将其元复制到内部的动态数组中。元素之间总是存在某种顺序,它是一种有序群集。支持随即存取。它的迭代器是随机存取迭代器,所以对任何一个STL算法都奏效。

向vector添加一个元素或者删除其中的一个元素,其后的所有元素都要移动位置,每一次移动都要调用赋值操作符。所以,在末端添加或删除元素,性能很好。但是在前段或者中部的话,性能较差。

vector优异性能的秘诀之一是它配置比其所容纳的元素所需更多的内存。我们需要了解大小容量的关系。

函数size()可以返回vector的大小,即vector中实际元素的个数。

而capacity()返回容量,是当前的vector所实际能够容纳的元素的数量。它应该总是大于或者等于vector的大小。如果需要向vector中放置比capacity更多的元素,则需要重新配置内部存储器。vector的容量也会随之增长。看下面的示例代码:

[cpp] view plaincopyprint?
  1. #include <iostream>  
  2. #include <vector>  
  3. #include <string>  
  4. #include <algorithm>  
  5. using namespace std;  
  6.   
  7. int main()  
  8. {  
  9.     vector<string> sentence(1);  
  10.     cout << "max_size():" << sentence.max_size() << endl;  
  11.     cout << "size():" << sentence.size() << endl;  
  12.     cout << "capacity():" << sentence.capacity() << endl;  
  13.   
  14.     sentence.reserve(5);  
  15.   
  16.     sentence.push_back("Hello,");  
  17.     sentence.push_back("how ");  
  18.     sentence.push_back("are ");  
  19.     sentence.push_back("you ");  
  20.     sentence.push_back("?");  
  21.   
  22.     copy(sentence.begin(),sentence.end(),  
  23.         ostream_iterator<string>(cout," "));  
  24.     cout << endl;  
  25.   
  26.     cout << "max_size():" << sentence.max_size() << endl;  
  27.     cout << "size():" << sentence.size() << endl;  
  28.     cout << "capacity():" << sentence.capacity() << endl;  
  29.   
  30.     swap(sentence[1],sentence[3]);  
  31.   
  32.     sentence.insert(find(sentence.begin(),sentence.end(),"?"),  
  33.         "always");  
  34.   
  35.     sentence.back() = "!";  
  36.   
  37.     copy(sentence.begin(),sentence.end(),  
  38.         ostream_iterator<string>(cout," "));  
  39.   
  40.     cout << endl;  
  41.   
  42.     cout << "max_size():" << sentence.max_size() << endl;  
  43.     cout << "size():" << sentence.size() << endl;  
  44.     cout << "capacity():" << sentence.capacity() << endl;  
  45. }  
运行结果:


在程序中,当再次向vector插入元素时,由于vector的容量不够,所以引起了内存的重新分配。但是capacity()的结果与实作版本有关,max_size也是。


vector的容量十分重要,是因为:

1、一旦内存重新配置,与之相关的所有的reference、pointers、iterators都会失效。

2、内存配置很费时。

解决这个问题的方法有:

1、可以使用reserve()保留适当容量,减少重新配置内存的次数。示例代码:

[cpp] view plaincopyprint?
  1. vector<string> sentence(1);  
  2. sentence.reserve(50);  
2、在初始化期间向构造函数传递附加参数,构造出足够的空间。
[cpp] view plaincopyprint?
  1. vector<T> v(5);  
当然,这种元素的型别必须提供默认构造函数。但是如果元素的型别比较复杂,初始化操作也很耗时。如果只是为了保留足够的内存,使用方法1较好。

注意:reserve不能缩减vector的容量。由此,我们可以知道,即使删除元素,其reference、pointers、iterators也会继续有效,指向动作发生前的位置。

但是插入操作可能使reference、pointers、iterators失效(因为可能会导致重新配置空间)。


使用swap函数可以缩减vector容量。因为两个vector交换内容后,他们的容量也会互换。

1、

[cpp] view plaincopyprint?
  1. template<class T>  
  2. void shrinkCapacity(vector<T> &v)  
  3. {  
  4.     vector<T> tmp(v);  
  5.     v.swap(tmp);  
  6. }  
2、
[cpp] view plaincopyprint?
  1. vector<T>(v).swap(v);  
上面两种方法等价。
都是先构造出一个临时vector对象,以v的元素进行初始化,再与v进行交换。需要注意的是:临时对象一般都是精确分配实际所需的内存。所以能够起到减小vector容量的效果。


三、vector的操作函数

所有的构造函数和析构函数如下:


非变动性操作:


赋值操作:


上述操作进行的是将新元素赋值给vector,并将旧元素全部移除!示例代码:

[cpp] view plaincopyprint?
  1. #include <iostream>  
  2. #include <vector>  
  3. #include <string>  
  4. #include <algorithm>  
  5. using namespace std;  
  6.   
  7. int main()  
  8. {  
  9.     vector<string> sentence(1);  
  10.     cout << "max_size():" << sentence.max_size() << endl;  
  11.     cout << "size():" << sentence.size() << endl;  
  12.     cout << "capacity():" << sentence.capacity() << endl;  
  13.   
  14.     sentence.reserve(5);  
  15.   
  16.     sentence.push_back("Hello,");  
  17.     sentence.push_back("how ");  
  18.     sentence.push_back("are ");  
  19.     sentence.push_back("you ");  
  20.     sentence.push_back("?");  
  21.   
  22.     copy(sentence.begin(),sentence.end(),  
  23.         ostream_iterator<string>(cout," "));  
  24.     cout << endl;  
  25.   
  26.     sentence.assign(3,"new");  
  27.   
  28.     copy(sentence.begin(),sentence.end(),  
  29.         ostream_iterator<string>(cout," "));  
  30.     cout << endl;  
  31. }  
运行结果:

可以看出原来的元素全部被删除了。


元素存取

在这几个函数中,唯一进行下标检查的是at函数。

因此,在调用operator[]的时候,必须心理清楚索引是否是有效的。


迭代器相关函数


迭代器失效的两种情况是:

1、在一个较小的位置上删除或者是移动元素。

2、由于容量的变换引起内存重新分配。


插入和移除元素


插入和移除元素,都会使“作用点”之后的各元素的reference、pointers、iterators失效。插入操作还可能引发内存重新分配,那么该容器上的所有的reference、pointers、iterators都会失效。


四、把vector当做一般数组使用

现在的C++标准保证vector的元素必须分布于连续空间中。对于vector中的一个合法索引,满足下列表达式:

&v[i] = &v[0] + i;

我们必须保证vector能够容纳所有数据。如果使用的是C-String,记住最后有个'\0'。

只要我们需要一个元素型别为T的数组,就可以采用vector<T>,然后传递第一个元素的地址给它。

注意:千万不要把迭代器当做第一元素的地址来传递。因为vector迭代器是由实作版本定义的,不一定是一个一般指针。

[cpp] view plaincopyprint?
  1. printf("%s",v.begin());//ERROR(might work,but not portable)  
  2. printf("%s",&v[0]);//OK  

【C++ STL学习之三】容器deque深入学习

C++ STL容器deque和vector很类似,也是采用动态数组来管理元素。

使用deque之前需包含头文件:

#include <deque>

它是定义在命名空间std内的一个class template:

template<class _Ty,
class _Ax = allocator<_Ty> >
class deque;

第一个template参数用来表示元素型别,第二个可有可无,指定内存模型。一般使用默认的内存模型。

与vector不同的是deque的动态数组首尾都开放,因此能够在首尾进行快速地插入和删除操作。

deque的逻辑结构:



deque的内部结构

deque是一种优化了的对序列两端元素进行添加和删除操作的基本序列容器。通常由一些独立的区块组成,第一区块朝某方向扩展,最后一个区块朝另一方向扩展。它允许较为快速地随机访问但它不像vector一样把所有对象保存在一个连续的内存块,而是多个连续的内存块。并且在一个映射结构中保存对这些块以及顺序的跟踪。

其内部结构如下图所示:


deque的特点:

1、支持随机访问,即支持[]以及at(),但是性能没有vector好。

2、可以在内部进行插入和删除操作,但性能不及list。


deque和vector的不同之处:

1、两端都能够快速插入和删除元素。vector只能在尾端进行。

2deque的元素存取和迭代器操作会稍微慢一些。因为deque的内部结构会多一个间接过程。

3、迭代器是特殊的智能指针,而不是一般指针。它需要在不同的区块之间跳转。

4deque可以包含更多的元素,其max_size可能更大。因为不止使用一块内存。

5、不支持对容量和内存分配时机的控制。

注意:在除了首尾两端的其他地方插入和删除元素,都将会导致指向deque元素的任何pointersreferencesiterators失效。不过,deque的内存重分配优于vector。因为其内部结构显示不需要复制所有元素。

6deque的内存区块不再被使用时,会被释放。deque的内存大小是可缩减的。不过,是不是这么做以及怎么做由实作版本定义。

 

dequevector相似的特性:

1、在中间部分插入和删除元素相对较慢,因为所有元素都要被移动。

2、迭代器属于随即存取迭代器。

 

最好采用deque的情形:

1、需要在两端插入和删除元素。

2、无需引用容器内的元素。

3、要求容器释放不再使用的元素。

 

deque的操作函数

构造函数和析构函数:

非变动性操作:


变动性操作:



deque的各项操作只有一下两点和vector不同:

deque不提供容量操作:capacity()reverse()

deque直接提供函数完成首尾元素的插入和删除。

其他均与vector相同。

注意:

1、除了at()函数,其他成员函数都不会检查索引或迭代器是否有效。

2、元素的插入和删除可能会导致内存重新分配。所以任何插入或删除操作都会使所有指向deque元素的pointers、reference、iterators失效。唯一例外的是在首尾插入元素之后,pointers和reference可能仍然有效。


程序示例:

[cpp] view plaincopyprint?
  1. #include <iostream>  
  2. #include <deque>  
  3. #include <string>  
  4. #include <algorithm>  
  5.   
  6. using namespace std;  
  7.   
  8. int main()  
  9. {  
  10.     deque<string> strDeq;  
  11.   
  12.     strDeq.assign(4,string("CHINA"));  
  13.     strDeq.push_back("first_string");  
  14.     strDeq.push_front("last_string");  
  15.   
  16.     copy(strDeq.begin(),strDeq.end(),  
  17.         ostream_iterator<string>(cout," "));  
  18.     cout << endl;  
  19.     cout << endl;  
  20.   
  21.     for(int i = 0;i < strDeq.size();++i)  
  22.         cout << "strDeq[" << i << "] : " << strDeq[i] << endl;  
  23.     cout << endl;  
  24.       
  25.     for(int i = 0;i < strDeq.size();++i)  
  26.         cout << "strDeq.at(" << i << ") : " << strDeq.at(i) << endl;  
  27.     cout << endl;  
  28.   
  29.     strDeq.pop_front();  
  30.     strDeq.pop_back();  
  31.   
  32.     copy(strDeq.begin(),strDeq.end(),  
  33.         ostream_iterator<string>(cout," "));  
  34.     cout << endl;  
  35.     cout << endl;  
  36.   
  37.     for(int i = 1;i < strDeq.size();++i)  
  38.         strDeq[i] = "pre" + strDeq[i];  
  39.     copy(strDeq.begin(),strDeq.end(),  
  40.         ostream_iterator<string>(cout," "));  
  41.     cout << endl;  
  42.     cout << endl;  
  43.   
  44.     strDeq.resize(4,"resized string");  
  45.   
  46.     copy(strDeq.begin(),strDeq.end(),  
  47.         ostream_iterator<string>(cout," "));  
  48.     cout << endl;  
  49.     cout << endl;  
  50. }  

运行结果:


【C++ STL学习之四】容器list深入学习

一、list基础

List使用一个双向链表来管理元素。图一显示了list的结构。


图一 list的结构

任何型别只要具备赋值和可拷贝两种性质,就可以作为list的元素。


二、list的功能

list的内部结构和vector和deque截然不同,在很多方面都表现不同:

1、list不支持随机存取。所及既不提供下表操作符,也不提供at()函数。

2、任何位置上插入元素删除元素都很快。

3、插入和删除操作不会造成指向其他元素的pointers、references、iterators失效。

4、list对于异常的处理方式是:要么成功,要么什么都不发生。

5、不提供容量、空间重新分配等操作函数。

6、提供了一些函数专门用于移动元素。


三、list的操作函数

生成、赋值和销毁与序列式容器相同。

构造函数和析构函数:


非变动性操作


赋值


元素存取


一般来说,这些操作不会检查元素是否为空,所以程序员必须保证容器不为空。

迭代器相关函数

存取元素只能用迭代器。然而由于list不能随机存取,这些迭代器只是双向而非随机迭代器。所以凡是用到随机存取迭代器的算法(所有用来操作元素顺序的算法-特别是排序算法都归于此类)都不能调用。不过可以用list的成员函数sort()完成此功能。


元素的插入和删除



list提供deque的多余功能,还增加了remove()和remove_if()算法应用于list‘身上的特殊版本。这些成员函数比STL的remove算法更快,因为它们只进行内部指目标工作,无需估计元素。

要将所有与某值相等的元素删除,可以用如下语句(注意:确实是将这些元素删除了,而不是像vector那样只是某种移动而已)

[cpp] view plaincopyprint?
  1. list<int> list1;  
  2. list1.remove(val);  
list还提供了其他一些成员函数,用来改变元素和区间的次序,或是重新串链。我们可以用他们来移动单一list里的元素,也可以移动两个list之间的元素(前提是list的型别一致)。



四、list的异常处理

在所有的STL标准容器中,list提供了最佳支持。几乎所有的操作都是要么正确,要么什么都不发生。只有少数几个操作没有这样的保证:赋值和sort()。不过他们也不会泄露资源,也不会与容器的恒常特性发生冲突。merge(),remove(),remove_if(),unique()提供的保证是有前提的L就是元素间的比较动作不会抛出异常。


五、list示例程序

[cpp] view plaincopyprint?
  1. #include <iostream>  
  2. #include <list>  
  3. #include <algorithm>  
  4. using namespace std;  
  5.   
  6. void outputLists(const list<int> &l1,const list<int> &l2)  
  7. {  
  8.     cout << "list1:";  
  9.     copy(l1.begin(),l1.end(),ostream_iterator<int>(cout," "));  
  10.     cout << endl << "list2:";  
  11.     copy(l2.begin(),l2.end(),ostream_iterator<int>(cout," "));  
  12.     cout << endl << endl;  
  13. }  
  14.   
  15. int main()  
  16. {  
  17.     list<int> iList1,iList2,iList3;  
  18.   
  19.     for (int i = 1;i < 9;++i)  
  20.     {  
  21.         iList1.push_back(i);  
  22.         iList2.push_front(i);  
  23.         iList3.push_front(i);  
  24.     }  
  25.   
  26.     outputLists(iList1,iList2);  
  27.   
  28.     iList2.splice(iList2.end(),iList2,iList2.begin());  
  29.     outputLists(iList1,iList2);  
  30.   
  31.     iList2.sort();  
  32.     outputLists(iList1,iList2);  
  33.   
  34.     iList2.unique();  
  35.     outputLists(iList1,iList2);  
  36.   
  37.     iList1.merge(iList2);  
  38.     outputLists(iList1,iList2);  
  39.   
  40.     /*outputLists(iList3,iList1); 
  41.     iList1.merge(iList3); 
  42.     outputLists(iList3,iList1);*/  
  43. }  
运行结果:

注意:merge只使用与合并两个元素已序(而且都是升序)排列的的容器。如上面的程序,最后merge ilist1和ilist3程序就会报错。


【C++ STL学习之五】容器set和multiset

一、set和multiset基础

set和multiset会根据特定的排序准则,自动将元素进行排序。不同的是后者允许元素重复而前者不允许。


需要包含头文件:

#include <set>

set和multiset都是定义在std空间里的类模板:

[cpp] view plaincopyprint?
  1. template<class _Kty,  
  2.     class _Pr = less<_Kty>,  
  3.     class _Alloc = allocator<_Kty> >  
  4. class set  
[cpp] view plaincopyprint?
  1. template<class _Kty,  
  2.     class _Pr = less<_Kty>,  
  3.     class _Alloc = allocator<_Kty> >  
  4. class multiset  

只要是可复赋值、可拷贝、可以根据某个排序准则进行比较的型别都可以成为它们的元素。第二个参数用来定义排序准则。缺省准则less是一个仿函数,以operator<对元素进行比较。

所谓排序准则,必须定义strict weak ordering,其意义如下:

1、必须使反对称的。

对operator<而言,如果x<y为真,则y<x为假。

2、必须使可传递的。

对operator<而言,如果x<y为真,且y<z为真,则x<z为真。

3、必须是非自反的。

对operator<而言,x<x永远为假。

因为上面的这些特性,排序准则可以用于相等性检验,就是说,如果两个元素都不小于对方,则它们相等。


二、set和multiset的功能

和所有关联式容器类似,通常使用平衡二叉树完成。事实上,set和multiset通常以红黑树实作而成。

自动排序的优点是使得搜寻元素时具有良好的性能,具有对数时间复杂度。但是造成的一个缺点就是:

不能直接改变元素值。因为这样会打乱原有的顺序。

改变元素值的方法是:先删除旧元素,再插入新元素。

存取元素只能通过迭代器,从迭代器的角度看,元素值是常数。


三、操作函数

构造函数和析构函数

set的形式可以是:


有两种方式可以定义排序准则:

1、以template参数定义:

[cpp] view plaincopyprint?
  1. set<int,greater<int>> col1;  
此时,排序准则就是型别的一部分。型别系统确保只有排序准则相同的容器才能被合并。

程序实例:

[cpp] view plaincopyprint?
  1. #include <iostream>  
  2. #include <set>  
  3. using namespace std;  
  4.   
  5. int main()  
  6. {  
  7.     set<int> s1;  
  8.     set<int,greater<int> > s2;  
  9.   
  10.     for (int i = 1;i < 6;++i)  
  11.     {  
  12.         s1.insert(i);  
  13.         s2.insert(i);  
  14.     }  
  15.     if(s1 == s2)  
  16.         cout << "c1 equals c2 !" << endl;  
  17.     else  
  18.         cout << "c1 not equals c2 !" << endl;  
  19. }  
程序运行会报错。但是如果把s1的排序准则也指定为greater<int>便运行成功。

2、以构造函数参数定义。

这种情况下,同一个型别可以运用不同的排序准则,而排序准则的初始值或状态也可以不同。如果执行期才获得排序准则,而且需要用到不同的排序准则,这种方式可以派上用场。

程序实例:

[cpp] view plaincopyprint?
  1. #include <iostream>  
  2. #include "print.hpp"  
  3. #include <set>  
  4. using namespace std;  
  5.   
  6. template <class T>  
  7. class RuntimeCmp{  
  8. public:  
  9.     enum cmp_mode{normal,reverse};  
  10. private:  
  11.     cmp_mode mode;  
  12. public:  
  13.     RuntimeCmp(cmp_mode m = normal):mode(m){}  
  14.   
  15.     bool operator()(const T &t1,const T &t2)  
  16.     {  
  17.         return mode == normal ? t1 < t2 : t2 < t1;  
  18.     }  
  19.   
  20.     bool operator==(const RuntimeCmp &rc)  
  21.     {  
  22.         return mode == rc.mode;  
  23.     }  
  24. };  
  25.   
  26. typedef set<int,RuntimeCmp<int> > IntSet;  
  27.   
  28. void fill(IntSet& set);  
  29.   
  30. int main()  
  31. {  
  32.     IntSet set1;  
  33.     fill(set1);  
  34.     PRINT_ELEMENTS(set1,"set1:");  
  35.   
  36.     RuntimeCmp<int> reverse_order(RuntimeCmp<int>::reverse);  
  37.   
  38.     IntSet set2(reverse_order);  
  39.     fill(set2);  
  40.     PRINT_ELEMENTS(set2,"set2:");  
  41.   
  42.     set1 = set2;//assignment:OK  
  43.     set1.insert(3);  
  44.     PRINT_ELEMENTS(set1,"set1:");  
  45.   
  46.     if(set1.value_comp() == set2.value_comp())//value_comp <span style="font-family: verdana, arial, helvetica, sans-serif; ">Returns the comparison object associated with the container</span>  
  47.         cout << "set1 and set2 have the same sorting criterion" << endl;  
  48.     else  
  49.         cout << "set1 and set2 have the different sorting criterion" << endl;  
  50. }  
  51.   
  52. void fill(IntSet &set)  
  53. {  
  54.     set.insert(4);  
  55.     set.insert(7);  
  56.     set.insert(5);  
  57.     set.insert(1);  
  58.     set.insert(6);  
  59.     set.insert(2);  
  60.     set.insert(5);  
  61. }  
运行结果:


虽然set1和set2的而比较准则本身不同,但是型别相同,所以可以进行赋值操作。


非变动性操作

注意:元素比较操作只能用于型别相同的容器。

特殊的搜寻函数

赋值

赋值操作两端的容器必须具有相同的型别,但是比较准则本身可以不同,但是其型别必须相同。如果比较准则的不同,准则本身也会被赋值或交换。


迭代器相关函数


元素的插入和删除

注意:插入函数的返回值不完全相同。

set提供的插入函数:

[cpp] view plaincopyprint?
  1. pair<iterator,bool> insert(const value_type& elem);   
  2. iterator  insert(iterator pos_hint, const value_type& elem);   
multiset提供的插入函数:

[cpp] view plaincopyprint?
  1. iterator  insert(const value_type& elem);   
  2. iterator  insert(iterator pos_hint, const value_type& elem);  
返回值型别不同的原因是set不允许元素重复,而multiset允许。当插入的元素在set中已经包含有同样值的元素时,插入就会失败。所以set的返回值型别是由pair组织起来的两个值:

第一个元素返回新元素的位置,或返回现存的同值元素的位置。第二个元素表示插入是否成功。

set的第二个insert函数,如果插入失败,就只返回重复元素的位置!

但是,所有拥有位置提示参数的插入函数的返回值型别是相同的。这样就确保了至少有了一个通用型的插入函数,在各种容器中有共通接口。


注意:还有一个返回值不同的情况是:作用于序列式容器和关联式容器的erase()函数:

序列式容器的erase()函数:

[cpp] view plaincopyprint?
  1. iterator erase(iterator pos);   
  2. iterator erase(iterator beg, iterator end);  
关联式容器的erase()函数:

[cpp] view plaincopyprint?
  1. void     erase(iterator pos);   
  2. void     erase(iterator beg, iterator end);   
这完全是为了性能的考虑。因为关联式容器都是由二叉树实现,搜寻某元素并返回后继元素可能很费时。

五、set应用示例

[cpp] view plaincopyprint?
  1. #include <iostream>  
  2. #include <set>  
  3. using namespace std;  
  4.   
  5. int main()  
  6. {  
  7.     typedef set<int,greater<int> > IntSet;  
  8.     IntSet s1;  
  9.   
  10.     s1.insert(4);  
  11.     s1.insert(3);  
  12.     s1.insert(5);  
  13.     s1.insert(1);  
  14.     s1.insert(6);  
  15.     s1.insert(2);  
  16.     s1.insert(5);  
  17.     //the inserted element that has the same value with a element existed is emitted  
  18.   
  19.     copy(s1.begin(),s1.end(),ostream_iterator<int>(cout," "));  
  20.     cout << endl << endl;  
  21.   
  22.     pair<IntSet::iterator,bool> status = s1.insert(4);  
  23.     if(status.second)  
  24.         cout << "4 is inserted as element "  
  25.         << distance(s1.begin(),status.first) + 1 << endl;  
  26.     else  
  27.         cout << "4 already exists in s1" << endl;  
  28.     copy(s1.begin(),s1.end(),ostream_iterator<int>(cout," "));  
  29.     cout << endl << endl;  
  30.   
  31.     set<int>  s2(s1.begin(),s1.end());//default sort criterion is less<  
  32.     copy(s2.begin(),s2.end(),ostream_iterator<int>(cout," "));  
  33.     cout << endl << endl;  
  34. }  

上述程序最后新产生一个set:s2,默认排序准则是less。以s1的元素作为初值。

注意:s1和s2有不同的排序准则,所以他们的型别不同,不能直接进行相互赋值或比较。

运行结果:


【C++ STL学习之六】STL算法之for_each

for_each()函数将调用者提供的操作施加于每一个元素身上。它既可以是非变动性算法,也可以说是变动性算法。

[cpp] view plaincopyprint?
  1. template <class InputIterator, class Function>  
  2.    Function for_each (InputIterator first, InputIterator last, Function f);  

将函数f施加于区间[first,last)的每一个元素身上。其实现:

[cpp] view plaincopyprint?
  1. template<class InputIterator, class Function>  
  2.   Function for_each(InputIterator first, InputIterator last, Function f)  
  3.   {  
  4.     for ( ; first!=last; ++first ) f(*first);  
  5.     return f;  
  6.   }  
它返回f已在算法内部变动过的一个副本。
f可以是普通函数,也可是仿函数。它的任何返回值都将被忽略。


程序实例:

下面的例子实现了两个功能:

一是使用普通函数print()打印所以元素;而是使用自定义的仿函数来改变每个元素:将每个元素乘以3.

main.cpp:

[cpp] view plaincopyprint?
  1. #include "algostuff.h"  
  2.   
  3. using namespace std;  
  4.   
  5. void print(int elem)  
  6. {  
  7.     cout << elem << " ";  
  8. }  
  9.   
  10. //define a functor  
  11. //multiply every element with the value initialized  
  12. template <class T>  
  13. class MultiplyValue{  
  14. private:  
  15.     T value;  
  16. public:  
  17.     MultiplyValue(const T val):value(val){}  
  18.   
  19.     //the function call  
  20.     void operator()(T &elem)  
  21.     {  
  22.         elem *= value;  
  23.     }  
  24. };  
  25.   
  26. int main()  
  27. {  
  28.     vector<int> intVec;  
  29.     INSERT_ELEMENTS(intVec,1,9);  
  30.   
  31.     cout << "elements : " << endl;  
  32.     for_each(intVec.begin(),intVec.end(),print);  
  33.     cout << endl << endl;  
  34.   
  35.     for_each(intVec.begin(),intVec.end(),MultiplyValue<int>(3));  
  36.     PRINT_ELEMNTS(intVec,"after multiply : ");  
  37.     cout << endl;  
  38. }  
algostuff.h:
[cpp] view plaincopyprint?
  1. #ifndef ALGOSTUFF_H  
  2. #define ALGOSTUFF_H  
  3.   
  4. #include <iostream>  
  5. #include <vector>  
  6. #include <list>  
  7. #include <deque>  
  8. #include <set>  
  9. #include <map>  
  10. #include <string>  
  11. #include <algorithm>  
  12. #include <functional>  
  13. #include <numeric>  
  14.   
  15. //print all the elements  
  16. template <class T>  
  17. inline void PRINT_ELEMNTS(const T &col,const char *optcstr = " ")  
  18. {  
  19.     typename T::const_iterator pos;  
  20.     cout << optcstr;  
  21.     for(pos = col.begin();pos != col.end();++pos)  
  22.         cout << *pos << " ";  
  23.     cout << endl;  
  24. }  
  25.   
  26. //insert values from first to last into the collection  
  27. template <class T>  
  28. inline void INSERT_ELEMENTS(T &col,int first,int last)  
  29. {  
  30.     for(int i = first;i <= last;++i)  
  31.         col.insert(col.end(),i);  
  32. }  
  33.   
  34. #endif  
运行结果:


【C++ STL学习之七】STL算法之find和find_if

一、find()算法

[cpp] view plaincopyprint?
  1. template<class InputIterator, class T>  
  2.   InputIterator find ( InputIterator first, InputIterator last, const T& value )  
  3.   {  
  4.     for ( ;first!=last; first++) if ( *first==value ) break;  
  5.     return first;  
  6.   }  
返回区间[first,end)中第一个值等于value的元素的位置。

如果没有找到匹配元素,则返回end。

复杂度:线性复杂度。最多比较次数是:元素的总个数。

程序实例:

下面的程序在int类型的vector中搜寻元素5和12,如果搜索到,就返回其位置if欧泽输出提示信息。

main.cpp(头文件algostuff.h和上一篇博客中的相同):

[cpp] view plaincopyprint?
  1. #include "algostuff.h"  
  2.   
  3. using namespace std;  
  4.   
  5. int main()  
  6. {  
  7.     vector<int> intVec;  
  8.   
  9.     INSERT_ELEMENTS(intVec,1,9);  
  10.   
  11.     vector<int>::iterator pos;  
  12.     pos = find(intVec.begin(),intVec.end(),5);  
  13.   
  14.     if(pos != intVec.end())  
  15.         cout << "The value 5 exists,and its position is " <<  
  16.         distance(intVec.begin(),pos) + 1 << endl;  
  17.     else  
  18.         cout << "The value 4 not found!" << endl;  
  19.   
  20.     pos = find(intVec.begin(),intVec.end(),12);  
  21.   
  22.     if(pos != intVec.end())  
  23.         cout << "The value 12 exists,and its position is " <<  
  24.         distance(intVec.begin(),pos) + 1 << endl;  
  25.     else  
  26.         cout << "The value 12 not found!" << endl;  
  27. }  
运行结果(头文件algostuff.h和上一篇博客中的相同)


二、find_if()算法

[cpp] view plaincopyprint?
  1. template<class InputIterator, class Predicate>  
  2.   InputIterator find_if ( InputIterator first, InputIterator last, Predicate pred )  
  3.   {  
  4.     for ( ; first!=last ; first++ ) if ( pred(*first) ) break;  
  5.     return first;  
  6.   }  
它在区间[first,end)中搜寻使一元判断式pred为true的第一个元素。

如果没找到,返回end。

程序实例:

下面程序找出第一个能够被3整除的元素,如果找到返回其位置。

main.cpp:

[cpp] view plaincopyprint?
  1. #include "algostuff.h"  
  2.   
  3. using namespace std;  
  4.   
  5. int main()  
  6. {  
  7.     vector<int> intVec;  
  8.   
  9.     INSERT_ELEMENTS(intVec,1,9);  
  10.   
  11.     vector<int>::iterator pos;  
  12.     pos = find_if(intVec.begin(),intVec.end(),  
  13.         not1(bind2nd(modulus<int>(),3)));  
  14.   
  15.     if(pos != intVec.end())  
  16.         cout << "The value divided by 3 exists,and the first value's position is " <<  
  17.         distance(intVec.begin(),pos) + 1 << endl;  
  18.     else  
  19.         cout << "The value divided by 3 not found!" << endl;  
  20. }  
运行结果:


【C++ STL学习之八】逆向迭代器reverse_iterator

对于逆向迭代器,很重要的一点是需要弄清楚逻辑位置实际位置二者的区别。

下图显示了逆向迭代器的位置和所指的数值


可以发现,逆向迭代器所指位置(实际位置)和所代表的的数值(逻辑位置或数值)是不同的。C++这么做是有其原因的。导致这个行为的原因是区间的半开性。为了能够制定容器内的所有元素,我们必须运用最后一个元素的下一个位置。但是对于reverse迭代器而言,这个位置位于第一个元素之前。这时候问题就出现了,这个位置也许并不存在,因为容器并不要求其第一个元素之前的位置合法。
因此,逆向迭代器运用了一个小技巧:实际上倒置了“半开原则”,即逆向迭代器所定义的区间不包括起点,而包括终点。但是逻辑上一如常态。这样就导致了逆向迭代器实际所指的元素位置和逻辑上所指的元素位置就不一致。

下面再看看将一个迭代器转化为逆向迭代器的过程:

可以发现,迭代器的实际位置(元素)不变,但是逻辑位置(元素)发生了变化。图中pos迭代器转化为逆向迭代器rpos后实际位置还是5,但是逻辑位置是4.即逻辑元素位置是实际位置的前一个位置。测试代码:

[cpp] view plaincopyprint?
  1. #include <iostream>   
  2. #include <vector>   
  3. #include <algorithm>   
  4. using namespace std;   
  5.  int main()    
  6. {   
  7.     vector<int> coll;   
  8.   
  9.   
  10.     //insert elements from 1 to 9   
  11.     for (int i=1; i<=9; ++i) {   
  12.          coll.push_back(i);   
  13.     }   
  14.   
  15.   
  16.     //find position of element with value 5   
  17.     vector<int>::iterator pos;   
  18.     pos = find (coll.begin(), coll.end(),   
  19.                 5);   
  20.   
  21.   
  22.     //print value to which iterator pos refers   
  23.     cout << "pos: " << *pos << endl;   
  24.   
  25.   
  26.     //convert iterator to reverse iterator rpos   
  27.     vector<int>::reverse_iterator rpos(pos);   
  28.   
  29.   
  30.     //print value to which reverse iterator rpos refers   
  31.     cout << "rpos: " << *rpos <<endl;   
  32. }   

输出结果是(VS2008):



0 0
原创粉丝点击