顺序容器
Posted 灵魂摆渡人
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了顺序容器相关的知识,希望对你有一定的参考价值。
声明:本文借鉴多人的博客,但文中代码均已提炼修改,本文仅作为知识点记录为主
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 //以上是关于顺序容器的主要内容,如果未能解决你的问题,请参考以下文章