C++容器
容器(container)是C++中能够存储几乎任何数据类型的数据结构,分为一级容器、容器适配器以及类容器。不同的容器有一些共同的成员函数,通过迭代(在性质上与指针相似)可以实现对容器元素的操作。不同容器支持的迭代(具体来说是迭代权限)是不同的,这也决定了它们所能用于的算法是不同的(因为每种算法都有其特定支持的迭代类型)。内置数组也可以作为标准库算法的操作对象,此时它的指针就作为迭代。
以下为C++容器共有的成员函数:
- 默认构造函数:初始化一个空容器
- 复制构造函数:复制一个现存同类型容器
- 移动构造函数:(C++11)将一个容器中的内容移到另一个同类型容器中
- 析构函数:详见https://my.oschina.net/SamYjy/blog/828146
- 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<int, double, less<int> > 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<int> intDequeStack; //Stack with underlying deque.
stack<int, vector<int> > intVStack; //Stack with underlying vector
stack<int, list<int> > 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<size> b;
- set(index):将index处的比特值设为on
- reset(index):将index处的比特值设为off
- flip:将所有位倒置
- index:获得所在为的地址,不作下标检查
- 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 <iostream>
#include <iterator> // ostream_iterator and istream_iterator
using 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 <iostream>
#include <vector> // vector class-template definition
using namespace std;
// prototype for function template printVector
template < 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<int> 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 elements
template < 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 <iostream>
#include <array> // array class-template definition
#include <vector> // vector class-template definition
#include <algorithm> // copy algorithm
#include <iterator> // ostream_iterator iterator
#include <stdexcept> // out_of_range exception
using 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<int> 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 times
integers.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 <iostream>
#include <array>
#include <list> // list class-template definition
#include <algorithm> // copy algorithm
#include <iterator> // ostream_iterator
using namespace std;
// prototype for function template printList
template < 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 elements
template < 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 <iostream>
#include <array>
#include <set> // multiset class-template definition
#include <algorithm> // copy algorithm
#include <iterator> // ostream_iterator
using 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<int> 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 <iostream>
#include <map> // multimap class-template definition
using namespace std;
int main()
{
multimap< int, double, less< int > > pairs; // create multimap
cout << "There are currently " << pairs.count( 15 )
<< " pairs with key 15 in the multimap\n";
// insert two value_type objects in pairs
pairs.insert( make_pair( 15, 2.7 ) );
pairs.insert( make_pair( 15, 99.3 ) );
cout << "After inserts, there are " << pairs.count( 15 )
<< " pairs with key 15\n\n";
auto val1 = make_pair( 30, 111.11 );
// insert five value_type objects in pairs
pairs.insert( val1 );
pairs.insert({10, 22.2} );
pairs.insert( make_pair( 25, 33.333 ) );
pairs.insert( make_pair( 20, 9.345 ) );
pairs.insert( make_pair( 5, 77.54 ) );
cout << "Multimap pairs contains:\nKey\tValue\n";
// walk through elements of pairs
for ( auto mapItem : pairs )
cout << mapItem.first << '\t' << mapItem.second << '\n';
cout << endl;
} // end main
参考资料:
Paul Deitel & Harvey Deitel, C++11程序设计(英文版)(第2版)