STL-Containers

advertisement
Standard Template Library
(STL) Containers
Moshe Fresko
Bar-Ilan University
Object Oriented Programming
2007-2008
Standard Library Design
The C++ Standard Library
1.
2.
3.
4.
5.
6.
Provides support for language features such as memory
management and run-time type information
Supplies information about implementation-defined aspects of
the language, such as the largest float value
Supplies functions that cannot be implemented optimally in
the language itself for every system, such as sqrt() and
memmove()
Supplies non-primitive facilities that a programmer can rely on
for portability, such as lists, maps, sort functions, and I/O
streams
Provides a framework for extending the facilities
Provides the common foundation for other libraries
Containers
Container is an object that holds other
objects


Examples: lists, vectors, and associative arrays

Objects can be added and removed from a
container

STL defines two kinds of containers
1.
2.
Sequences
Associative Containers
Containers
<vector>
<list>
<deque>
<queue>
<stack>
<map>
<set>
<bitset>
one-dimensional array of T
doubly-linked list of T
double-ended queue of T
queue of T
(priority_queue is defined here)
stack of T
associative array from T to Value
(multimap is defined here)
set of T
(multiset is defined here)
array of booleans
vector

Vectors are dynamic containers that keep sequence of elements of a template
type T.

The standard vector is a template defined in namespace std and presented in
<vector>.

It first defines a set of types
template <class T, class A=allocator<T> > class std::vector {
public:
typedef T value_type;
// type of element
typedef A allocator_type; // type of mem.mngr.
typedef typename A::size_type size_type;
typedef typename A::difference_type difference_type;
typedef ……… iterator; // T*
typedef ……… const_iterator; // const T*
typedef std::reverse_iterator<iterator> reverse_iterator;
typedef std::reverse_iterator<const_iterator>
const_reverse_iterator;
typedef typename A::pointer pointer;
typedef typename A::const_pointer const_pointer;
typedef typename A::reference reference;
typedef typename A::const_reference const_reference;
//………
};
vector types usage Example
template<class C> typename C::value_type sum(const C& c)
{
typename C::value_type s=0;
typename C::const_iterator p=c.begin();
while (p!=c.end()) {
s+=*p;
++p;
}
return s;
}
// Usage of that function
int main() {
vector<int>& vi;
vector<float>& vf;
vector<Complex>& vc;
// .........
int isum = sum(vi);
float fsum = sum(vf);
Complex csum = sum(vc);
}
iterators

Iterators are used to navigate containers without the programmers
having to know the actual type used to identify the elements.
template <class T, class A=allocator<T> > class std::vector {
public:
// ………
iterator begin();
// points to 1st element
const_iterator begin() const;
iterator end();
// points to one-past-last element
const_iterator end() const;
reverse_iterator rbegin();
// 1st element of reverse seq.
const_reverse_iterator rbegin() const;
reverse_iterator rend();
// one-past-last element of reverse seq.
const_reverse_iterator rend() const;
// ………
};



begin()/end() works in ordinary element order
rbegin()/rend() works in reverse element order
In a list of three elements A, B, C
begin()
end()
A
rend()
B
C
rbegin()
iterators

To keep track of a list of Complex numbers
…
vector<Complex> vec ;
vec.push_back(Complex(3,4)) ;
vec.push_back(Complex(5.0,6.6)) ;
vec.push_back(Complex(-3.1,5.2)) ;
…
vector<Complex>::iterator it;
for(it=vec.begin();it!=vec.end();++it) {
Complex c = *it ;
cout<<“The next Complex number in list is:”<<c<<endl;
}
for(vector<Complex>::reverse_iterator rit=vec.rbegin();
rit!=rend();++rit)
cout<<“Reverse order next Complex number:”<<*rit<<endl;
…
iterators

Example: A utility that searches for the last occurrence of an element in
a sequence
template<class C>
typename C::iterator find_last(C& c, typename C::value_type v)
{
typename C::reverse_iterator p=c.rbegin();
while (p!=c.rend()) {
if (*p==v) {
typename C::iterator i=p.base();
return --i;
}
++p;
}
return c.end();
}
Element Access

In vector container types one can easily and efficiently access
individual elements in any order.
template <class T, class A=allocator<T> > class std::vector {
public:
// ………
reference operator[](size_type n); // unchecked access
const_reference operator[](size_type n) const;
reference at(size_type n);
// checked access
const_reference at(size_type n) const;
reference front();
const_reference front() const;
reference back();
const_reference back() const;
// ………
};
// first element
// last element
Element Access

operator[]() provides unchecked access whereas at() does a range check and may throw
out_of_range.
void f(vector<int>& vi, int i1, int i2) {
for (int i=0;i<vi.size();++i) {
int next=v[i];
// …
}
try {
v.at(i1)=v.at(i2);
// range check is done
} catch(out_of_range) { // …
}




}
The return values are reference or const_reference depending on the container object being
const or not.
For vector<X>, reference is simply X& and const_reference is const X&.
vector<int> vi(1);
vi[0]=10;
int& i=vi[0];
i=20;
// same as v[i]=20
The effect of trying to create an out_of_range reference is undefined.
front() returns the element pointed by begin().
Constructors, Destructors, Copy operators
template <class T, class A=allocator<T> > class std::vector {
public:
// ………
explicit vector(const A& =A());
explicit vector(size_type n, const T& val=T(), const A&=A());
template <class In>
// In must be an input iterator
vector(In first,In last,const A&=A());// copy from [first:last[
vector(const vector& x);
~vector();
vector& operator=(const vector& x);
template <class In>
void assign(In first, In last);// In must be an input iterator
void assign(size_type n, const T& val);
// ………
};
Examples for Ctor, Dtor
vector<Record> vr(10000);
void f(int s1, int s2) {
vector<int> vi(s1);
vector<double>* p=new vector<double>(s2);
}
class Num {
public: Num(long);
}
vector<Num> v1(1000); // Error. No default ctor
vector<Num> v2(1000,Num(0));
// ok
void f(const list<X>& lst) {
vector<X> vl(lst.begin(),lst.end());
char p[]=“Something”;
vector<char> v2(p,&p[sizeof(p)-1]);
}
Stack operations
template <class T, class A=allocator<T> > class std::vector {
public:
// ………
void push_back(const T& x);
// add to end
void pop_back();
// remove last element
// ………
};

