C++中的容器大致可以分为两个大类:顺序容器和关联容器。顺序容器中有包含有顺序容器适配器。
顺序容器:将单一类型元素聚集起来成为容器,然后根据位置来存储和访问这些元素。主要有vector、list、deque(双端队列)。顺序容器适配器:stack、queue和priority_queue。
关联容器:支持通过键来高效地查找和读取元素。主要有:pair、set、map、multiset和multimap。
接下来依次对于各种容器做详细的介绍。
一、顺序容器
1、顺序容器定义
为了定义一个容器类型的对象,必须先包含相关的头文件:
定义vector:#include <vector>
定义list:#include <list>
定义deque:#include <deque>
定义示例
1
2
3
|
vector< int > vi; list< int > li; deque< int > di; |
2、顺序容器初始化
初始化示例:
1
2
3
4
5
6
7
8
9
10
|
//初始化为一个容器的副本 vector< int > vi; vector< int > vi2(vi); //利用vi来初始化vi2 //初始化为一段元素的副本 char *words[] = { "stately" , "plump" , "buck" , "mulligan" }; size_twords_size = sizeof (words)/ sizeof ( char *); list<string> words2(words, words + words_size); //分配和初始化指定数目的元素 constlist< int >::size_type list_size = 64; list<string> slist(list_size, "a" ); // 64 strings, each is a |
3、顺序容器支持的指针运算
①所有顺序都支持的指针运行
②vector 和 deque 容器的迭代器提供额外的运算
③迭代器失效:一些容器操作会修改容器的内在状态或移动容器内的元素。这样的操作使所有指向被移动的元素的迭代器失效,也可能同时使其他迭代器失效。使用无效迭代器是没有定义的,可能会导致与悬垂指针相同的问题。
④begin和end成员:begin和end操作产生指向容器内第一个元素和最后一个元素的下一位置的迭代器。
3、顺序容器操作
①添加元素
添加元素示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
//在容器首部或者尾部添加数据 list< int > ilist; ilist.push_back(ix); //尾部添加 ilist.push_front(ix); //首部添加 //在容器中指定位置添加元素 list<string> lst; list<string>::iterator iter = lst.begin(); while (cin >> word) iter = lst.insert(iter, word); // 和push_front意义一样 //插入一段元素 list<string> slist; string sarray[4] = { "quasi" , "simba" , "frollo" , "scar" }; slist.insert(slist.end(), 10, "A" ); //尾部前添加十个元素都是A list<string>::iterator slist_iter = slist.begin(); slist.insert(slist_iter, sarray+2, sarray+4); //指针范围添加 |
②容器大小的操作
示例:
1
2
3
4
|
list< int > ilist(10, 1); ilist.resize(15); // 尾部添加五个元素,值都为0 ilist.resize(25, -1); // 再在尾部添加十个元素,元素为-1 ilist.resize(5); // 从尾部删除20个元素 |
③访问元素
1
2
3
4
5
6
|
int vector< int > vi; for ( int i=0;i<10;i++)vi.push_back(i); cout<<vi[0]<<endl; cout<<vi.at(0)<<endl; cout<<vi[10]<<endl; //越界错误 cout<<vi.at(10)<<endl; //越界错误 |
④删除元素
示例:
1
2
3
4
5
6
7
8
9
10
|
//删除第一个或最后一个元素 list< int > li; for ( int i=0;i<10;i++)list.push_back(i); li.pop_front(); //删除第一个元素 li.pop_back(); //删除最后一个元素 //删除容器内的一个元素 list< int >::iterator iter =li.begin(); if (iter!= li.end())li.erase(iter); //删除容器内所有元素 li.clear(); |
⑤赋值与swap
1
2
3
4
|
list<string> sl1,sl2; for ( int i=0;i<10;i++)sl2.push_back( "a" ); sl1.assign(sl2.begin(),sl2.end()); //用sl2的指针范围赋值,sl1中十个元素都为a sl1.assign(10, "A" ); //s1被重新赋值,拥有十个元素,都为A |
swap示例:
vector<string> vs1(3); // vs1有3个元素
vector<string> vs(5); // vs2有5个元素
vs1.swap(vs2);//执行后,vs1中5个元素,而vs2则存3个元素。
⑥vector的自增长:capacity 和 reserve 成员
为了提高vector的效率,不用每次添加元素都重新分配空间。vector会在分配空间时候预分配大于需要的空间。vector 类提供了两个成员函数:capacity 和reserve 使程序员可与 vector 容器内存分配的实现部分交互工作。
capacity操作:获取在容器需要分配更多的存储空间之前能够存储的元素总数
reserve操作:告诉vector容器应该预留多少个元素的存储空间
capacity(容量)与size(长度)的区别:size指容器当前拥有的元素个数,而capacity则指容器在必须分配新存储空间之前可以存储的元素总数。capacity是比size大的一般情况下。
示例:
1
2
3
4
5
6
|
vector< int > ivec; cout << "ivec: size: " << ivec.size() << " capacity: " << ivec.capacity() << endl; //都为0 for (vector< int >::size_type ix = 0; ix != 24; ++ix)ivec.push_back(ix); cout << "ivec: size: " << ivec.size() << " capacity: " << ivec.capacity() << endl; //capacity大于size |
可以通过函数reserve()来操作预留空间
//在之前一段代码的基础上
ivec.reserve(ivec.capacity()+50);//为ivec增加了50的预留空间
另外:如果不手动操作来预留空间,每当 vector 容器不得不分配新的存储空间时,以加倍当前容量的分配策略实现重新分配。
4、容器的选用
选择容器类型的常规法则:
①如果程序要求随机访问元素,则应使用 vector 或 deque 容器。
②如果程序必须在容器的中间位置插入或删除元素,则应采用 list 容器。
③如果程序不是在容器的中间位置,而是在容器首部或尾部插入或删除元素,则应采用 deque 容器。
④如果只需在读取输入时在容器的中间位置插入元素,然后需要随机访问元素,则可考虑在输入时将元素读入到一个 list 容器,接着对此容器重新排序,使其适合顺序访问,然后将排序后的 list 容器复制到一个 vector容器。
如果程序既需要随机访问又必须在容器的中间位置插入或删除元素,选择何种容器取决于下面两种操作付出的相对代价:随机访问 list 容器元素的代价,以及在 vector 或 deque 容器中插入/删除元素时复制元素的代价。通常来说,应用中占优势的操作(程序中更多使用的是访问操作还是插入/删除操作)将决定应该什么类型的容器。
5、容器适配器
①适配器通用的操作和类型
②适配器的初始化
所有适配器都定义了两个构造函数:默认构造函数用于创建空对象,而带一个容器参数的构造函数将参数容器的副本作为其基础值。
默认的stack和queue都基于deque容器实现,而priority_queue则在vector容器上实现。
示例:
1
2
3
4
|
vector< int > vi; deque< int > deq; stack< int > stk(deq); //用deq初始化stk stack< int > stk1(vi); //报错 |
③适配器的操作
栈适配器:
队列和优先级队列:
二、关联容器
1、pair
①pairs类型提供的操作
②pairs类型定义和初始化
pair<string, string> test("A", "B");
③pairs其他操作
1
2
3
4
5
6
7
8
|
//pairs对象的操作 string firstBook; if (author.first == "James" && author.second == "Joyce" )firstBook = "Stephen Hero" ; //生成新的pair对象 pair<string, string> next_auth; next_auth = make_pair( "A" , "B" ); //第一种方法 next_auth = pair<string, string>( "A" , "B" ); //第二种方法 cin>>next_auth.first>>next_auth.second; //第三种方法 |
2、map
map 是键-值对的集合。map 类型通常可理解为关联数组:可使用键作为下标来获取一个值,正如内置数组类型一样。而关联的本质在于元素的值与某个特定的键相关联,而并非通过元素在数组中的位置来获取。
①map对象的定义
map<string, int> word_count;
这个语句定义了一个名为 word_count 的 map 对象,由 string 类型的键索引,关联的值则int型。
map访问:对迭代器进行解引用时,将获得一个引用,指向容器中一个pair<const string,int>类
型的值。通过pair类型的访问方式进行访问。
②map的构造函数
③键类型的约束
在使用关联容器时,它的键不但有一个类型,而且还有一个相关的比较函数。所用的比较函数必须在键类型上定义严格弱排序(strict weak ordering):可理解为键类型数据上的“小于”关系,虽然实际上可以选择将比较函数设计得更复杂。
对于键类型,唯一的约束就是必须支持 < 操作符,至于是否支持其他的关系或相等运算,则不作要求。
④map类定义的类型
value_type是存储元素的键以及值的pair类型,而且键为const。例如,word_count 数组的value_type 为pair<const string,int> 类型。value_type 是 pair 类型,它的值成员可以修改,但键成员不能修改。
⑤map添加元素
添加元素有两种方法:1、先用下标操作符获取元素,然后给获取的元素赋值 2、使用insert成员函数实现
下标操作添加元素:如果该键已在容器中,则 map 的下标运算与 vector 的下标运算行为相同:返回该键所关联的值。只有在所查找的键不存在时,map 容器才为该键创建一个新的元素,并将它插入到此 map 对象中。此时,所关联的值采用值初始化:类类型的元素用默认构造函数初始化,而内置类型的元素初始化为 0。
insert 操作:
示例:
1
2
|
word_count.insert(map<string, int >::value_type( "Anna" , 1)); word_count.insert(make_pair( "Anna" , 1)); |
insert的返回值:包含一个迭代器和一个bool值的pair对象,其中迭代器指向map中具有相应键的元素,而bool值则表示是否插入了该元素。如果该键已在容器中,则其关联的值保持不变,返回的bool值为true。在这两种情况下,迭代器都将指向具有给定键的元素。
pair<map<string, int>::iterator, bool> ret =
word_count.insert(make_pair(word, 1));
ret存储insert函数返回的pair对象。该pair的first成员是一个map迭代器,指向插入的键。ret.first从insert返回的pair对象中获取 map 迭代器;ret.second从insert返回是否插入了该元素。
⑥查找并读取map中的元素
map中使用下标存在一个很危险的副作用:如果该键不在 map 容器中,那么下标操作会插入一个具有该键的新元素。所以map 容器提供了两个操作:count 和 find,用于检查某个键是否存在而不会插入该键。
1
2
3
4
|
int occurs = 0; if (word_count.count( "foobar" ))occurs = word_count[ "foobar" ]; map<string, int >::iterator it = word_count.find( "foobar" ); if (it != word_count.end())occurs = it->second; |
⑦从map对象中删除元素
1
2
3
4
|
string removal_word = "a" ; if (word_count.erase(removal_word)) cout << "ok: " << removal_word << " removed\n" ; else cout << "oops: " << removal_word << " not found!\n" ; |
3、set
①set容器的定义和使用
set 容器的每个键都只能对应一个元素。以一段范围的元素初始化set对象,或在set对象中插入一组元素时,对于每个键,事实上都只添加了一个元素。
1
2
3
4
5
6
7
8
|
vector< int > ivec; for (vector< int >::size_type i = 0; i != 10; ++i) { ivec.push_back(i); ivec.push_back(i); } set< int > iset(ivec.begin(), ivec.end()); cout << ivec.size() << endl; //20个 cout << iset.size() << endl; // 10个 |
②在set中添加元素
1
2
3
4
|
set<string> set1; set1.insert( "the" ); //第一种方法:直接添加 set< int > iset2; iset2.insert(ivec.begin(), ivec.end()); //第二中方法:通过指针迭代器 |
③从set中获取元素
set 容器不提供下标操作符。为了通过键从 set 中获取元素,可使用 find运算。如果只需简单地判断某个元素是否存在,同样可以使用 count 运算,返回 set 中该键对应的元素个数。当然,对于 set 容器,count 的返回值只能是1(该元素存在)或 0(该元素不存在)
1
2
3
4
5
6
|
set< int > iset; for ( int i = 0; i<10; i++)iset.insert(i); iset.find(1) // 返回指向元素内容为1的指针 iset.find(11) // 返回指针iset.end() iset.count(1) // 存在,返回1 iset.count(11) // 不存在,返回0 |
3、multimap 和 multiset
关联容器 map 和 set 的元素是按顺序存储的。而 multimap 和multset 也一样。因此,在multimap和multiset容器中,如果某个键对应多个实例,则这些实例在容器中将相邻存放。迭代遍历multimap或multiset容器时,可保证依次返回特定键所关联的所有元素。
①迭代器的关联容器操作
以上就是本文的全部内容,希望对大家的学习有所帮助。