首页 > 代码库 > 顺序容器

顺序容器

声明:本文借鉴多人的博客,但文中代码均已提炼修改,本文仅作为知识点记录为主

1.容器概述

 1     #include <iterator>       //迭代器,包含c++11的begin() 和end()函数  
 2     #include <array>          //c++11 数组类型,长度固定,提供了更好、更安全的接口,执行效率和内置数组相同,可以有效替代内置数组  
 3     #include <valarray>       //c++11 值类型的数组类型,针对值类型的数组,有更多的操作,比如求和,最大最小数等。  
 4     #include <list>           //双向链表,插入删除速度快,不支持随机访问  
 5     #include <forward_list>   //c++11 单向链表,单向访问,插入删除速度快,不支持随机访问,没有size操作  
 6     #include <deque>          //双端队列,支持快速随机访问  
 7     #include <string>         //string类型,插入删除耗时  
 8     #include <vector>         //迭代器类型,插入删除耗时  
 9     #include <iostream>  
10       
11     /* 
12      * 具体需要哪个容器根据它的数据结构的优势来选择 
13      * C++11后  vector<vector<int>>   “>>”不用添加空格了 
14      */  

 

 

2.迭代器不能比较大小

1     list<int>il = {1,2,3,4,5,6,7};  
2     list<int>::iterator it = il.begin(), it2 = il.end();  
3     while(it < it2) //error  
4     { ...}   

3.begin和end

C++11新增加了auto和begin,end的结合用法。

增加了cbegin和crbegin。

 1     #include <iterator>  
 2     #include <iostream>  
 3     #include <list>  
 4       
 5     using namespace std;  
 6       
 7     int main()  
 8     {  
 9         list<string>il = {"hello", "world", "wang", "wei", "hao" };  
10         auto it1 = il.begin();         //list<string>::iterator  
11         auto it2 = il.cbegin();        //list<string>::const_iterator  
12         auto it3 = il.rbegin();        //list<string>::reverse_iterator  
13         auto it4 = il.crbegin();       //list<string>::const_reverse_iteratror  
14         cout << *it1 << endl;  
15         //*it2 = "ww"; error:const类型不能修改  
16         cout << *it2 << endl;  
17         cout << *it3 << endl;  
18         cout << *it4 << endl;  
19         //*it4 = "ww"; error:const类型不能修改  
20     }   

!当我们不需要写访问时,应该使用cbegin和cend。


4.容器的定义和初始化

<1.几种初始化

 1     #include <vector>  
 2     #include <iostream>  
 3       
 4     using namespace std;  
 5       
 6     int main()  
 7     {  
 8         vector<int>ivec1;                            //默认初始化  
 9         vector<int>ivec2 = {1,2,3,4,5,6,7,8,9,0};    //列表初始化  
10         //vector<int>ivec2{1,2,3,4,5,6,7,8,9,0};   
11         vector<int>ivec3(ivec2);                     //拷贝初始化  
12         //vector<int>ivec3 = ivec2;  
13         vector<int>ivec4(ivec1.begin(), ivec1.end());//迭代器初始化  
14         vector<int>ivec5(10);                        //n个元素初始化  
15         vector<int>ivec6(10, 9);                     //n个元素加初始值初始化  
16       
17         return 0;  
18     }  

<2.为了创建一个容器为另一个容器的拷贝,两个容器的类型及其元素类型必须匹配

不过当传递迭代器参数来拷贝一个范围时,就不要求容器类型是相同的。而且新容器和原容器中的元素类型也可以不同,!只要能将拷贝的元素转换即可。

 1     #include <iostream>  
 2     #include <vector>  
 3     #include <list>  
 4     #include <string>  
 5     #include <deque>  
 6       
 7     using namespace std;  
 8       
 9     int main()  
10     {  
11         list<string>il1 = {"hello", "world", "hehe"};//新标准中可以对一个容器进行列表初始化  
12         list<string>il2(il1);  
13         for(const string&s : il2)  
14             cout << s << endl;  
15         //deque<string>deq(il1);  
16         deque<string>deq(il1.begin(), il1.end());  
17         for(const string&s : deq)  
18             cout << s << endl;  
19     }  

<3.如果元素类型默认构造函数,除了大小参数外,还必须指定一个显式的元素初始值只有顺序容器的构造函数才接受大小参数,关联容器并不支持

<4.标准库array具有固定大小

array类型是c++11数组类型,长度固定(必须初始的时候指定),提供了更好,更安全的接口,执行效率和内置数组相同,可以有效替代内置数组标准库array的大小也是类型的一部分,当定义一个array时,除了指定元素类型,还要指定容器大小。

 

1     #include <array>  
2     array<int, 10>a = {1,2,3,4,5,6,7,8,9,0};  
3     array<string, 10>  
4     array<int, 10>b = a;  
5     但是内置类型就不支持数组复制  

5.赋值和swap

<1.不论第一个容器有多少元素,只要被赋值,全部为被赋值的容器的元素。

array类型不能赋值

赋值相关运算会导致指向左边的容器内部的迭代器,引用,指针失效,而swap操作将容器内容交换不会导致指向容器的迭代器,引用和指针失效

容器类型array和string除外

