introduction to c & c++ lecture 6 – stl jjcao. content c++ is about efficient programming with...
TRANSCRIPT
Introduction to C & C++
Lecture 6 – STL
JJCAO
Content
• C++ is about efficient programming with abstractions
• STL is a good example, let our programs Succinct, Abstract & efficient
– Bookkeeping details– Take care of memory– Worry about actual problem we need to solve.
The standard library
• Gentle introduction of STL:– container classes– generic algorithms
• OOP vs Generic Programming
Iterator: common interface for any arbitrary container type
1. Container Types
Most used STL containers• std::pair• std::vector• std::map
Sequential containerSequential Containersvector Supports fast random accesslist Supports fast insertion/deletiondeque Double-ended queue
Sequential Container Adaptorsstack Last in/First out stack
queue First in/First out queue
priority_queue Priority-managed queue
Vector#include <iostream>#include <vector>using namespace std;int main(){
vector<int> coll; //vector container for integer elements// append elements with values 1 to 6for (int i=1; i<=6; ++i) { coll.push_back(i);
}//print all elements followed by a spacefor (int i=0; i<coll.size( ); ++i) { cout << coll[i] << ' ';}cout << endl;
}
vector
std::vector<int> v; // create an empty vectorstd::vector<T> v(5); // creates a vector and initializes it with five values. calls five times the default constructor of type Tv.reserve (80); // reserve memory for 80 elements
//make coll be a copy of the contents of lcoll.assign(l.begin(),l.end());
remove//remove all elements with value valcoll.erase(remove(coll.begin(),coll.end(), val), coll.end());
To remove only the first element that has a certain value, you must use the following statements:
//remove first element with value valstd::vector<Elem>::iterator pos;pos = find(coll.begin(),coll.end(), val);if (pos != coll.end()) {
coll.erase(pos);}
Using Vectors as Ordinary Arrays
Deque /deck/
#include <iostream>#include <deque>using namespace std;int main(){
deque<float> coll; //deque container for floating-point elements//insert elements from 1.1 to 6.6 each at the frontfor (int i=1; i<=6; ++i) {coll.push_front(i*1. 1); //insert at the front}//print all elements followed by a spacefor (int i=0; i<coll.size(); ++i) {cout << coll[i] << ' ';}cout << endl;
}
list
#include <iostream>#include <list>using namespace std;int main(){
list<char> coll; //list container for character elementsfor (char c='a'; c<= ' z '; ++c) {// append elements from 'a' to 'z'
coll.push_back(c);}/* print all elements* - while there are elements* - print and remove the first element*/while (! coll.empty()) {
cout << coll.front() << ' ';coll.pop_front();
}cout << endl;
}
Iterator: Iterating Over Elements of a List
list<char> coll; //list container for character elements// append elements from 'a' to 'z'for (char c='a'; c<='z'; ++c) { coll.push_back(c);}/*print all elements* - iterate over all elements*/list<char>::const_iterator pos;for (pos = coll.begin(); pos != coll.end(); ++pos) { cout << *pos << ' ' ;}
Iterator Types
Every container defines two iterator types:1. container::iterator
is provided to iterate over elements in read/write mode.
2. container: : const_iteratoris provided to iterate over elements in read-only mode.
for (pos = coll.begin(); pos != coll.end(); ++pos) {...}
for (pos = coll.begin() ; pos < coll.end(); ++pos) {...}
X
√
Associative Containers
pair
namespace std { template <class U, class V> struct pair {
U first;V second;pair() : first(U() ), second(V() ) {}//default constructor// constructor for two valuespair(const U& a, const V& b):first(a),sectond(b){}// copy constructor with implicit conversionstemplate<class T1, class T2>pair(const pair<T1,T2>& p):first(p.first),sectond(p.second){}
};
//comparisons template <class U, class V> bool operator== (const pair<U,V>&, const pair<U,V>&); … //similar: !=, <=, <, >, >= template <class U, class Y> pair<U, V> make_pair(const U& first, const V& second);}
Struct instead of classSTL use pair a lot
Usages
• std::pair<int,float> p; //initialize p. first and p.second with Zero
• std::pair<int,float>(42,7.77); // more clear• std::make_pair(42, '@'); // easier
Set
typedef std::set<int> IntSet; //type of the collectionIntSet coll; //set container for int values// insert elements from 1 to 6 in arbitray order. value 1 gets inserted twicecoll.insert(3);coll.insert(1);coll.insert(5);coll.insert(4);coll.insert(1);coll.insert(6);coll.insert(2);/* print all elements by iterating over all elements */IntSet::const_iterator pos;for (pos = coll.begin(); pos != coll.end(); ++pos) {
std::cout << *pos << ' ';}std::cout << std::endl;
No push_back() or push_front() . you can't specify the position of
the new element
elements in associative containers are always sorted automatically according to their sorting criterion.
Multisettypedef std::multiset<int> IntSet; //type of the collectionIntSet coll; //set container for int values// insert elements from 1 to 6 in arbitray order. value 1 gets inserted twicecoll.insert(3);coll.insert(1);coll.insert(5);coll.insert(4);coll.insert(1);coll.insert(6);coll.insert(2);/* print all elements by iterating over all elements */IntSet::const_iterator pos;for (pos = coll.begin(); pos != coll.end(); ++pos) {
std::cout << *pos << ' ';}std::cout << std::endl; 1 1 2 3 4 5 6
multimap
typedef multimap<int, string> IntStringMMap; //type of the collectionIntStringMMap coll; //set container for int/string values//insert some elements in arbitrary order. a value with key 1 gets inserted twicecoll.insert(make_pair(5,"tagged"));coll.insert(make_pair(2,"a"));coll.insert(make_pair(1,"this"));coll.insert(make_pair(4,"of"));coll.insert(make_pair(6,"strings"));coll.insert(make_pair(1,"is"));coll.insert(make_pair(3,"multimap"));/* print all element values, element member second is the value*/IntStringMMap::iterator pos;for (pos = coll.begin(); pos != coll.end(); ++pos) {
cout << pos->second << ' ';}cout << endl;
this is a multimap of tagged strings
Maps as Associative Arrays
/* type of the container:* - string: keys have type string* - float: values have type float*/typedef map<string,float> StringFloatMap;StringFloatMap coll;//insert some elements into the collectioncoll["VAT"] = 0.15;coll["Pi"] = 3.1415;coll["an arbitrary number"] = 4983.223;coll["Null"] = 0;/*print all elements* - element member first is the key* - element member second is the value*/StringFloatMap::iterator pos;for (pos = coll.begin(); pos != coll.end(); ++pos) {
cout << "key: \"" << pos->first << "\" “ << "value: " << pos->second << endl;}
key: "Null" value: 0key: "Pi" value: 3.1415key: "VAT" value: 0.15key: "an arbitrary number" value: 4983.22
Common Operations of Container Classes (continues)
When to Use which Container• By default, you should use a vector.
Algorithm#include <algorithm>vector<int> coll; vector<int>::iterator pos;//insert elements from 1 to 6 in arbitrary ordercoll.push_back(2); coll.push_back(5);coll.push_back(4); coll.push_back(1);coll.push_back(6); coll.push_back(3);//find and print minimum and maximum elementspos = min_element (coll.begin(), coll.end()); // max_element cout << "min: " << *pos << endl;//sort all elements in ascending ordersort (coll.begin(), coll.end());//find the first element with value 3pos = find (coll.begin(), coll.end(), 3); //reverse the order of the found element with value 3 and all following elementsreverse (pos, coll.end());//print all elementsfor (pos=coll.begin(); pos!=coll.end(); ++pos) {
cout << *pos << ' ' ;}cout << endl; 1 2 6 5 4 3
Range [ )
Handling Multiple Rangeslist<int> coll1;vector<int> coll2;//insert elements from 1 to 9for (int i=1; i<=9; ++i) {
coll1.push_back(i);}
coll2.resize (coll1. size());//RUNTIME ERROR:// - overwrites nonexisting elements in the destinationcopy (coll1 .begin(), coll1.end(), coll2.begin());
deque<int> coll3(coll1 size());//copy elements from first into third collectioncopy (coll1.begin(), coll1.end(), coll3.begin());
usually must define both thebeginning and the end only for the first range. For all other ranges you need to pass only theirbeginnings.
Iterator Adapters
• Iterators are pure abstractions: Anything that behaves like an iterator is an iterator.
• Predefined iterators (iterator adapters):
Insert Iterators
#include <iterator>list<int> coll1;for (int i=1; i<=9; ++i) {
coll1.push_back(i);}// copy the elements of coll1 into coll2 by appending themvector<int> coll2;copy (coll1.begin(), coll1.end(), back_inserter(coll2)); //copy the elements of coll1 into coll3 by inserting them at the front// - reverses the order of the elementsdeque<int> coll3;copy (coll1.begin(), coll1.end(), front_inserter(coll3));//copy elements of coll1 into coll4// - only inserter that works for associative collectionsset<int> coll4;copy (coll1.begin(), coll1.end(), inserter(coll4,coll4.begin()));
Stream Iteratorsvector<string> coll;/*read all words from the standard input* - source: all strings until end-of-file (or error)* - destination: coll (inserting)*/copy (istream_iterator<string>(cin), istream_iterator<string>(), back_inserter(coll)); //sort elementssort (coll.begin(), coll.end());/*print all elements without duplicates* - source: coll* - destination: standard output (with newline between elements)*/unique_copy (coll.begin(), coll.end(), ostream_iterator<string> (cout, "\n"));
a separator between the
elements
Reverse Iterators
vector<int> coll;//insert elements from 1 to 9for (int i=1; i<=9; ++i) {
coll.push_back(i);}//print all element in reverse ordercopy (coll.rbegin(), coll.rend(), ostream_iterator<int> (cout," ")); cout << endl;
Removing Elementslist<int> coll;for (int i=1; i<=6; ++i) {//insert elements from 6 to 1 and 1 to 6
coll.push_front(i); coll.push_back(i);}
//print all elements of the collectioncout << "pre: ";copy (coll.begin(), coll.end(), ostream_iterator<int> (cout," "));cout << endl;
//remove all elements with value 3remove (coll.begin() , coll.end(), 3);
//print all elements of the collectioncout << "post: ";copy (coll.begin(), coll.end(), ostream_iterator<int> (cout," "));cout << endl;
pre: 6 5 4 3 2 1 1 2 3 4 5 6post: 6 5 4 2 1 1 2 4 5 6 5 6
remove() did not change the number of elements in the collection for which it was called
Removing Elements
//remove all elements with value 3// - retain new endlist<int>::iterator end =remove (coll.begin() , coll.end(), 3);
//print resulting elements of the collectioncout << "post: ";copy (coll.begin(), end, ostream_iterator<int>(cout," "));cout << endl;//print number of resulting elementscout << "number of removed elements: “ << distance(end,coll.end()) << endl;
//remove "removed'' elementscoll.erase (end, coll.end());//print all elements of the modified collectioncopy (coll.begin(), coll.end(), ostream_iterator<int>(cout," "));cout << endl;
remove elements in associative containers
• elements in associative containers are always sorted automatically according to their sorting criterion.
• prevents you from calling removing algorithms for associative containers
• Call their member functions!
Remove elements in Setset<int> coll;//insert elements from 1 to 9for (int i=1; i<=9; ++i) {
coll.insert(i);}//print all elements of the collectioncopy (coll.begin(), coll.end(),ostream_iterator<int>(cout," "));cout << endl;/*Remove all elements with value 3* - algorithm remove() does not work* - instead member function erase() works*/int num = coll.erase(3);//print number of removed elementscout << "number of removed elements: " << num << endl;//print all elements of the modified collectioncopy (coll.begin(), coll.end(), ostream_iterator<int>(cout," "));cout << endl;
Algorithms versus Member Functions• Even if you are able to use an algorithm, a container might
have member functions that provide much better performance.
list<int> coll;//insert elements from 6 to 1 and 1 to 6for (int i=1; i<=6; ++i) { coll.push_front(i); coll.push_back(i);}//remove all elements with value 3//- poor performancecoll.erase (remove(coll.begin(),coll.end(), 3),coll.end());//remove all elements with value 4//- good performancecoll.remove (4);
the algorithm doesn't know that it is operating on a list. Thus, it does what it does for any container: It reorders the elements by changing their values. This behavior contradicts the main advantage of lists - the ability to insert, move, and remove elements by modifying the links instead of the values.
Functions as Algorithm Arguments//function that prints the passed argumentvoid print (int elem){ cout << elem << ' ' ;}int main(){ vector<int> coll; //insert elements from 1 to 9 for (int i=1; i<=9; ++i) { coll.push_back(i); }
//print all elementsfor_each (coll.begin(), coll.end(), print); cout << endl;
}
functional
Another exampleint square (int value){ return value*value;}int main(){ std::set<int> coll1;
std::vector<int> coll2;//insert elements from 1 to 9 into coll1for (int i=1; i<=9; ++i) coll1.insert(i);PRINT_ELEMENTS(coll1,"initialized: ");//transform each element from coll1 to coll2// - square transformed valuesstd::transform (coll1.begin(),coll1.end(), std::back_inserter(coll2), square); PRINT_ELEMENTS(coll2,"squared: ");
}
Unary Predicates
• Unary predicates check a specific property of a single argument.
//predicate, which returns whether an integer is a prime numberbool isPrime (int number){ //ignore negative sign
number = abs(number);// 0 and 1 are prime numbersif (number == 0 || number == 1) { return true;}//find divisor that divides without a remainderint divisor;for (divisor = number/2; number%divisor != 0; --divisor) {
;}//if no divisor greater than 1 is found, it is a prime numberreturn divisor == 1;
}
Unary Predicates (continue)
list<int> coll;//insert elements from 24 to 30for (int i=24; i<=30; ++i) { coll.push_back(i);}//search for prime numberlist<int>::iterator pos;pos = find_if (coll.begin(), coll.end(), isPrime); //predicate
if (pos != coll.end()) {//found cout << *pos << " is first prime number found" << endl;}else {//not found cout << "no prime number found" << endl;}
Binary Predicates/*binary function predicate:*- returns whether a person is less than another person*/bool personSortCriterion (const Person& p1, const Person& p2){/*a person is less than another person*- if the last name is less*- if the last name is equal and the first name is less*/
return p1.lastname()<p2.1astname() ||(!(p2.1astname()<p1.lastname()) && p1.firstname()<p2.firstname());
}
int main(){deque<Person> coll;sort (coll. begin(), coll. end() , personSortCriterion);
}
function object (functor)
• Functional arguments for algorithms don't have to be functions. They can be objects that behave as functions.
• Sometimes you can use a function object when an ordinary function won't work.
functor//simple function object that prints the passed argumentclass PrintInt {public:
void operator() (int elem) const {cout << elem << ' ';
}};int main(){
vector<int> coll;//insert elements from 1 to 9for (int i=1; i<=9; ++i) coll.push_back(i);
//print all elementsfor_each (coll.begin(), coll.end(), PrintInt()); cout << endl;
}
See next page
for_each (coll.begin(), coll.end(), PrintInt());
namespace std {template <class Iterator, class Operation>Operation for_each (Iterator act, Iterator end, Operation op){
while (act != end) { //as long as not reached the endop (*act); // - call op() for actual elementact++; // - move iterator to the next element
}return op; }
}}
• If the third parameter is an ordinary function, it simply calls it with *act as an argument.
• If it is a functor, it calls operator () for the function object op with *act as an argument:• PrintInt::operator()(*act)
More on Functors1. Functors are "smart functions.“– may have other member functions and attributes.– This means that functors have a state.
2. Each functor has its own type.– Ordinary functions have different types only when their
signatures differ.– Functors can have different types even when their
signatures are the same.– Even design hierarchies of functors so that you can have
different, special kinds of one general criterion
3. Functors are usually faster than ordinary functions.– The concept of templates usually allows better
optimization because more details are defined at compile time.
If you process the value to add at runtime, … resort to global variable
void add10 (int& elem){elem += 10;
}
vector<int> coll;for_each (coll.begin(), coll.end(), add10);
template <int theValue>void add (int& elem){elem += theValue;}vector<int> coll;for_each (coll.begin() , coll.end(), add<10>);
class AddValue {int the Value; //the value to add
public:AddValue(int v) : theValue(v) {}//the "function call" for the element adds the valuevoid operator() (int& elem) const {
elem += theValue;}
};for_each (coll.begin(), coll.end(), AddValue(10)) ;
Predefined Functors
//process the square of all elementstransform (coll.begin(), coll.end(), //first source
coll.begin(), //second sourcecoll.begin(), //destinationmultiplies<int>()) ; //operation
set<int> coll;set<int, less<int> > coll; //sort elements with <set<int ,greater<int> > coll; //sort elements with >
deque<int> coll2;//transform all elements into coll2 by multiplying 10transform (coll1 .begin(), coll1 .end(), //source
back_inserter(coll2), //destinationbind2nd(multiplies<int>() ,10)); //operation
• transform() is expecting as its fourth argument an operation that takes one argument
• the value that always should be used as a second argument to get an operation for one argument.
//print 3 raised to the power of all elementstransform (coll.begin(), coll.end(), //source
ostream_iterator<int>(cout," "), //destinationbind1st(fopow<float ,int>() ,3)); //operation
//print all elements raised to the power of 3transform (coll.begin(), coll.end(), //source
ostream_iterator<int> (cout," "), //destinationbind2nd(fopow<float,int>(),3)) ; //operation
bind1st VS bind2nd
//replace value equal to 70 with 42replace_if (coll2.begin(),coll2.end(), //range
bind2nd(equal_to<int>() ,70) , //replace criterion42) ; //new value
//remove all elements with values less than 50coll2.erase(remove_if(coll2.begin(),coll2.end(), //range
bind2nd(less<int>() ,50)), //remove criterioncoll2.end());
Predefined Functors (continues)
Container elements - Value Semantics or Reference Semantics
• All containers create internal copies of their elements and return copies of those elements.
• If you modify objects as elements of the container, you modify a copy, not the original object.
• Hence use pointers as elements carefully when necessary :– objects to which they refer may no longer exist– comparisons may not work as desired because pointers
instead of the objects are compared.
A better solution
• Use a kind of smart pointer as elements– std::auto_ptr
– 6.8 of “The C++ Standard Library”
– There are 6 kinds of smart pointers in boost. Which one is suitable?
X√
Numeric Limits#include <limits>cout << "max(float): “ << numeric_limits<float>::max() << endl;double eps = numeric_limits<double>::epsilon();
Auxiliary Functions
• pmax = max (px, py, int_ptr_less);• std::swap(x,y);
Algorithm Header Files
• #include <algorithm>• #include <numeric>• #include <functional>
• Nonmodifying algorithms• Modifying algorithms• Removing algorithms• Mutating algorithms• Sorting algorithms• Sorted range algorithms• Numeric algorithms
Table 9.6. Sorting Algorithms
Algorithms for Sorted Ranges
The for_each() Algorithmc1ass MeanValue {
long num; // number of elementslong sum; // sum of all element values
public:MeanValue () : num(0), sum(0) {}void operator() (int elem) {
num++; // increment countsum += elem; // add value
}operator double() {// return mean value (implicit type conversion)
return static_cast<double>(sum) / static_cast<double>(num);}
};
double mv = for_each (coll.begin(), coll.end(), MeanValue());
Counting Elements
// count and print elements with value 4num = count (coll.begin(), coll.end(), 4); // count elements with even valuenum = count_if (coll.begin(), coll.end(), isEven);
Minimum and Maximum
*min_element(coll.begin(),coll.end())*max_element(coll.begin(),coll.end(),absLess)
Searching Elements
// find first element with value 4list<int>::iterator pos1;pos1 = find (coll.begin(), coll.end(), 4); // find first element greater than 3pos = find_if (coll.begin(), coll.end(), bind2nd(greater<int>(),3));
// find 3 consecutive elements with value 4pos = search_n (coll.begin(), coll.end(), 4, 3);
// search first occurrence of subcoll in collpos = search (coll.begin(), coll.end(), subcoll.begin(), subcoll.end()); // search last occurrence of subcoll in collpos = find_end (coll.begin(), coll.end(), subcoll.begin(), subcoll.end()); //search first two elements for which the second has double the value of the firstpos = adjacent_find (coll.begin(), coll.end(), doubled);
Replacing Elements//replace all elements with value less than 5 with 0replace_if (coll.begin(), coll.end(), bind2nd(less<int>(),5), 0);
//print all elements with value 5 replaced with 55replace_copy(coll.begin(), coll.end(), ostream_iterator<int>(cout," "),5, 55);
5. Numerics - Complex Numbers
#include <complex>/*complex number with real and imaginary parts*-real part: 4.0*-imaginary part: 3.0*/complex<double> c1(4.0,3.0);• // print complex numbers with real and imaginary parts• cout << "c1: " << c1 << endl;/*create complex number from polar coordinates*-magnitude:5.0*-phase angle:0.75*/complex<float> c2(polar(5.0,0.75));//print complex numbers as polar coordinatescout << "c1: magnitude: " << abs (c1)
<< " (squared magnitude: " << norm(c1) << ") "<< " phase angle: " << arg(c1) << endl;
//print complex conjugates
cout << "c1 conjugated: " << conj(c1) << endl;
cout << "c1 + c2: “ << c1 + complex<double>(c2.real(),c2.imag()) << endl;
Global Numeric Functions
6. Input/Output Stream - Input/Output of Special Types
The standard I/O operators are also defined for types bool, char*, and void*.
bool b(0); // 0 == falsecout << noboolalpha << b << " == " << boolalpha << b << endl;
char buffer [81]; // 80 characters and '\0'std::cin >> std::setw(81) >> buffer;// read, at most, 80 characters:
char* cstring = "hello";std::cout << "string \"" << cstring << "\" is located at address: "<< static_cast<void*>(cstring) << std::endl;
format#include <iomanip>std::cout << std::setw(8) << std::setfill('_') <<-3.14 << ' ' << 42 << std::endl;std::cout << std::setw(8) << "sum: “ << std::setw(8) << 42 << std::endl;
std::cout << 12345678.9 << std::endl;std::cout.setf (std::ios::showpos | std::ios::uppercase);std::cout << 12345678.9 << std::endl;
std::cout.setf (std::ios::hex, std::ios::basefield);
std::cout << std::ios::hex << x << std::endl;std::cout << y << ' ' << std::ios::dec << z << std::endl;
----3.14 42---sum: ------42
1.23457e+07+1.23457E+07
Tight Coupling Using Stream Buffers
// stream for hexadecimal standard outputostream hexout(cout.rdbuf());hexout.setf (ios::hex, ios::basefield);hexout.setf (ios::showbase);// switch between decimal and hexadecimal outputhexout << "hexout: " << 177 << " ";cout << "cout: " << 177 << " ";hexout << "hexout: " << -49 << " " ;cout << "cout: " << -49 << " ";hexout << endl;
Hexout: 0xb1 cout: 177 hexout: 0xfffffcf cout: -49
Redirecting Standard Streams
// output written to cout is not sent to the standard output channel but rather to the file cout.txtstd::ofstream file ("cout.txt");std::cout.rdbuf (file.rdbuf());
//copyfmt() can be used to assign all format information of a given stream to another stream objectstd::ofstream file ("cout.txt");file.copyfmt (std::cout);std::cout.rdbuf (file.rdbuf());
References
• The Use of STL and STL Extensions in CGAL• C++ Primer