9.4 How a vector Grows
To support fast random access, vector elements are stored contiguously - each element is adjacent to the previous element. (so that index can work)
When there is no space for a new element, vector must allocate new memory to hold old elements plus the new one, copy the elements and deallocate the old memory.
For most applications, the best container to use is vector.
The way vectors achieve fast allocation is by allocating capacity beyond what is immediately needed.
9.4.1 capacity and reserve Members
The vector class includes two members, capacity and reserve, that let us interact with the memory-allocation part of vector's implementation.
capacity tells use how many elements the container could hold before it must allocate more space.
reserve lets us tell the vector how many elements it should be prepared to hold.
size is the number of elements in the vector; capacity is how many it could hold before new space must be allocated.capacity >= size.
vector<int> ivec;
cout << ivec.size() << ivec.capacity() <<endl; //size is 0, capacity is 0
for(vector<int>::size_type ix = 0; ix != 24; ++ix)
ivec.push_back(ix);
cout << ivec.size() << ivec.capacity() <<endl; //size is 24, capacity is 32
ivec.reserve(50);
cout << ivec.size() << ivec.capacity() <<endl; //size is 24, capacity is 50
//as long as there is excess capacity, vector must not reallocate
while (ivec.size()!= ivec.capacity())
ivec.push_back(0);
cout <<ivec.size() << ivec.capacity() <<endl; //size is 50, capacity is 50
ivec.push_back(0);
cout <<ivec.size() << ivec.capacity()<<endl; //size is 51, capacity is 100.
//so the library will double capacity each time (VC gives different capacity value)
We can implement vector differently, but:
1. it must provide reserve and capacity functions.
2. it must not allocate new memory until it is forced to do so.
9.5 Deciding Which Container to Use
whether elements are stored contiguously has significant impacts:
1. The costs to add or delete elements from the middle of the container
2. The costs to perform nonsequential access to elements of the container
vector and deque provide fast nonsequential access to elements at the cost of making it expensive to add or remove elements anywhere other than the ends of the container.
list supports fast insertion and deletion anywhere but at the cost of making nonsequential access to elements expensive.
How Insertion Affects Choice of Container
list: inserting or removing an element in a list does not move any other elements. But random access is not supported
vector: inserting or removing anywhere except at the back of a vector requires that each element to the right of the inserted(or deleted) element to be moved.
deque:
1. like vector, it is inefficient to insert or erase elements in the middle of the deque
2. unlike vector, a deque offers efficient insert and erase at the front as well as the back.
3. unlike list and like vector, deque supports fast random access to any element
4. Inserting elements at the front or back of a deque does not invalidate any iterators. Erasing the front/back element invalidates only iterators referring to the elements erased. Inserting or erasing anywhere else invalidates all iterators. ??? implementation?
Hints on Selecting Which Container to Use
1. If the program requires random access to elements, use a vector or a deque
2. If the program needs to insert or delete elements in the middle of the container, use a list
3. If the program needs to insert or delete elements at the front and the back, but not in the middle, of the container, use a deque.
4. If we need to insert elements in the middle of the container only while reading input and then need random access to the elements, consider reading them into a list and then reordering the list as appropriate for subsequent access and copying the reordered list into a vector.
When you are not certain which container to use. Try uses only operations common to both vector and lists: Use iterators, not subscripts, and avoid random access to elements.
9.6 strings Revisited P 337
string supports most of the sequential container operations. String support the same operations that vector support: The exceptions are:
1. string does not support stack operation: front, back, pop_back.
2. string does not support constructor that takes a single size parameter.
String-only functions:
1. substr operation
2. append/replace functions
3. search operations
4. compare operations
9.7 Container Adaptors
An adaptor is a mechanism for making one thing act like another.
A container adaptor takes an existing container type and makes it act like a different abstract type.
To use an adaptor, we must include its associated header:
#include <stack> //stack adaptor
#include <queue> //both queue and priority_queue adaptors
Initializing an Adaptor
Each adaptor defines two constructors:
1. The default constructor that creates an empty object
2. A constructor that takes a container and makes a copy of that container as its underlying value.
stack<int> stk; //empty object
stack<int> stk2(deq); //deq is a deque<int>
Overriding the Underlying Container Type
By default both stack and queue are implemented in terms of deque, and priority_queue is implemented on a vector.
We can override the default container type by naming a sequential container as a second type argument when creating the adaptor:
//empty stack implemented on top of vector
stack<string, vector<string> > str_stk;
//str_stk2 is implemented on top of vector and holds a copy of svec
stack<string, vector<string> > str_stk2(svec);
There are constraints on which containers can be used for a given adaptor:
1. any sequential container can be used for a stack
2. queue adaptor required push_front, so could be built on a deque/list but not a vector.
3. priority_queue requires random access, so could be build on a deque/vector but not list.
size_type | Type large enough to hold size of largest object of this type |
value_type | Element type |
container_type | Type of the underlying container on which the adaptor is implemented |
A a; | Create a new empty adaptor named a |
A a(c); | Create a new adaptor named a with a copy of the container c. |
Relational Operators | Each adaptor supports all relational operators: ==, !=, < <=, >, >= |
9.7.1 Stack Adaptor
s.empty() | return true if stack is empty |
s.size() | return number of elements in stack |
s.pop() | Removes, but does not return, the top element |
s.top() | Returns, but does not remove, the top element |
s.push(item) | Places a new top element |
9.7.2 Queue and Priority Queue
queue uses a first-in, first-out storage and retrieval policy.
priority queue place a newly entered item ahead of all those with a lower priority. By default, the library uses the < operator on the element type to determine relative priorities.
q.empty() | return true if queue is empty |
q.size() | return number of elements in queue |
q.pop() | removes, but does not return the front element from the queue |
q.front() | returns, but does not remove, the front element on the queue only for queue |
q.back() | returns, but does not remove, the back element on the queue only for queue |
q.top() | returns, but does not remove the highest-priority element. only for priority_queue |
q.push(item) | places a new element at the end of the queue place a new element at its appropriate position based on priority in a priority queue |
example:
#include<iostream>
#include<queue>
#include<string> //must include the string header
using namespace std;
int main()
{
priority_queue<string> pq;
pq.push("abc");
pq.push("bac");
pq.push("cba");
while(!pq.empty())
{
cout << pq.top() <<endl;
pq.pop();
}
}
out put is: cba, bac, abc