几种交换和赋值操作

 1     #include <iostream>  
 2     #include <vector>  
 3       
 4     using namespace std;  
 5       
 6     int main()  
 7     {  
 8         vector<int>ivec;  
 9         ivec = {1,2,3,4,5,6,7,8};  
10         for(const int &i: ivec)  
11             cout << i << endl;  
12         vector<int>ivec2 = {9,9,9};  
13         ivec = ivec2;                    //直接赋值  
14         for(const int &i: ivec)  
15             cout << i << endl;  
16         vector<int>ivec3 = {1,1,1,1,1};  
17         vector<int>ivec4 = {2,2,2,2,2};  
18         //swap(ivec3,ivec4);             //swap的两种方式,统一使用非成语版本swap是一个好习惯  
19         //ivec3.swap(ivec4);               
20         for(const int &i:ivec3)  
21             cout << i << endl;  
22         for(const int &i:ivec4)  
23             cout << i << endl;  
24         ivec3.assign(10,11);             //赋值assign的几种方式  
25         //ivec3.assign({2,2,2,2,2,2});  
26         //ivec3.assign(ivec4.begin(), ivec4.end());  
27         for(const int &i:ivec3)  
28             cout << i << endl;     
29     }  

交换两个容器保证会很快,元素本身并为交换,swap只是交换了两个容器的内部结构。

意味着指向容器的迭代器,引用,指针在swap操作之后都不会失效

除了array,string以外


当赋值时容器类型不匹配时

 1     #include <string>  
 2     #include <list>  
 3     #include <vector>  
 4     #include <iostream>  
 5       
 6     using namespace std;  
 7       
 8     int main()  
 9     {  
10         list<string>il = {"wang", "wei", "hao"};  
11         vector<const char*>ivec = {"hao", "wei", "wang"};  
12         il.assign(ivec.begin(), ivec.end());只能通过迭代器赋值  
13         for(const string &s : il)  
14             cout << s << endl;  
15     }  

6.容器大小操作

size( ):返回容器中元素的个数

empty( ):查看容器是否为空,size=0时返回true

max_size( ):返回一个大于或等于该容器所能容纳最大元素数的值

forward_list不支持size( )操作。


7.关系运算符

关系运算符左右两端必须容器类型相同。

只有当其元素类型也定义了相应的比较运算符时,我们才可以使用关系运算符来比较两个容器。


8.顺序容器的操作

<1.向顺序容器里添加元素

除了array以外,所有标准库容器都能提供灵活的内存管理

例子

 1 #include <list>  
 2 #include <string>  
 3 #include <iostream>  
 4 #include <vector>  
 5 
 6 using namespace std;
 7 
 8 class people
 9 {
10 public:
11     people() = default;
12     people(double h) :height(h), sex(0), name(" "), age(0)
13     {
14     }
15     people(double h, bool s, string na, int ag) :
16         height(h), sex(s), name(na), age(ag)
17     {
18     }
19 
20     double height = 0.0;
21     bool sex = 0;
22     string name = "";
23     int age = 0;
24 };
25 
26 int main()
27 {
28     vector<people>ivec;
29     people p1(175, 0, "tfsong", 24);
30     ivec.push_back(p1);                                //c.push_back()尾部"创建"一个元素,返回void,注意创建这个词,说明它会重新建立一个元素,而不是以前的  
31 
32     ivec.emplace_back(100, 1, "xxxxxxx", 000);        //C++11 c.emplace_back()同上,区别是传递的是参数,emplace传递的是参数,不是对象,就如左边  
33     for (people &p : ivec)
34     {
35         cout << "name:" << p.name << " sex:" << p.sex << " height:" << p.height << " age:" << p.age << endl;
36     }
37     cout << endl;
38 
39     list<people>il;                                    //一样,传递的是类的参数,emplace成员使用这些参数在容器管理的内存空间中直接构造元素。  
40     il.push_front(p1);                                 //c.push_front()头部创建一个元素,返回void  
41     il.emplace_front(100, 1, "xxxxxxx", 000);            //同上  
42     il.emplace_front(100);                                //因为emplace会用参数通过容器构造对象,所以只传递了171参数,构造时调用people类的只含h的构造函数  
43 
44     auto iter = il.begin();
45     iter = il.insert(iter, p1);                        //c.insert(p, t)  p是迭代器类型,指定位置插入t对象  
46     il.emplace(iter, 100, 1, "wwwwww", 200);              //同上,传递的是参数  
47         
48     for (people p : il)
49     {
50         cout << "name:" << p.name << " sex:" << p.sex << " height:" << p.height << " age:" << p.age << endl;
51     }
52     vector<int>ivec2 = { 1 };
53     auto iter2 = ivec2.begin();
54     ivec2.insert(iter2, 3, 10);                        //c.insert(p, n, t) 迭代器p位置插入n个t元素  
55     cout << "ivec2" << endl;
56     for (const int &i : ivec2)
57         cout << i << endl;
58 
59     vector<int>ivec3 = { 1 };
60     auto iter3 = ivec3.begin();
61     ivec3.insert(iter3, ivec2.begin(), ivec2.end());   //c.insert(p, b, e) 迭代器p位置插入另一个类型相同容器迭代器(b,e)范围内的元素  
62     cout << "ivec3" << endl;
63     for (const int &i : ivec3)
64         cout << i << endl;
65 
66     vector<int>ivec4;
67     ivec4.insert(ivec4.begin(), {1,1,1,1,1,1,1,1});   //c.insert(p, il)迭代器p位置插入il一个花括号包围的初始值列表。但运行会报错不知是编译器的问题还是  
68     cout << "ivec4" << endl;
69     for (const int &i : ivec4)
70         cout << i << endl;
71 
72     return 0;
73 }

 

