namespace Matrices{ class 1DArray; //classes class 2DArray; 1DArray operator +(const 1DArray& a, const 1DArray& b); //functions 2DArray operator +(const 2DArray& a, const 2DArray& b); //functions ostream& display(const 1DArray& a); //functions ostream& display(const 2DArray& a); //functions }To refer to a member of this namespace, one uses the scope resolution operator, e.g., Matrices::display(a) and Matrices::1DArray.
The standard library is defined in a namespace called std. So if you refer to a type (class) or function from the standard library, like string, you should write std::string.
#include<string> #include<list> std::string s = "Four legs Good; two legs Baaaad!"; std::list<std::string> slogans;Many of the classes in the standard library are template classes. For example, list is a template class. The standard library is sometimes called the standard template library (STL).
Writing std:: as a prefix is a pain; so one can dump the names into the global namespace in the following way:
#include<string> //make the standard string facilities accessible using namespace std; //make std names available without std:: prefix string s = "Ignorance is bliss!" //ok; sting is std::stringIt is generally in poor taste to dump every name from a namespace into the global namespace, because you run the risk of name clashes if you name a variable the same as something in the standard library. You can dump the names from a namespace into a local space, e.g., you could make the names local to a function:
#include<string> // complex number facility std::string function() { using namespace std; //make std names available without std:: prefix string s = "Ignorance is bliss!"; //ok; sting is std::string //other stuff }(Actually many compilers like g++ just assume the standard library names are in the global namespace, so you don't have to write ``using namespace std.'' But this may change as compilers are upgraded.)
As we mentioned earlier, the iostream classes are in the standard library. So we could write,
#include<iostream> std::cout << "hello! \n";We have just been writing cout because we included iostream.h. In some cases, such as iostream, by including <X.h> rather than <X>, we make the std names global. iostream and fstream are examples of this. So are headers in the C standard library. A standard header with a name starting with the letter c is equivalent to a header in the C standard library. For every header <cX> defining names in the std namespace, there is a header <X.h> defining the same names in the global namespace. For example, the standard math library facility <cmath> corresponds to the C library <math.h>. We can write
#include<cmath> void main(){ float x=2; float a; a = std::pow(x,4); }
or we can dump the names in the global namespace:
#include<math.h> void main(){ float x=2; float a; a = pow(x,4); //ok; refers to std::pow }
By the way, blank spaces matter in include statements. For example, the following will not work:
#include< iostream.h >
The standard library has facilities that deal with
#include <string> string s1 = "Hello"; string s2 = "world"; void func() { string s3 = s1 + "," + s2 + "!\n"; cout << s3; }s3 is initialized to Hello, world! followed by a newline. For strings, addition means concatenation. You can add strings, string literals, and characters to a string.
In many applications, the most common form of concatenation is adding something to the end of a string. This is directly supported by the += operation. For example:
#include <string> void func(string& s1, string& s2) { s1 = s1 + '\n'; //append newline s2 += '\n'; //append newline }These two ways of adding to the end of a string are semantically equivalent.
Naturally, strings can be compared against each other and against string literals. For example:
#include <string> string password; void respond(const string& answer) { if (answer==password) { // do something} else if (answer=="yes") { // do something else} }
The string class also provides the ability to manipulate substrings. For example,
#include <string> string name = "George Washington"; void func() { string s = name.substr(7,10); //s = "Washington" name.replace(0,6,"Mrs. Martha"); //name becomes "Mrs. Martha Washington" }The substr() operation returns a string that is a copy of the substring indicated by its arguments. The first argument is the position of the first letter of the substring, and the second argument is the length of the substring. Since indexing starts from 0, s gets the value of "Washington".
The replace operation replaces a substring with a value. In this case the substring starting at 0 with length 6 is George; it is replaced by Mrs. Martha. Thus the final value of name is Mrs. Martha Washington. Note that the replacement string need not be the same size as the substring that it is replacing.
#include<complex> template<class scalar> class complex { public: complex(scalar re, scalar im); // ... };The usual arithmetic operations and the most common mathematical functions are supported for complex numbers. For example:
#include<complex> template<class C> complex<C> pow(const complex<C>&, int); //raise complex number to a power template<class C> complex<C> cosh(const complex<C>&); //cosh void f(complex<float> fl, complex<double> db) { complex<long double> ld = fl + sqrt(db); db += fl*3; fl = pow(1/fl,2); // ... }More details can be found in Stroustrup, section 22.5.
#include<valarray> template<class T> class valarray { // ... T& operator[](size_t); // ... }The type
size_t
is the unsigned integer type that the implementation
uses for array indices.
The usual arithmetic operations and the most common mathematical functions are supported for <valarray>s. For example:
#include<valarray> template<class T> valarray<T> abs(const valarray<T>&); // absolute value void func(valarray<double>& a1, valarray<double>& a2) { valarray<double> a = a1 * 3.14 + a2/a1; a2 += a1 * 3.14; a = abs(a); double d = a2[7]; // ... }More details can be found in Stroustrup, section 22.4.
Container share the notion of a sequence. We can represent a sequence graphically like this:
'\0'
which ends
char* strings), but it doesn't have to be. We need some standard
notation for operations such as ``access an element through an
iterator'' and ``make the iterator refer to the next element.'' The
obvious choices (once you get the idea) are to use the dereference
operator * to mean ``access an element through an iterator''
and the increment operator ++ to mean
``make the iterator refer to the next element.''
Let's examine some of the container classes:
class Point; Point pts[100];then it's hard to resize the array pts later in the program. The standard library provides a templated vector class to take care of that:
#include <vector> class Point; vector<Point> pts(100); //Note use of parentheses void display(int i) //simple use; exactly as for an array { cout << pts[i].x() << endl; } // Print x-coordinate void add_entries(int n) //increase size by n { pts.resize(pts.size() + n); }The vector member function size() gives the number of elements. Note the use of parentheses in the definition of pts. We made a single object of type vector<Point> and supplied its initial size as an initializer. This is very different from declaring a built-in array:
vector<Point> point(1000); //vector with 1000 points vector<Point> points[1000]; //1000 empty vectorsShould you make the mistake of using [ ] where you meant () when declaring a vector, your compiler will almost certainly catch the mistake and issue an error message when you try to use the vector.
A vector is a single object that can be assigned. In other words, the assignment operator for vector has been overloaded. For example:
#include <vector> class Point; vector<Point> pts(100); void func(vector<Point>& v) { vector<Point> v2 = pts; v = v2; // ... }Assigning a vector involves copying its elements. Thus, after initialization and assignment in func, v and v2 each holds a separate copy of every Point in pts. When a vector holds many elements, such innocent-looking assignments and initializations can be prohibitively expensive. Where copying is undesirable, references or pointers should be used. Or one can use reference counting as we described in lecture 13.
#include <vector> class Point; vector<Point> pts(100); void func() { double xx = pts[101].x(); } // 101 is out of boundsThe initialization of xx will likely give some garbage value to xx rather than giving an error. This is undesirable, so we can make a derived class Vec that will do bounds checking. A Vec is like a vector, except that it throws an exception of type
out_of_range
if a subscript is out of bounds.
#include <vector> template<class T> class Vec: public vector<T> //Vec is derived from vector { public: Vec() : vector<T>() {} Vec(int s) : vector<T>(s) {} T& operator[](int i) {return at(i);} //bounds checked const T& operator[](int i) const {return at(i);} //bounds checked };The at() operation is a vector subscript operation that throws an exception of type
out_of_range
if its argument is out of the
vector's range. Notice that Vec is derived from vector.
We use Vec in exactly the same way as we used vector.
#include <vector> class Point; Vec<Point> pts(100); void display(int i) //simple use; exactly as for a vector { cout << pts[i].x() << endl; } // Print x-coordinateAn out of bounds request will throw an exception that the user can catch. For example:
void f() { try { for(int i=0; i<1000; i++) display(i); } catch(out_of_range) { cout << "range error\n"; } }
#include <list> class Point; list<Point> pts;It differs from a vector in a few respects. For example, it is easier to add and delete entries from a list than from a vector. When we use a list, we tend not to access elements using subscripting the way we commonly do for vectors. Instead, we might search a list looking for an element with a given value. To do this, we take advantage of the fact that a list is a sequence as we described earlier. Recall that a sequence has an iterator to refer to an element and provides an operation that makes the iterator refer to the next element of the sequence. The dereference operator * is used to ``access an element through an iterator'' and the increment operator ++ is used to ``make the iterator refer to the next element.''
#include <list> class Point; list<Point> pts; void print_point(const float& xx, const float& yy) { typedef list<Point>::const_iterator LI; for (LI i=pts.begin(); i != pts.end(); ++i) { Point& p = *i; //reference used as shorthand if((xx == p.x()) && (yy == p.y())) cout << p.x() << ' ' << p.y() << '\n'; } }The search for the point with the coordinates (xx,yy) starts at the beginning of the list and proceeds until either (xx,yy) are found or the end is reached. Every standard library container provides the functions begin() and end(), which return an iterator to the first and to one-past-the-last element, respectively. Given an iterator i, the next element is ++i. Given an iterator i, the element it refers to is
*i
.
A user need not know the exact type of the iterator for a standard
container. That iterator type is part of the definition of the
container and can be referred to by name. When we don't need to modify
an element of the container, const_iterator
is the type we
want. Otherwise, we use the plain iterator type.
Adding elements to a list is easy:
void add_point(Point& p, list<Point>::iterator i) { pts.push_front(p); //add at beginning pts.push_back(p); //add at end pts.insert(i,p); //add before the element 'i' refers to }
#include <string> #include <map> class Point; map<string,Point> California; // A city name paired with a pointHere we might imagine a map of California with points corresponding to cities. The name of a city is a string. In other contexts, a map is known as an associative array or a dictionary.
When indexed by a value of its first type (called the key) a map returns the corresponding value of the second type (called the value or the mapped type). For example:
void print_city(const string& s) { if (Point i = California[s]) cout << s << ' ' << i.x() << " , " << i.y() << endl; }If no match is found for the key s, a default value is returned from the map object. For example, the default value for California could be 0. Then Point i is zero and the if statement is false. Each element in a map is a pair. The first element of a pair is called first, and the second element is called second. For example, suppose we have a map that consists of a sequence of (string,int) pairs. Then we could define a print function as follows:
#include <algo> #include <string> #include <map> #include <iostream.h> void print(const pair<const string,int>& r) { cout << r.first << r.second << '\n'; } int main() { map<string,int> histogram; //initialize pairs... for_each(histogram.begin(),histogram.end(),print); }where
for_each
is an algorithm that applies the print function
to every element of a sequence, in this case, to every pair of
a map.
The elements of a map are sorted in a particular order so that the less-than operation is defined for its key types. For elements for which there is no obvious order or when there is no need to keep the container sorted, one might consider using a hash map.
The standard containers and their basic operations are designed to be
similar from a notational point of view. Furthermore, the meanings of the
operations are equivalent for the various containers. For example,
push_back()
can be used (reasonably efficiently) to add elements
to the end of a vector as well as for a list, and every
container has a size() member function that returns its number
of elements.
#include <list> #include <vector> #include <algo> void f(vector<float>& ve, list<float>& le) { sort(ve.begin(),ve.end()); unique_copy(ve.begin(),ve.end(),le.begin()); }In the example, sort() sorts the sequence in increasing order from ve.begin() to ve.end() - which just happens to be all the elements of a vector. Of course, sorting only makes sense if having one element is greater than (>) another is defined. For writing, you need only specify the first element to be written. If more than one element is written, the elements following that initial element will be overwritten. If we want to add the new elements to the end of a container, we can write:
#include <list> #include <vector> #include <algo> void f(vector<float>& ve, list<float>& le) { sort(ve.begin(),ve.end()); unique_copy(ve.begin(),ve.end(),le.back_inserter(le)); }
When you first encounter a container, a few iterators referring to useful elements can be obtained; begin() and end() are the best examples of this. In addition, many algorithms return iterators. For example, the standard algorithm find looks for a value in a sequence and returns an iterator to the element found.
#include <string> #include <iostream.h> #include <algo> void find_it(const string& s, char c) { string::const_iterator i = find(s.begin(), s.end(), c); if(i != s.end()) cout << "found " << c << endl; }The find algorithm returns an iterator to the first occurrence of a value in a sequence or the one-past-the-end iterator.
Counting occurrences of an element is another algorithm provided by the standard library. count takes a sequence as its argument, rather than a container. For example,
#include <string> #include <iostream.h> #include <algo> #include <complex> void f(list<complex>& lc, vector<string>& vc, string s); { int i1 = count(lc.begin(),lc.end(),complex(1,3)); int i2 = count(vc.begin(),vc.end(),"Michael"); int i3 = count(s.begin(), s.end(), 'x'); }
Some algorithms apply a function to the elements of a sequence.
for_each
is an example of this. It goes through
the elements of a sequence and applies a function to each element.
For example, suppose that we have
the class Student and that each student has a name.
Let print be a function that prints the name of a student.
#include <iostream.h> #include <algo> #include <list> class Student; void print(Student& st) { cout << st.name() << endl; } main() { list<Student> roll; // initialize students ... // print names of all the students in roll for_each(roll.begin(),roll.end(),print); }
for_each
goes through the list of students and prints
the name of each student. Notice that we pass do not pass the container
list, but rather a sequence by indicating the beginning and end
of the sequence.
A list iterator must be something more complicated than a simple pointer to an element because an element of a list in general does not know where the next element of that list is. Thus, a list iterator might be a pointer to a link:
const_iterator
.
(We use const_iterator
if we are not going to change the element.)
For example, list<Point>::iterator is the general iterator
type for list<Point>. We rarely have to worry about the
details of how that type of iterator is defined.