C++进阶篇一:C++ 标准模板库之容器和迭代
阅读量:6122 次
发布时间:2019-06-21
本文共 19637 字,大约阅读时间需要 65 分钟。
C++容器
容器(container)是C++中能够存储几乎任何数据类型的数据结构,分为一级容器、容器适配器以及类容器。不同的容器有一些共同的成员函数,通过迭代(在性质上与指针相似)可以实现对容器元素的操作。不同容器支持的迭代(具体来说是迭代权限)是不同的,这也决定了它们所能用于的算法是不同的(因为每种算法都有其特定支持的迭代类型)。内置数组也可以作为标准库算法的操作对象,此时它的指针就作为迭代。
以下为C++容器共有的成员函数:
- 默认构造函数:初始化一个空容器
- 复制构造函数:复制一个现存同类型容器
- 移动构造函数:(C++11)将一个容器中的内容移到另一个同类型容器中
- 析构函数:详见
- empty方法:判断容器是否为空
- size方法:返回容器中现有元素个数,forward_list不能用
- max_size方法:返回一个容器中所能融入的最大元素个数
- operator=:用于复制容器内容。特别地,在C++11中用于移动容器内容。
- operator<, operator<=, operator>, operator>=, operator==, operator!=:比较不同容器之间的属性
- insert方法:向容器中插入元素
- swap方法:交换两个容器的元素,C++11中作为一个使用移动构造函数的全局函数,用于交换同类型容器的地址。
- begin方法:可返回iterator或const_iterator,用于指向容器的第一个元素
- end方法:可返回iterator或const_iterator,用于指向容器最后一个元素的后一个位置
- rbegin方法:可返回iterator或const_iterator,用于指向容器的最后一个元素
- rend方法:可返回iterator或const_iterator,用于指向容器的第一个元素的前一个位置
- cbegin:(C++11)返回const_iterator,用于指向容器的第一个元素
- cend, crbegin和crend则可以以此类推
- erase:从容器中删除一个或多个元素
- clear:删除容器中所有元素(对array不适用)
C++容器共有函数使用注意点:
- <, <=, >, >=, ==和!=不能用于优先队列
- <, <=, >和 >=不能用于非顺序关联型容器(后面会详细解释)
- rbegin, rend, crbegin和crend不能用于forward_list
各类型容器简介如下:
一级容器:
一级容器分为序列型容器、顺序关联型容器和非顺序关联型容器。
- 序列型容器:vector,deque, list, forward_list(为单链表,C++11新特性), array
- 顺序关联型容器(容器中的key是排序好的):set和map(不允许重复key,此外在C++11中key是不可修改的), multiset和multimap(允许重复的key,此外在C++11中key是不可修改的)。这些头文件分别是<set>和<map>
- 非顺序关联型容器(因为容器中的key未排序,所以性能方面可能更出色):unsorted_set, unsorted_map, unsorted_multiset和unsorted_multimap,用法与对应的顺序关联型容器类似。
在具体的算法实现方面,如果需要在容器最后进行频繁插入删除操作的话,则vector是一个理想的选择(因为vector为连续存储结构);如果需要频繁在两头进行插入删除操作,则deque是一个理想选择;如果需要在任意位置进行插入或删除操作,则list是一个理想选择。 **容器适配器:**栈、队列和优先队列 **类容器:**内置数组、比特集合(bitsets,用于对于标志集合(即flag values)和数值数组进行快速数学向量运算时使用)
C++各容器特有常用函数归纳:
序列型容器:
- assign(n, val):将容器中的元素改为出现n次的val值。
- assign(iters_begin, iters_end):将元素中的元素改为从begin到end的一个序列。
vector 容器(使用见例2和例3)
- push_back(val):在vector末尾添加一个val元素
- size:返回vector中元素的个数
- capacity: 返回vector中能够存储的最多元素的个数
- shift_to_fit:使得capacity与size相同,但是视编译环境不同,此指令可能被忽略。
- at(index): 读写相应index的元素,对index范围作出检查。
-
- insert(iter_index, ele):在用迭代(以begin()或cbegin()作为0加index)计算出来的index处插入ele。
- insert(iter_insert_index, iter_start_range, iter_end_range):在被插入元素的(用迭代表示的)insert_index处插入(用迭代表示的)从start_range到end_range的一个系列。
- erase(iter_index):删除迭代表示的index处相应元素。
- erase(iter_begin, iter_end):删除用迭代表示的从begin到end的元素。
- front:返回指向第一个元素的地址。
- back:返回最后一个元素的地址。
list 容器(使用见例4)
- push_front(ele):在list前端增加元素ele,此方法仅适用于forward_list, list和queue。
- push_back(ele):类似vector的同名函数
- sort:注意此处的sort与STL的sort是不同的,list特有的sort无传入参数并且默认对list中的元素按非降序排序。
- reverse():将列表中的元素倒置。
- splice(iter_insert_index, list& b):将list b插入本list由iterator指定的下标处并将其中所有元素删除。
- splice(iter_insert_index, list& b, iter_delete_index):从list b中删除由迭代delete_index确定的那一个元素,并将list b中所有元素插入insert_index后删除list b中的所有元素。
- splice(iter_insert_index, list& b, iter_start_delete_index, iter_end_delete_index):从list b中删除由迭代start_delete到end_delete中的一个范围内的元素,并将list b中所有元素插入insert_index后删除list b中的所有元素。
- merge(list &b):(前提是本列表和列表b都已经排好序)将list b元素merge到本列表中,默认为升序排列。
- unique():删除列表中重复的元素,每个元素仅出现一次。
- swap(list &b):将本列表与b列表的所有元素对换。
- remove(ele):从list中删去ele元素。
- remove_if(未完待续)
deque 容器
与所有的vector操作基本类似,又增加了push_front和pop_front方法。
关联型容器:
- find(x):在容器中寻找x元素并返回x元素位置的迭代。若未找到x,则返回end()位置的迭代。
multiset< T, compareObj >: (使用见例5),T为元素类型,compareObj为比较方式,一般情况下为less<T>
- count(val):统计元素val在multiset中出现的次数
- insert(val):将元素val插入multiset
- insert(iter_index, val):将元素val插入iter_index
- insert(a.iter_start(), a.iter_end()):将a容器一定范围内的值插入multiset,注意iter_start和iter_end表示反应容器元素下标的迭代
- lower_bound(x):返回该元素最早出现的位置下标的迭代。若不存在该元素返回end位置的迭代。
- upper_bound(x):返回该元素最晚出现的位置下标的迭代。若不存在该元素返回end位置的迭代。
- equal_range(x):返回包含lower_bound和upper_bound两个迭代的一对元素对象(pair object),一般被赋值给自动类型的变量(如auto p)。分别用p.first和p.second两个常量获得。在去指针化时要注意这两个指针没有指向end()或rbegin()以及类似实际不存在元素的位置。
set: 与multiset一样,除了插入重复key时操作会被忽略。
multimap<T1, T2, compareObj>:(使用见例6)此处compareObj针对的是key。multimap建立了从键到值的多对一关系。
特别地,在C++11中,一个多重映射的初始化可以写成例如如下的形式:
multimap
> mMaps = { {10, 22.2}, {20, 9.345}, {5, 77.4} };
- make_pairs:一般使用方式为mMap.insert(make_pair(key, val)); 用于向multimap中插入键值对。
- insert(pairObj):像multimap中插入一个组对象。
- insert({key, val}):以列表初始化的方式插入键值对。
- pair.first:取得某个键值对的键。
- pair.second:取得某个键值对的值。
- count(key):统计key这个键在multimap中出现的次数。
map:在C++中用来表示严格的一对一映射。
在multimap的基础上多了用下标读取的功能。如pairs[25]表示读取map pairs中键为25的值。
容器适配器:
栈(头文件<stack>):C++中,栈可用vector, list或者deque实现(默认为deque实现)。举例如下:
stack intDequeStack; //Stack with underlying deque.stack
> intVStack; //Stack with underlying vectorstack
> intListStack; //Stack with underlying list
- push(x):将x元素入栈
- pop():将栈顶元素出栈
- top():返回栈最顶端元素,不推出
队列(头文件<queue>):同样,队列也可以由vector, list或者deque实现(默认为deque实现)。常用方法如下:
- push(x):将元素x插入队尾。
- front():获取队列头元素的地址(不推出)
- back():获取队列尾元素的地址(不推出)
- pop():将队列头元素出队
优先队列:包含功能与队列完全一致,只是自动将最大的元素通过调堆放在队头。算法上的实现方式为堆(heap)。可以通过声明comparator改变推出顺序。
类容器:
bitset:编译时尺寸固定的一种用来操作比特集合的容器。定义容器示例:
//Size is an unsigned integer:bitset b;
- set(index):将index处的比特值设为on
- reset(index):将index处的比特值设为off
- flip:将所有位倒置
- :获得所在为的地址,不作下标检查
- at(index):获得所在为的地址,作下标检查
- test(index):作下标检查,返回所在位的下标的布尔值。on为true,off为false
- count:返回比特集合中下标on的位数
- any:若比特集合中有位是on,则返回true
- all:若比特集合中所有位都是on,则返回true
- none:若比特集合中所有位都是off,则返回true == / !=:比较两个比特集合是否相等 &= / != / ^=/ |= / >>= (右移)/ <<=(左移):进行比特集合运算 to_string / to_ulong():分别返回比特集合的string和无符号长整数
C++迭代器
C++的迭代用于指向一级容器、序列或范围(sequences or ranges,比如输入流和输出流),适用前面提到的begin和end方法。其中,迭代器的权限从高到低分为任意存取(random access)、双向迭代(bidiretional)、单向迭代(forward)以及输入输出流(input和output,属于同级别)。其中,高级权限的迭代器拥有本级别以及所有低于其级别的迭代权限。
各个迭代权限所对应的操作如下(从高级别权限到低级别权限):
- 随机存取级别:p += i, p -= i, p + i, i + p, p - i, p[i](从p所指位置开始存取对应第加i个位置操作), p - p1(p和p1的距离之差), p < p1(p的位置是否在p1之前), p <= p1, p > p1以及p >= p1
- 双向迭代:--p, p--
- 向前迭代:提供所有输入和输出级别的操作
- 输出迭代:*p, p1 = p2
- 输入迭代:*p, p->m(通过迭代读取元素m), p1 == p2, p1 != p2
- 所有级别:++p, p++, p1 = p2 (将迭代p2赋给迭代p1)
注意:++操作对于反向迭代对象(reverse_iterator和const_reverse_iterator)来说是指向当前元素的前一个元素。
前文提到,不同的一级容器所对应的迭代权限是不同的,各一级容器的迭代权限如下:
- 随机存取级别:vector, array, deque
- 双向迭代级别:list, 顺序关联型容器、非顺序关联型容器
- 向前迭代级别:forward_list
C++标准模版容器常用异常
- out_of_range:提示下标越界,例如常与at方法结合使用。
- invalid_argument:提示函数被传入无效参量。
- length_error:提示被创建容器中元素过多。
- bad_alloc:提示内存不足,使用new函数创建内存时失败。
以下为若干C++ 标准模版库使用的例子:
例1:使用输入流读取两个整数进行相加:
// Fig. 15.4: fig15_04.cpp// Demonstrating input and output with iterators.#include #include // ostream_iterator and istream_iteratorusing namespace std;int main(){ cout << "Enter two integers: "; // create istream_iterator for reading int values from cin istream_iterator< int > inputInt( cin ); int number1 = *inputInt; // read int from standard input ++inputInt; // move iterator to next input value int number2 = *inputInt; // read int from standard input // create ostream_iterator for writing int values to cout ostream_iterator< int > outputInt( cout ); cout << "The sum is: "; *outputInt = number1 + number2; // output result to cout cout << endl;} // end main
本例中,ostream_iterator<int>为只输出int类型(或其兼容类型)的输出流,它的头文件是<iterator>
例2: vector扩展、获取容量以及元素数量、迭代器使用若干操作
// Fig. 15.10: Fig15_10.cpp// Demonstrating Standard Library vector class template.#include #include // vector class-template definitionusing namespace std;// prototype for function template printVectortemplate < typename T > void printVector( const vector< T > &integers2 );int main(){ const size_t SIZE = 6; // define array size int values[ SIZE ] = { 1, 2, 3, 4, 5, 6 }; // initialize values //vector< int > integers(4); // create vector of ints vector integers; cout << "The initial size of integers is: " << integers.size() << "\nThe initial capacity of integers is: " << integers.capacity(); // function push_back is in vector, deque and list for(int i = 0; i < (sizeof(values) / sizeof(int)) ; i++) integers.push_back(values[i]); //Size is 7 and capacity becomes 8 in this case. cout << "\nThe size of integers is: " << integers.size() << "\nThe capacity of integers is: " << integers.capacity(); //Cause the vector to be cleaned up: integers.resize(4); integers.shrink_to_fit(); cout << "\nThe size of integers is: " << integers.size() << "\nThe capacity of integers is: " << integers.capacity(); cout << "\n\nOutput built-in array using pointer notation: "; // display values using pointer notation for ( const int *ptr = begin( values ); ptr != end( values ); ++ptr ) cout << *ptr << ' '; cout << endl; cout << "Output vector using ptr notation: "; for(auto ptr = begin(integers); ptr != integers.end(); ptr++) cout << *ptr << ' '; cout << "\nOutput vector using iterator notation: "; printVector( integers ); cout << "\nReversed contents of vector integers: "; // display vector in reverse order using const_reverse_iterator for ( auto reverseIterator = integers.crbegin(); reverseIterator!= integers.crend(); ++reverseIterator ) cout << *reverseIterator << ' '; cout << endl;} // end main// function template for outputting vector elementstemplate < typename T > void printVector( const vector< T > &integers2 ){ // display vector elements using const_iterator for ( auto constIterator = integers2.cbegin(); constIterator != integers2.cend(); ++constIterator ) cout << *constIterator << ' '; cout << endl; // //Can also be following way:// for(auto const item : integers2)// cout << item << ' ';} // end function printVector
注意:capacity方法视编译器不同,效果可能不同。调用shrink_to_fit()方法(C++11中)能够使得容器的capacity和元素个数相等。
例3:Vector insert, copy, erase, clean以及异常处理
// Fig. 15.11: fig15_11.cpp// Testing Standard Library vector class template // element-manipulation functions.#include #include // array class-template definition#include // vector class-template definition#include // copy algorithm #include // ostream_iterator iterator#include // out_of_range exceptionusing namespace std;int main(){// const size_t SIZE = 6; // array< int, SIZE > values = { 1, 2, 3, 4, 5, 6 };// vector< int > integers(values.cbegin(), values.cend()); int values[] = {1, 2, 3, 4, 5, 6}; vector integers; integers.insert(integers.begin(), begin(values), end(values));// for(int i : values)// integers.push_back(i); ostream_iterator< int > output( cout, " " ); cout << "Vector integers contains: "; copy( integers.cbegin(), integers.cend(), output ); cout << "\nFirst element of integers: " << integers.front() << "\nLast element of integers: " << integers.back(); integers[ 0 ] = 7; // set first element to 7 integers.at( 2 ) = 10; // set element at position 2 to 10 // insert 22 as 2nd element integers.insert( integers.cbegin() + 1, 22 ); cout << "\n\nContents of vector integers after changes: "; copy( integers.cbegin(), integers.cend(), output ); // access out-of-range element try { integers.at( 100 ) = 777; } // end try catch ( out_of_range &outOfRange ) // out_of_range exception { cout << "\n\nException: " << outOfRange.what(); } // end catch // erase first element integers.erase( integers.cbegin() ); cout << "\n\nVector integers after erasing first element: "; copy( integers.cbegin(), integers.cend(), output ); // erase remaining elements integers.erase( integers.cbegin(), integers.cend() ); cout << "\nAfter erasing all elements, vector integers " << ( integers.empty() ? "is" : "is not" ) << " empty"; //insert elements from the array values //integers.insert( integers.cbegin(), values.cbegin(), values.cend() ); //If values is a built in type array, this sentence is: integers.insert(integers.cbegin(), begin(values), end(values)); cout << "\n\nContents of vector integers before clear: "; copy( integers.cbegin(), integers.cend(), output ); // empty integers; clear calls erase to empty a collection integers.clear(); cout << "\nAfter clear, vector integers " << ( integers.empty() ? "is" : "is not" ) << " empty" << endl;} // end main
- 此例中,copy方法(头文件<algorithm>)的三个参数分别为输入容器的起始位置、输入容器的结束位置(不包括)以及输出容器。
- 此外,front方法和back方法分别返回第一个元素的地址以及最后一个元素的地址。注意begin方法是返回指向第一个元素的随即存储迭代,而end方法返回指向最后一个元素后一个位置的随机存储迭代。此外,调用front和end方法时必须确保容器非空,否则将会取得未定义参数。
- 另:at方法跟用下标的区别在于at方法对于迭代越界进行检查,越界时抛出out_of_range异常。
- insert方法:可被使用在任意类型的顺序序列(除了array和forward_list,原因是array尺寸固定,forward_list只有insert_after方法),将元素插入到特定位置(从begin到end,但不包括end)。其他版本的insert能将同一个元素插入多次或者插入一列元素。如如下代码在第二个元素的位置插入22这个值4遍:
// insert 22 at 2nd element 4 timesintegers.insert(integers.begin() + 1, 4, 22);
- erase方法可以删除某一个或某一范围内的特定元素(除了array不能删除以及forward_list用的是erase_after之外)。注意erase方法通常删除容器相应位置的对象,但是对于动态分配的内存并没有delete内存,从而可能导致内存泄漏。对于unique_ptr来说,erase可以删除分配的内存以及销毁指针;对于shared_ptr来说,地址计数会被降低,并且在在地址计数变为0的情况下,内存也会被删除。
例4:list容器使用示例:
// Fig. 15.13: Fig15_13.cpp// Standard library list class template.#include #include #include // list class-template definition#include // copy algorithm#include // ostream_iteratorusing namespace std;// prototype for function template printListtemplate < typename T > void printList( const list< T > &listRef );int main(){ const size_t SIZE = 4; array< int, SIZE > ints = { 2, 6, 4, 8 }; list< int > values; // create list of ints list< int > otherValues; // create list of ints // insert items in values values.push_front( 1 ); values.push_front( 2 ); values.push_back( 4 ); values.push_back( 3 ); cout << "values contains: "; printList( values ); values.sort(); // sort values cout << "\nvalues after sorting contains: "; printList( values ); // insert elements of ints into otherValues otherValues.insert( otherValues.cbegin(), ints.cbegin(), ints.cend() ); cout << "\nAfter insert, otherValues contains: "; printList( otherValues ); // remove otherValues elements and insert at end of values values.splice( values.cend(), otherValues ); cout << "\nAfter splice, values contains: "; printList( values ); values.sort(); // sort values cout << "\nAfter sort, values contains: "; printList( values ); // insert elements of ints into otherValues otherValues.insert( otherValues.cbegin(), ints.cbegin(), ints.cend() ); otherValues.sort(); // sort the list cout << "\nAfter insert and sort, otherValues contains: "; printList( otherValues ); // remove otherValues elements and insert into values in sorted order values.merge( otherValues ); cout << "\nAfter merge:\n values contains: "; printList( values ); cout << "\n otherValues contains: "; printList( otherValues ); values.pop_front(); // remove element from front values.pop_back(); // remove element from back cout << "\nAfter pop_front and pop_back:\n values contains: "; printList( values ); values.unique(); // remove duplicate elements cout << "\nAfter unique, values contains: "; printList( values ); // swap elements of values and otherValues values.swap( otherValues ); cout << "\nAfter swap:\n values contains: "; printList( values ); cout << "\n otherValues contains: "; printList( otherValues ); // replace contents of values with elements of otherValues values.assign( otherValues.cbegin(), otherValues.cend() ); cout << "\nAfter assign, values contains: "; printList( values ); // remove otherValues elements and insert into values in sorted order values.merge( otherValues ); cout << "\nAfter merge, values contains: "; printList( values ); values.remove( 4 ); // remove all 4s cout << "\nAfter remove( 4 ), values contains: "; values.reverse(); printList( values ); cout << endl;} // end main// printList function template definition; uses // ostream_iterator and copy algorithm to output list elementstemplate < typename T > void printList( const list< T > &listRef ){ if ( listRef.empty() ) // list is empty cout << "List is empty"; else { ostream_iterator< T > output( cout, " " ); copy( listRef.begin(), listRef.end(), output ); } // end else} // end function printList
例 5:multiset以及unordered_multiset使用示例:
// Fig. 15.15: Fig15_15.cpp// Standard Library multiset class template#include #include #include // multiset class-template definition#include // copy algorithm#include // ostream_iteratorusing namespace std;int main(){ const size_t SIZE = 10; array< int, SIZE > a = { 7, 22, 9, 1, 18, 30, 100, 22, 85, 13 }; //There should be a space between the right > of int and the right > of multiset //Otherwise compiling error may occur. multiset< int, less< int > > intMultiset; // multiset of ints ostream_iterator< int > output( cout, " " ); cout << "There are currently " << intMultiset.count( 15 ) << " values of 15 in the multiset\n"; intMultiset.insert( 15 ); // insert 15 in intMultiset intMultiset.insert( 15 ); // insert 15 in intMultiset cout << "After inserts, there are " << intMultiset.count( 15 ) << " values of 15 in the multiset\n\n"; // find 15 in intMultiset; find returns iterator auto result = intMultiset.find( 15 ); if ( result != intMultiset.end() ) // if iterator not at end cout << "Found value 15\n"; // found search value 15 // find 20 in intMultiset; find returns iterator result = intMultiset.find( 20 ); if ( result == intMultiset.end() ) // will be true hence cout << "Did not find value 20\n"; // did not find 20 // insert elements of array a into intMultiset intMultiset.insert( a.cbegin(), a.cend() ); cout << "\nAfter insert, intMultiset contains:\n"; copy( intMultiset.cbegin(), intMultiset.cend(), output ); // determine lower and upper bound of 22 in intMultiset cout << "\n\nLower bound of 22: " << *( intMultiset.lower_bound( 22 ) ); cout << "\nUpper bound of 22: " << *( intMultiset.upper_bound( 22 ) ); // use equal_range to determine lower and upper bound // of 22 in intMultiset auto p = intMultiset.equal_range( 22 ); cout << "\n\nequal_range of 22:" << "\n Lower bound: " << *( p.first ) << "\n Upper bound: " << *( p.second ); cout << endl; int count = 0; while(p.first != intMultiset.begin()){ p.first--; count ++; } cout << "p.first is the " << count << "th index in the set. " << endl; cout << endl; unordered_set intSet; intSet.insert(a.begin(), a.end()); copy( intMultiset.cbegin(), intMultiset.cend(), output );} // end main
备注:所有顺序关联型容器中的key(在set中key即元素)必须支持比较型功能函数。在比较类型的声明中(此处为less<int>),被比较的类型必须要与比较功能函数相对应(此处为operator<)。
例6 multimap使用示例
// Fig. 15.17: Fig15_17.cpp// Standard Library multimap class template.#include #include
参考资料:
Paul Deitel & Harvey Deitel, C++11程序设计(英文版)(第2版)