运行结果

技术分享

 

注意:

<<1. 向一个vector,string,deque插入元素会使所有指向容器的迭代器,引用,指针失效

<<2.当我们用一个对象来初始化容器时,或将一个对象插入到容器时,实际上放的是一个拷贝,而不是对象本身。

<<3.每个insert都接受一个迭代器作为第一个参数。

<<4.insert函数将元素都插入到敌人代器所指定的位置之前

<<5.将元素插入到vector,deque,string中的任何位置都是合法的。然而这样做可能会很耗时

<<6.c++11新标准下,接受元素的个数或范围的insert版本返回指向第一个元素的迭代器。如果范围为空,不插入任何元素。

<<7.使用emplace:新标准引入了三个成员,emplace_front, emplace, emplace_back,这些操作构造而不是拷贝元素。

当我们调用emplace成员函数时,则是将参数传递给元素类型的构造函数。emplace成员使用这些参数在容器管理的内存空间中直接构造元素。

emplace_back会在容器管理的内存空间中直接创建对象,而调用push_back则会创建一个局部临时对象,并将其压入容器中。

emplace函数在容器中直接构造元素,传递给emplace函数的参数必须与元素类型的构造函数相匹配。

 

<2.访问元素

包括array在内的每个顺序容器都有一个front函数成员,而除forward_list之外的所有顺序容器都有一个back成员函数。这两个操作分别返回首元素和尾元素的引用。

当然可以用迭代器c.begin( )和(c.end( ))--。但是都要确保容器非空,如果容器为空,行为是未定义的

 1 #include <iostream>  
 2 #include <vector>  
 3 
 4 using namespace std;
 5 
 6 int main()
 7 {
 8     vector<int>ivec = { 1, 2, 3, 4, 5 };
 9     auto i = ivec.front();
10     auto j = ivec.back();        //forward_list不支持  
11     i = 10;                      //并不修改容器的元素的值,除非一开始定义auto &i = ivec.front();  
12     cout << i << endl;
13 
14     auto ii = ivec.front();    //容器第一个元素的值还是1;
15     cout << ii << endl;
16 
17     auto &iii = ivec.front(); 
18     iii = 10;
19     auto iiii = ivec.front();   //容器第一个元素的值变为10;
20     cout << iiii << endl;
21 
22     cout << j << endl;
23 }

 

顺序容器访问元素的操作例子

 1 #include <iostream>  
 2 #include <vector>  
 3 
 4 using namespace std;
 5 
 6 int main()
 7 {
 8     vector<int>ivec = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
 9     //vector<int>ivec;  
10     cout << ivec[0] << endl;            //c[n]       返回下标为n的元素的引用   n>=c.size() 结果是未定义的  
11     cout << ivec.at(0) << endl;         //c.at(n)    返回下标为n的元素的引用,如果下标越界,则抛出异常out_of_range  
12     cout << ivec.front() << endl;       //c.front()  返回容器的第一个元素  
13     cout << ivec.back() << endl;        //c.back()   返回容器的最后一个元素  
14     cout << *(ivec.begin()) << endl;    //迭代器解引用
15 }

注意:

<<1.迭代器c.end( )是末尾元素的下一个位置

<<2.为确保下标是合法的,我们可以使用at成员函数,at成员函数类似下标运算符,但如果下标越界,at会抛出一个out_of_range异常