Example
void f(vector<char>& s) {
s.push_back(‘a’);
s.push_back(‘b’);
s.push_back(‘c’);
s.pop_back();
// s.back()==‘b’
s.pop_back();
// s[s.size()-1]==‘a’
}
List Operations

To add/remove elements to/from the middle of the vector
template <class T, class A=allocator<T> >
class std::vector {
public:
// ………
iterator insert(iterator pos, const T& x);
void insert(iterator pos,size_type n, const T& );
template <class In>
void insert(iterator pos, In first, In last);
iterator erase(iterator pos);
iterator erase(iterator first, iterator last);
// ………
};
Example List Operations

Examples
vector<string> fruit;
fruit.push_back(“peach”); fruit.push_back(“apple”);
fruit.push_back(“kiwi”);
fruit.push_back(“pear”); fruit.push_back(“starfruit”);
fruit.push_back(“grape”);
sort(fruit.begin(),fruit.end());
vector<string>::iterator
p1=find_if(fruit.begin(),fruit.end(),initial(‘p’));
vector<string>::iterator
p2=find_if(p1,fruit.end(),initial_not(‘p’));
fruit.erase(p1,p2);
// deletes those starting with ‘p’
fruit.erase(find(fruit.begin(),fruit.end(),”starfruit”));
fruit.erase(fruit.begin()+1);
fruit.insert(fruit.begin()+1,”cherry”);
fruit.insert(fruit.end(),”orange”);
Size and Capacity

A vector grows as needed and sometimes it is worthwhile to affect its
growth.
template <class T, class A=allocator<T> >
class std::vector {
public:
// ………
size_type size() const(); // number of elements
bool empty() const {return size()==0;}
size_type max_size() const;// size of largest vec
void resize(size_type sz, T val=T());
size_type capacity() const;// size of mem
void reserve(size_type n); // reserve mem
// ………
};
Size and Capacity - Example
class Histogram {
vector<int> count;
public:
Histogram(int h) : count(max(h,8)) { }
void record(int i) {
if (i<0) i=0;
if (count.size()<=i) count.resize(i+1);
count[i]++;
}
};
void addNNumbers(vector<int>& vi, int n) {
vi.clear();
vi.reserve(n);
for (int i=0;i<n;++i)
vi.push_back(i);
}
Other member functions
template <class T, class A=allocator<T> >
class std::vector {
public:
// ………
void swap(vector&);
allocator_type get_allocator()const;
// ………
};
template <class T, class A>
bool std::operator==(const vector<T,A>& x, const vector<T,A>& y);
bool std::operator<(const vector<T,A>& x, const vector<T,A>& y);
// Example: for giving memory back to system
vector<X> v;
//…
// many operations on v
//
{ vector<X> tmp=v;
v.swap(tmp); }
Exercises
1.
2.
3.
4.
Create a vector containing the letters of alphabet in
order. Print them in reverse order.
Create a string vector. Read from the user strings
and insert to it. Sort and print the sorted list.
Create a two dimensional string vector. Read names
from the user and insert it into a vector according
to its initial character. ‘A’ will be inserted into the
65th vector, ‘B’ into 66th, etc.
Write a class Student containing name, idnumber,
and grade. Create a vector of students and fill it.
Sort them. How can you find a student and change
his grade?
List


List keeps a sequence of elements like vector. But it
is optimized for insertion and deletion.
It is implemented by doubly-linked list.
template <class T, class A=allocator<T>
class std::list {
public:
// types and operations like vector
// except [], at(), capacity(), reserve()
// …
};
Splice, sort, merge

List has some operations suited for doubly linked lists.
template <class T, class A=allocator<T> class std::list {
public:
void splice(iterator pos, list&x);
// move all elements from x to pos
void splice(iterator pos, list& x, iterator p);
// move only *p from x
void splice(iterator pos, list& x,
iterator first, iterator last);
void merge(list&);
template <class Cmp> void merge(list&,Cmp);
void sort();
template <class Cmp> void sort(Cmp);
// …
};
Comparisons
Associative containers require that the key
elements can be ordered.
Some container operations (like sort, merge,
binary_search) require ordering.
Default ordering is done according to operator<.
If an ordering criteria is cmp




1.
2.
3.
cmp(x,x) is false
If cmp(x,y) and cmp(y,z) then cmp(x,z)
Define equiv(x,y) as !(cmp(x,y)||cmp(y,x)
If equiv(x,y) and equiv(y,z) then equiv(x,z)
Comparisons - example



Consider
template <class R> void sort(R first, R last);
template <class R, class Cmp> void sort(R first, R last,
Cmp cmp);
For sorting a string list case-insensitive
class NoCase {
public:
bool operator()(const string&x,const string&y) const {
string::const_iterator p=x.begin(),q=y.begin();
while (p!=x.end() && q!=y.end() &&
toupper(*p)==toupper(*q))
{ ++p; ++q; }
if (p==x.end()) return q!=y.end();
return toupper(*p)<toupper(*q);
}
};
Usage
sort(fruit.begin(),fruit.end(),NoCase());
Download