3.删除元素

 1 #include <vector>  
 2 #include <list>  
 3 #include <iostream>  
 4 
 5 using namespace std;
 6 
 7 int main()
 8 {
 9     int ia[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
10     vector<int>ivec;
11     list<int>il;
12     for (int i = 0; i < 10; ++i)
13     {
14         ivec.push_back(ia[i]);
15         il.push_back(ia[i]);
16     }
17 
18     for (auto it = ivec.begin(); it != ivec.end();)
19     {
20         if (*it % 2 == 0)
21         {
22             it = ivec.erase(it);                   //erase返删除元素的下一个迭代器。  
23         }
24         else
25             it++;
26     }
27     for (const int &i : ivec)
28         cout << i << " ";
29     cout << endl;
30 
31     for (list<int>::iterator it = il.begin(); it != il.end();)
32     {
33         if (*it % 2 == 1)
34         {
35             it = il.erase(it);
36         }
37         else
38             it++;
39     }
40     
41     for (const int &i : il)
42         cout << i << " ";
43     cout << endl;
44 
45 }
46 //c.pop_back() 删除尾元素,返回void
47 //c.pop_front()删除首元素,返回void
48 //c.erase(p)   删除迭代器p指向的元素,返回被删除元素的下一个元素的迭代器
49 //c.erase(b, e) 删除迭代器(b, e)范围内的元素,返回e的下一个元素的迭代器
50 //c.clear()    删除c中所有的元素

注意:

<<1.删除操作会改变容器的大小,所以不适合array

<<2.forward_list不支持pop_back,   vector和string不支持pop_front

<<3.删除deque中除首尾位置之外的任何元素都会使所有迭代器,引用指针失效

<<4.删除前必须保证他们是存在的

<<5.注意删除返回迭代器的那些操作在循环中需要做出哪些改变!

 

<4.特殊的forward_list操作

forward_list其实就是数据结构的单向链表

操作如下:

 1 #include <forward_list>  
 2 #include <string>  
 3 #include <iostream>  
 4 #include <vector>  
 5 
 6 using namespace std;
 7 
 8 int main()
 9 {
10     forward_list<int>ft = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
11     vector<int>ivec = { 11, 22, 33, 44 };
12     for (const int &i : ft)
13     {
14         cout << i << " ";
15     }
16     cout << endl;
17 
18     auto iter1 = ft.before_begin();        //一般返回的是操作对象先前的迭代器  
19     auto iter2 = ft.begin();
20     //auto iter = ft.cbefore_begin();       返回一个const_iterator  
21 //cout << *iter1 << endl;                //error!!!返回单向链表首元素的前一个位置迭代器,解引用未定义,随机值  
22     cout << *iter2 << endl;                //首元素  
23     //insert  
24     //ft.insert_after(iter2, 100);         //插入到迭代器iter2指向的元素后面  
25     //ft.insert_after(iter2, 10, 100);     //10个100  
26     //ft.insert_after(iter2, ivec.begin(), ivec.end());//插入范围,b,e不能使本对象中的范围  
27     //ft.insert_after(iter2, {11,22,33,44,55});  
28     for (const int&i : ft)
29     {
30         cout << i << " ";
31     }
32     cout << endl;
33     ft.emplace_after(iter2, 111);
34     for (const int&i : ft)
35     {
36         cout << i << " ";
37     }
38     cout << endl;
39     //erase                                //返回的是擦出元素的下一个元素,一定要注意  
40     //ft.erase_after(iter2);               //擦除的是迭代器所指向的后一个元素  
41     ft.erase_after(iter1, ft.end());       //擦除的是迭代器所指向的元素的后一个元素开始  
42     for (const int&i : ft)
43     {
44         cout << i << " ";
45     }
46     cout << endl;
47 
48     return 0;
49 }

注意:

<<1.一般要处理整个容器时,要保存两个迭代器。curr用来找元素,prev用来删元素

1 auto curr = ft.begin( );  
2 auto prev = ft.before_begin( );

<<2.erase_after了一定要记得修改curr。

1 curr = ft.erase_after(n);  

<<3.一定要注意函数的返回值和每次使用的迭代器指向的位置和使用函数后该迭代器应该怎样变化。

 

<5.改变容器大小

c.resize(n)   :调整c大小为n个元素。若n<c.size( ),则多出的元素被丢弃,若必须添加新元素,新元素使用值初始化

c.resize(n,t) :调整c大小为n个元素。任何新添加的元素都初始值为t。

!如果缩小容器,则指向被删除元素的迭代器,引用和指针都会失效。对vector,string,deque,resize可能导致迭代器,指针引用失效。

!如果容器保存的是类类型的元素,向容器添加元素时我们必须提供初始值,或元素必须提供默认构造函数。

 

<6.!容器操作可能使迭代器失效

是否会使迭代器失效还要看迭代器具体使用什么数据结构实现的。

比如forward_list这个容器就比较特殊,insert( )返回的是插入元素位置的迭代器。删除erase( p )删除的是p的下一个位置的元素,返回的是删除元素下一个位置的迭代器。

因为它是由单向链表实现,指针是单向的,我们不能返回去访问先前的元素,那么删除必须指定的元素必须要保存prev先前的迭代器,不然无法实现删除。

所以每个容器具体实现的数据结构是重点。

一个例子

复制容器中元素值是奇数的,删除容器中的元素值是偶数的。

好好体会一下,这些容器的差别

 1 #include <iostream>  
 2 #include <vector>  
 3 #include <list>  
 4 #include <forward_list>  
 5 
 6 using namespace std;
 7 
 8 /////////////////////////////////////////
 9 ////删除偶数,复制奇数
10 /////////////////////////////////////////
11 
12 int main()
13 {
14     //vector  
15     vector<int>ivec = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
16     auto iter = ivec.begin();
17     while (iter != ivec.end())
18     {
19         if (*iter % 2 == 1)
20         {
21             iter = ivec.insert(iter, *iter);
22             iter += 2;
23         }
24         else
25             iter = ivec.erase(iter);
26     }
27     for (const int&i : ivec)
28         cout << i << " ";
29     cout << endl;
30 
31     //list  
32     list<int>il = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
33     auto iter2 = il.begin();
34     while (iter2 != il.end())
35     {
36         if (*iter2 % 2 == 1)
37         {
38             il.insert(iter2, *iter2);
39             iter2++;
40         }
41         else
42         {
43             iter2 = il.erase(iter2);
44         }
45     }
46     for (const int&i : il)
47         cout << i << " ";
48     cout << endl;
49 
50     //forward_list 尤其需要理解 
51     forward_list<int>ft = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
52     auto fter1 = ft.before_begin();
53     auto fter2 = ft.begin();
54     while (fter2 != ft.end())
55     {
56         if (*fter2 % 2 == 1)
57         {
58             fter1 = fter2;
59             fter2 = ft.insert_after(fter2, *fter2);
60             fter1++;
61             fter2++;
62         }
63         else
64         {
65             fter2 = ft.erase_after(fter1);
66         }
67     }
68     for (const int&i : ft)
69         cout << i << " ";
70     cout << endl;
71 }

 

在看个例子

给容器中每个元素后面插入值

 1     #include <vector>  
 2     #include <iostream>  
 3       
 4     using namespace std;  
 5       
 6     int main()  
 7     {  
 8         vector<int>ivec = {1,2,3,4,5,6,7,8,9,0};  
 9         auto iter = ivec.begin();  
10         while(iter != ivec.end())  
11         {  
12             iter++; //前插  
13             iter = ivec.insert(iter, 1);//插入后迭代器失效,如果不赋值给iter,iter失效。  
14             iter++;  
15         }  
16         for(const int&i : ivec)  
17             cout << i << " ";  
18         cout << endl;  
19     }  

如果把iter = ivec.insert(iter, 1)写成ivec.insert(iter, 1)程序会出很大的错误。

迭代器失效了我们却没有保存iter。

 

!注意:

<<1.对于insert来说,除了forward_list插入是后插外,其他容器是前插,前插返回前插的元素的迭代器,记得保存,插入后迭代器会失效。后插返回的是后插元素的迭代器。

总之不管插入位置,只要插入返回的就是插入的元素的迭代器,但是要考虑它的位置是前插还是后插。

<<2.管理迭代器,确认每次改变容器的操作之后都正确的重新定位迭代器。这个对vector,string,deque尤其重要。

<<3.记不起来时想下它的数据结构。


<7.vector对象是如何增长的

 1 #include <iostream>  
 2 #include <vector>  
 3 
 4 using namespace std;
 5 
 6 #define CAP(x) cout << "capicity = " << x.capacity() << endl;    //capacity()返回容器的容量  
 7 #define SIZ(x) cout << "size = " << x.size() << endl;            //size()返回容器内部元素的多少  
 8 
 9 int main()
10 {
11     vector<int>ivec;
12     CAP(ivec);
13     SIZ(ivec);
14     ivec.reserve(10);      //分配至少能容纳n个元素的空间,小于当前容量什么也不做  
15     CAP(ivec);
16     SIZ(ivec);
17     ivec.shrink_to_fit();  //缩减容量capacity到size()的大小  
18     CAP(ivec);
19     SIZ(ivec);
20     vector<int>ivec2(10, 10);
21     CAP(ivec2);
22     SIZ(ivec2);
23     ivec2.reserve(20);
24     CAP(ivec2);
25     SIZ(ivec2);
26     ivec2.resize(9);       //设置元素的大小,影响size(),多余的默认使用值初始化。  
27     CAP(ivec2);
28     SIZ(ivec2);
29 }

影响容量的操作数量不多。
resize( )设定size( )

reserve( )设定capacity( )

 

8.额外的string操作

<1.构造string的其他方法

 1 #include <iostream>  
 2 #include <string>  
 3 
 4 using namespace std;
 5 
 6 int main()
 7 {
 8     //s若拷贝字符数组必须有空字符结尾  
 9     //注意越界问题  
10 
11     char a[100] = "abcdefghijklmnopqrstuvwxyz";
12     string s(a, 10);               //string s(cp, n)   s是cp指向的数组中的前n个字符的拷贝,此数组至少应该包含n个字符  
13     cout << s << endl;
14     string s2 = "tfsongtfsong";
15     string s3(s2, 5);              //string s(s1, pos)   s是s1字符串从pos后面开始的字符串,如果n>s1.size()结果未定义  
16     cout << s3 << endl;
17     string s4(s2, 5, 2);           //string s(s1, pos, len)  s是s1从pos位置后面的长度为len的字符串  
18     string s5(a + 5, 2);             //这种方式a必须是字符数组,不能是string类型  
19     cout << s4 << endl;
20     cout << s5 << endl;
21 
22     //substr操作返回一个string, 它是原始string的一部分或者全部的拷贝,可以传递开始和结尾  
23     string ss = "hello,world";
24     string ss2;
25     ss2 = ss.substr(0, 5);          //复制下标0-4  
26     //ss2 = ss.substr(5);             //复制下标5到结束  
27     ss2.append(ss.substr(5));        //追加下标5到结束  
28     cout << ss2 << endl;
29     //ss2 = ss.substr(12);           //越界,发出out_of_range异常  ,12超过下标了
30 
31     return 0;
32 }

 

<2.改变string的其他方法

<<1.

 1 #include <string>  
 2 #include <iostream>  
 3 
 4 using namespace std;
 5 
 6 int main()
 7 {
 8     string s = "hello,world!";
 9     //string除了assign和insert,erase操作,还定义了自己的insert和erase版本  
10     s.insert(s.size(), 5, !);        //末尾插入5个感叹号  
11     cout << s << endl;
12     s.erase(s.size() - 5, 5);            //删除最后的5个字符  
13     cout << s << endl;
14 
15     //标准库string还定义了接受c风格字符数组的insert和assign版本  
16     string s1;
17     const char *p = "hello ,world!";
18     s1.assign(p, 7);                   //复制p的前7个字符  
19     cout << s1 << endl;
20     //s1.insert(s.size(), p+4);        //error: out_of_range  
21     //cout << s1 << endl;  
22 
23     string s2 = "hello", s3 = "world";
24     s2.insert(0, s3);                  //将s3插入到s2的0起始位置  
25     cout << s2 << endl;
26     s2.insert(0, s3, 0, s3.size()-1);  //从s2 的0处插入s3的(0, s3.size()-1)范围的元素  
27     cout << s2 << endl;
28 
29 }

<<2.append和replace函数

 1 #include <iostream>  
 2 #include <string>  
 3 
 4 using namespace std;
 5 
 6 int main()
 7 {
 8     string s("c++ primer");
 9     s.append(" 5th Ed.");   //在末尾追加字符串  
10     //s.insert(s.size(), " 5th Ed."); 和上面等价  
11     cout << s << endl;
12     //replace操作是调用erase和insert的一种简写方式  
13     //s.replace(11, 3, "4thMMMMMMMMMM");  
14     s.erase(11, 3);
15     s.insert(11, "4thMMMMMMMMMMMM");
16     cout << s << endl;
17 }

 

!<<3.string搜索操作
string的搜索操作

s.find(args)                             查找args第一次出现的位置

s.rfind(args)                            查找args最后一次出现的位置
s.find_first_of(args)              在s中查找args任何一个字符第一次出现的位置

s.find_last_of(args)              在s中查找args任何一个字符最后一次出现的位置

s.find_first_not_of(args)      在s中查找第一个不在args中的字符

s.find_last_not_of(args)      在s中查找最后一个不再args中的字符

args的形式

c,pos          pos位置开始查找字符c,pos默认为0

s2,pos       pos位置开始查找字符串s2,pos默认为0

cp,pos        pos位置开始查找指针cp指向的以空字符结尾的C风格字符串,pos默认为0

cp,pos,n     pos位置开始查找指针cp指向的数组的前n个字符。pos和n无默认值

例子1:

 1 #include <iostream>  
 2 #include <string>  
 3 
 4 using namespace std;
 5 
 6 int main()
 7 {
 8     //find,区分大小写  
 9     //搜索成功返回一个string::size_type值,若失败,返回一个string::npos的static成员(unsigned)  
10     string s("tfsong");
11     auto pos1 = s.find("fs");                //pos返回的是第一次出现的下标,若找不到则返回一个最大数  
12     cout << pos1 << endl;
13     string s1("0123456789");
14     string name("0rrr1r");
15     //查找给定字符串中任何一个字符匹配的位置。  
16     auto pos2 = name.find_first_of(s1);       //s1在name中出现的第一个数字在name中的下标  
17     cout << pos2 << endl;
18     //搜索第一个不在参数中的字符  
19     auto pos3 = s1.find_first_not_of(name);   //s1第一不在name中出现的是0  
20     cout << pos3 << endl;
21 }

 

逆向搜索:

一般的find都是从左到右,rfind提供了从右到左。


find例子2:

找出小写字母和数字区分开。

字母用了—代替,数字用了|代替

 1 #include <iostream>  
 2 #include <string>  
 3 
 4 using namespace std;
 5 
 6 int main()
 7 {
 8     string s("abc2fe32f2ds2");
 9     string s1("abcdefghijklmnopqrstuvwxyz");    //要去掉字符,那么首先要创建s1,s中的字符可以和s1匹配,然后替换  
10     string s2("1234567890");                    //同理去掉数字  
11     string::size_type pos = 0;                  //起始位置初始化为0  
12     while ((pos = s.find_first_of(s1, pos)) != string::npos)     //找到位置返回,如果没找到返回string::npos  
13     {
14         s.replace(pos, 1, "-");
15 
16         pos++;
17     }
18     cout << s << endl;
19     pos = 0;                                    //记得再次初始化,否则没有作用  
20     while ((pos = s.find_first_of(s2, pos)) != string::npos)
21     {
22         s.replace(pos, 1, "|");
23         pos++;
24     }
25     cout << s << endl;
26 }

注意:当从文件中读取字母时,若有空格,用getline读取一行。


例子3:也是重要的一个

从一篇英语文章中找出除了包含有字母出头bf或字母下出头pqg这种的单词外最长的一个单词。

 1 #include <iostream>  
 2 #include <string>  
 3 #include <fstream>                       //文件流  
 4 #include <vector>  
 5 #include <sstream>  
 6 
 7 using namespace std;
 8 
 9 int main(int argc, char *argv[])
10 {
11     vector<string>cps;                   //存储文章  
12     string sent;
13     string word;
14     string Maxlenword;
15     //ifstream is(argv[1]);      //linux下可以用这个,后面跟文件名当参数
16     ifstream is("test.txt");                //创建文件流并且读取  
17     string s1("bdfgpq");                 //这是要除去的单词所包含的  
18 
19     while (getline(is, sent))             //一次读取一行,因为如果是字符串流读取的话,遇到空格就会停止。  
20     {
21         cps.push_back(sent);
22     }
23     for (auto it = cps.begin(); it != cps.end(); ++it) //处理文章,一次一个string  
24     {
25         cout << "\n\n\n" << endl;
26         cout << "处理" << *it << endl;
27         istringstream ist(*it);          //string绑定到istringstream流上,因为一次要处理一个单词  
28         while (!ist.eof())
29         {
30             ist >> word;
31             cout << "word: " << word << endl;
32             auto pos = word.find_first_of(s1);        //验证单词  
33             if (pos == string::npos)                   //如果验证成功不包含s1,处理  
34             {
35                 if (word.size() > Maxlenword.size())   //比MAX大就存到MAX里面  
36                 {
37                     Maxlenword = word;
38                     cout << "temp MaxlenWord:" << Maxlenword << endl;
39                 }
40             }
41             pos = 0;                                   //记得pos的位置一定要恢复到起始  
42         }
43     }
44     cout << "MaxLengWord is " << Maxlenword << endl;   //输出最大的  
45 }

<<4.compare函数,compare函数和c函数的strcmp函数很相似

等于大于小于返回0,正数或者负数

compare的6个版本如下

 1 #include <string>  
 2 
 3 using namespace std;
 4 
 5 int main()
 6 {
 7     string s1 = "aaacaaaaa";
 8     string s2 = "aaaaaaaab";
 9     char *p = "aaaaaaaac";
10 
11     cout << s1.compare(s2) << endl;             //s1>s2  
12     cout << s1.compare(5, 3, s2) << endl;       //s1从第6个字符开始的3个字符小于s2  
13     cout << s1.compare(0, 5, s2, 0, 5) << endl; //从s1的0开始的5个字符和s2从0开始的5个字符比较  
14     cout << s1.compare(p) << endl;              //比较p指向的地址开始的字符串  
15     cout << s1.compare(5, 4, p) << endl;        //比较s1从第6个开始的4个字符和p指向的地址的字符串  
16     cout << s1.compare(5, 4, p, 4) << endl;     //比较s1从第6个开始的4个字符和p指向的地址的4个字符  
17 }

 

<<5.数值转换

新标准引入了多个函数,可以实现数值数据与标准库string之间的转换

 1 #include <iostream> 
 2 #include <string>
 3 using namespace std;
 4 int main()
 5 {
 6     int a = 100;
 7     string s;
 8     s = to_string(a);
 9     cout << s << endl;
10 
11     std::size_t m = 2;
12     string s1 = "1000";
13     int i = stoi(s1); //stoi  
14     long b = stol(s1);//stol  
15     unsigned long c = stoul(s1);//stoul  
16     long long d = stoll(s1);//stoll  
17     unsigned long long e = stoull(s1);//stoull  
18     cout << "int:" << i << "long:" << b << "unsigned long:" << c << "longlong:" << d << "unsigned longlong:" << e << endl;
19 
20     string s2 = "1000.11";
21     float f = stof(s2);//stof  
22     double g = stod(s2);//stod  
23     long double h = stold(s2);//stold  
24     cout << "float:" << f << "double:" << g << "long double:" << h << endl;
25 }

 

例子:

设计一个类,它有三个unsigned成员,分别表示年,月和日。为其编写构造函数,接受一个表示日期的string参数,你的构造函数应该能处理不同数据格式,

如Jan 1, 1900   1/1/1990    Jan 1 1900形式

记录一下9.51的题,没有处理January 1,1900形式,月份统一用Jan三个字母表示了,如果完全按题就是比较这个字符串的指定书目前几个字母就好。

 1 #include <string>  
 2 #include <iostream>  
 3 #include <sstream>  
 4 #include <vector>  
 5 #include <map>  
 6 
 7 using namespace std;
 8 
 9 class dat
10 {
11 public:
12     dat() = default;
13     dat(unsigned d, unsigned y, unsigned m) :
14         day(d), year(y), mouth(m)
15     {
16     };
17     dat(string &s);
18     void print(void)
19     {
20         cout << year << "." << mouth << "." << day << endl;
21     }
22 
23 private:
24     string s1 = "0123456789";
25     unsigned long day;
26     unsigned long year;
27     unsigned long mouth;
28 };
29 
30 dat::dat(string &s)
31 {
32     vector<string>ivecs;//存储年月日  
33     string s1;//temp  
34     string::size_type pos;
35     string des(",/");
36     while ((pos = s.find_first_of(des)) != string::npos)
37     {
38         s.replace(pos, 1, " ");
39         pos++;
40     }
41     //cout << s << endl;  
42     istringstream is(s);
43     while (!is.eof())
44     {
45         is >> s1;
46         ivecs.push_back(s1);
47     }
48     map<string, unsigned>mp;
49     mp.insert(pair<string, unsigned long>("Jan", 1));
50     mp.insert(pair<string, unsigned long>("Feb", 2));
51     mp.insert(pair<string, unsigned long>("Apr", 4));
52     mp.insert(pair<string, unsigned long>("May", 5));
53     mp.insert(pair<string, unsigned long>("Jun", 6));
54     mp.insert(pair<string, unsigned long>("Jul", 7));
55     mp.insert(pair<string, unsigned long>("Aug", 8));
56     mp.insert(pair<string, unsigned long>("Sep", 9));
57     mp.insert(pair<string, unsigned long>("Oct", 10));
58     mp.insert(pair<string, unsigned long>("Nov", 11));
59     mp.insert(pair<string, unsigned long>("Dec", 12));
60     //分别处理3个字符串,转化为年月日  
61     if (ivecs[0][0] <= 57 && ivecs[0][0] >= 48)
62         mouth = stoul(ivecs[0]);
63     else
64     {
65         auto it = mp.find(ivecs[0]);
66         mouth = (*it).second;
67     }
68     day = stoul(ivecs[1]);
69     year = stoul(ivecs[2]);
70 }
71 
72 int main()
73 {
74     string s;
75     s = "Jan 1, 1900";
76     dat date(s);
77     date.print();
78     string s2;
79     s2 = "1/1/1990";
80     dat date2(s2);
81     date2.print();
82     string s3;
83     s3 = "Jan 1 1900";
84     dat date3(s3);
85     date3.print();
86 
87 }

<9.容器适配器      

container_type是实现适配器的底层容器类型

三种:stack, queue, priority_queue

本质上:适配器是一种机制,能使某种事物的行为看起来像另外一种事物一样,一个容器适配器接受一种已有的容器类型,使其行为看起来像一种不同的类型

理解:容器适配器是基于已有的容器做的功能上的一种改进。

定义一个适配器:

每个适配器都有两个默认构造函数:默认构造函数创建一个空对象,接受一个容器的构造函数拷贝该容器来初始化适配器。

1     stack<int> stk(deq);  //用一个deque类型的容器来初始化stack适配器  
2     我们可以创建一个适配器时将一个命名的顺序容器作为第二个类型参数,来重载默认容器  
3     stack<string, vector<string>> str_stk;  
4     stack<string, vector<string>> str_stk2(ivecs);  

<1.stack

stack:一种元素先进后出的一种容器
stack可以使用除了array和forward_list类型的其他任何类型的容器来构造

 1     #include <iostream>  
 2     #include <string>  
 3     #include <stack>  
 4     #include <vector>  
 5     #include <deque>  
 6       
 7     using namespace std;  
 8       
 9     int main()  
10     {  
11         vector<int>ivec = {1,2,3,4,5,6,7,8,9,0};  
12         deque<int>deq = {1,2,3,4,5,6,7,8,9,0};  
13         //stack<int>stk(ivec);   error:因为stack本来是使用deque改进的一种适配器,  
14         //如果要用vector构造,必须像下面明确指定stack<int, vector<int>>.  
15         stack<int>stk(deq);   
16         stack<int, vector<int>>stk2(ivec);//明确指出stack在vector上面实现。  
17         int val;  
18         while(!stk.empty())  
19         {  
20             cout << stk.top() << endl;  
21             stk.pop();  
22         }  
23            
24         while(!stk2.empty())  
25         {  
26             cout << stk2.top() << endl;  
27             stk2.pop();  
28         }  
29           
30     }  
31       
32     //stack默认是deque实现,也可以在list或vector实现  
33     //s.pop()             删除栈顶元素,但不返回元素值  
34     //s.push(item)        创建一个新元素压入栈顶  
35     //s.emplace(args)     同前面所说args是参数,动态构造一个对象  
36     //s.top()             返回栈顶元素,但不将元素出栈  

<2.queue

queue:一种元素先进先出的容器

queue可以由list和deque来构造,但是不能由vector来构造,默认是deque构造

 1 #include <queue>  
 2 #include <vector>  
 3 #include <list>  
 4 #include <deque>  
 5 #include <iostream>  
 6   
 7 using namespace std;  
 8   
 9 int main()  
10 {  
11     list<int>il = {1,2,3,4,5,6,7,8,9,0};  
12     deque<int>deq = {1,2,3,4,5,6,7,8,9,0};  
13     vector<int>ivec = {1,2,3,4,5,6,7,8,9,0};  
14     //queue<int>que(il); error:queue默认是deque构造的,只能由deque来初始化  
15     queue<int>que(deq);//yes  
16     //当然可以显示指定由那个来构造  
17     queue<int, list<int>>que2(il);//yes  
18     //但是不能是vector<>  
19     //- -但是! 我用vector成功了  
20     //原因是vector可以支持front这个操作,但是把que.front换成que.pop()就会报一堆错  
21     //其他的list和deque说是可以使用应该是可以兼容全部的操作。so 动手实践很重要  
22     queue<int, vector<int>>que3(ivec);  
23     //while(!que.empty())  
24     {  
25         cout << que.front() << endl;  
26         que.pop();  
27     }  
28     cout << que.back() << endl;  
29   
30 }  
31   
32 queue和priority_queue都在queue头文件中  
33 priority_queue就是一种带优先级的队列,不如10个元素,它首元素是最大或最小。  
34   
35 操作  
36 //q.pop()返回queue的首元素或priority_queue的最高优先级元素,不删除元素  
37 //q.front()返回首元素或尾元素,不删除  
38 //q.back()  
39 //q.top()返回最高优先级的元素,不删除,只适用于priority_queue  
40 //q.push(item)在queue末尾或priority_queue中恰当位置创建一个元素  
41 //q.emplace(args)动态构造对象 

 


 

顺序容器